API Reference

Our Playback API provides endpoints that allow you to stream historical footage from the cameras. In this end-to-end example, we walk you through the steps for streaming playback video.

Prerequisites

To use this endpoint, you'll need:

  • A valid Lumana api key.
  • A camera id.

Generating a Lumana API key:

There are two options to generate Lumana API key:

  1. In the platform go to: Organizations Settings -> API Keys -> Generate Key.
  2. Via the Lumana API:
$ curl --location 'https://access.lumana.ai/v1/api-key' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "api-key-name",
    "expiresIn": 30,
    "email": "your-email",
    "password": "your-password"
    ,"orgId": "your-org-id"
}'


Getting camera id

You can get cameraId by using the API or by going to the platform -> Edit camera -> Camera Id

Getting Lumana HLS Playback URL:

Each start request is identified by a sessionId, which can be reused in future requests to improve performance and response times for the same camera by maintaining an active session. You can either use the same session to start new stream from the same camera or proactively stop an active session by specifying the corresponding sessionId and cameraId.

Request example:

curl --request GET \
     --url 'https://access.lumana.ai/v1/playback/start?cameraId=<cameraId>&start=<start-timestamp>&end=<end-timestamp>&smartStorage=<boolean>' \
     --header 'authorization: Bearer <api-key>'

Response example:

{
  "cameraId": "<cameraId>",
  "sessionId": "028a1945-4860-4c5a-a8e7-652bc4fd3bbc",
  "url": "https://playback-dev.lumana.ai/session/028a1945-4860-4c5a-a8e7-652bc4fd3bbc/camera/<cameraId>/manifest.m3u8",
  "currentSessions": 1
}

Stream historical footage from camera:

Once you have the url you should start streaming in the first 90 seconds as the session will be invalid due to inactivity timeout. You can stream the video from the camera using this player example.

Enter the playback url obtained from the previous request and play the video.

<!DOCTYPE html>
<html lang="en">
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Live View Player</title>
  <style>
    /* General styles */
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background-color: #121212;
      color: #fff;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      padding: 20px;
      box-sizing: border-box;
    }

    /* Video container */
    #video-container {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 50vh; /* Adjusted height for better fit */
      background-color: #000;
      border-radius: 10px;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      margin-bottom: 50px; /* Increased space between video and controls */
    }

    video {
      width: 80%;
      max-width: 800px;
      border-radius: 10px;
    }

    /* Controls container */
    #controls {
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 620px;
    }

    /* Token input styling */
    #token-input, #url-input {
      width: 100%;
      height: 50px;
      font-size: 18px;
      padding: 10px;
      border-radius: 5px;
      border: 2px solid #333;
      background-color: #1e1e1e;
      color: #fff;
      margin-bottom: 10px; /* Space between input fields */
      box-sizing: border-box;
    }

    /* Connect button styling */
    #connect-btn {
      width: 100%;
      height: 60px;
      font-size: 20px;
      background-color: #007bff;
      color: #fff;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      transition: background-color 0.3s ease;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
      margin-top: 10px; /* Space between button and inputs */
    }

    #connect-btn:hover {
      background-color: #0056b3;
    }
  </style>
    <h1>Live View Player</h1>
</head>
<body>
  <!-- Controls container -->
  <div id="controls">
    <input type="text" id="url-input" value="https://stream.lumana.ai" placeholder="Live View URL">
    <input type="text" id="token-input" placeholder="Live View Token">
    <button id="connect-btn">Connect</button>
  </div>

  <!-- Video container -->
  <div id="video-container">
    <video id="video-player" autoplay playsinline></video>
  </div>
</body>

<script type="module">
    async function connectToLiveView() {
      const liveViewURL = document.getElementById('url-input').value;
      const token = document.getElementById('token-input').value;

      if (!liveViewURL || !token) {
        alert('Please enter both Live View URL and Token');
        return;
      }


      try {
        const room = new LivekitClient.Room({
        // automatically manage subscribed video quality
        adaptiveStream: true,

        // default capture settings
        videoCaptureDefaults: {
            resolution: LivekitClient.VideoPresets.h720.resolution,
        },
        });

        room.prepareConnection(liveViewURL, token);

        // set up event listeners
        room.on(LivekitClient.RoomEvent.TrackSubscribed, handleTrackSubscribed)
            .on(LivekitClient.RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
            .on(LivekitClient.RoomEvent.Disconnected, handleDisconnect)
            .on(LivekitClient.RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);

        // connect to room
        await room.connect(liveViewURL, token);
        console.log('connected to room', room.name);

        function handleTrackSubscribed(track, publication, participant) {
            console.log('handleTrackSubscribed', track.kind);

        if (track.kind === LivekitClient.Track.Kind.Video) {
            const videoElement = document.getElementById('video-player');
            track.attach(videoElement);
        }
        }

        function handleTrackUnsubscribed(track, publication,participant) {
            track.detach().forEach(element => element.remove());
        }

        function handleLocalTrackUnpublished(publication, participant) {
          // when local tracks are ended, update UI to remove them from rendering
          publication.track.detach();
        }

        function handleDisconnect() {
        console.log('disconnected from room');
        }
      } catch (error) {
        console.error('Error connecting to Live View:', error);
        alert(`Failed to connect ${JSON.stringify({liveViewURL, token}, null, 2)}` );
      }
    }
    document.getElementById('connect-btn').addEventListener('click', connectToLiveView);
  </script>
</body>
</html>