> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream Live and Recorded Video from Cameras

> Embed live and recorded video from Rhombus cameras in your applications using HLS streams, thumbnails, and shared playback URLs delivered through the API.

## Overview

Rhombus cameras expose multiple ways to access video content through the public API:

* **Live streaming** for real-time monitoring, embedded as a Rhombus-hosted iframe
* **HLS manifests** (`.m3u8`) for direct playback in your own player
* **Thumbnail images** for dashboards and quick previews
* **Frame captures** at any historical timestamp

For downloading recorded footage as MP4 files, see [Recorded Video Access](#recorded-video-access).

## Getting Camera Thumbnails

All Rhombus cameras automatically upload thumbnail images. These are perfect for dashboards, monitoring interfaces, and quick status checks.

### Thumbnail Endpoint

Retrieve camera thumbnails using a GET request to:

```text theme={null}
https://media.rhombussystems.com/media/{cameraUuid}/{mediaRegion}/snapshot.jpeg
```

<Note>
  All `media.rhombussystems.com` endpoints use the same authentication as the main API — include the `x-auth-scheme` and `x-auth-apikey` headers on every request.
</Note>

To get the `cameraUuid` and `mediaRegion` for a camera, call `POST /api/camera/getMinimalCameraStateList`. The response contains a `cameraStates` array; each entry has `uuid` and `mediaRegion` fields.

### Basic Implementation

```bash cURL theme={null}
# 1. List cameras to get UUIDs and media regions
curl -X POST \
  "https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{}'
```

```bash cURL theme={null}
# 2. Fetch the thumbnail using a camera's UUID and media region
curl -X GET \
  "https://media.rhombussystems.com/media/{CAMERA_UUID}/{MEDIA_REGION}/snapshot.jpeg" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  --output thumbnail.jpg
```

### Getting Frames at Specific Times

To get a JPEG image at a specific historical timestamp, call `POST /api/video/getExactFrameUri`. The response contains a `frameUri` — make a GET request to that URI with the same authentication headers to download the frame.

```javascript theme={null}
async function getFrameAtTime(cameraUuid, timestampMs, apiKey) {
  // Get a signed frame URI
  const frameUriResponse = await fetch('https://api2.rhombussystems.com/api/video/getExactFrameUri', {
    method: 'POST',
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      timestampMs: timestampMs // epoch milliseconds
    })
  });

  const { frameUri } = await frameUriResponse.json();

  // Fetch the actual JPEG
  const frameResponse = await fetch(frameUri, {
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey
    }
  });

  return frameResponse.blob();
}
```

The request also accepts optional `downscaleFactor`, `jpgQuality`, and `permyriadCropX`/`permyriadCropY`/`permyriadCropWidth`/`permyriadCropHeight` fields (each in 1/10000ths of the image dimensions, where `10000` = 100%).

## Live Video Streaming

Rhombus offers two ways to play live video in your application:

1. **Embed the Rhombus player as an iframe** — easiest path; analytics and timeline events render automatically.
2. **Play the HLS manifest directly** — use your own player (`hls.js`, Safari native, etc.) for full UI control.

Both come from the same API call: `POST /api/camera/createSharedLiveVideoStream`. The response contains both URLs:

| Field                       | Description                                                |
| --------------------------- | ---------------------------------------------------------- |
| `sharedLiveVideoStreamUrl`  | Hosted Rhombus player URL — embed in an iframe             |
| `sharedLiveM3U8StreamUrl`   | HLS (`.m3u8`) manifest — play directly with any HLS player |
| `sharedLiveVideoStreamUuid` | UUID of the created share, useful for revoking later       |

### Embedding Shared Streams as iframes (Recommended)

Create a shared stream, then drop the returned URL into an iframe. The iframe player handles authentication via the URL itself, so no API key is needed in the browser.

```javascript theme={null}
async function createSharedStream(cameraUuid, apiKey) {
  const response = await fetch('https://api2.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
    method: 'POST',
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ cameraUuid })
  });

  const data = await response.json();
  return data.sharedLiveVideoStreamUrl;
}
```

Embed the returned URL:

```html theme={null}
<iframe
  allow="fullscreen"
  frameborder="0"
  height="100%"
  width="100%"
  src="SHARED_STREAM_URL_GOES_HERE">
</iframe>
```

### React Component Example

```jsx theme={null}
import React, { useEffect, useState } from 'react';

const RhombusSharedStream = ({
  cameraUuid,
  apiKey,
  width = "100%",
  height = "400px",
  options = {}
}) => {
  const [streamUrl, setStreamUrl] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    (async () => {
      try {
        setLoading(true);
        const response = await fetch('https://api2.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
          method: 'POST',
          headers: {
            'x-auth-scheme': 'api-token',
            'x-auth-apikey': apiKey,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ cameraUuid })
        });

        if (!response.ok) throw new Error('Failed to create shared stream');
        const data = await response.json();

        const url = new URL(data.sharedLiveVideoStreamUrl);
        Object.entries(options).forEach(([key, value]) => {
          url.searchParams.set(key, value);
        });

        if (!cancelled) {
          setStreamUrl(url.toString());
          setError(null);
        }
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

    return () => { cancelled = true; };
  }, [cameraUuid, apiKey, options]);

  if (loading) return <div>Loading stream...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <iframe
      src={streamUrl}
      width={width}
      height={height}
      frameBorder="0"
      allow="fullscreen"
      title={`Camera ${cameraUuid} Stream`}
    />
  );
};

export default RhombusSharedStream;
```

### URL Parameters for Enhanced Control

Customize the embedded player with URL parameters. All parameters can be set to `true` or `false`:

| Parameter         | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `disableautoplay` | Enable or disable video autoplay upon loading           |
| `hideevents`      | Enable or disable the timeline and related events       |
| `realtime`        | Enable or disable real-time streaming by default        |
| `showheader`      | Enable or disable zoom & streaming buttons at the top   |
| `showfooter`      | Enable or disable camera name and timestamp at bottom   |
| `lowRes`          | Force low resolution on the video stream                |
| `performanceMode` | Force performance mode allowing GPU usage               |
| `t`               | Timestamp (epoch milliseconds) where video should start |

#### Parameter Usage

Append parameters to the shared stream URL: `https://{{url}}/?{{variable}}=true&{{variable}}=false`

**Example:**

```text theme={null}
https://console.rhombussystems.com/share/live/ehrF58ABSj2VT8QzXh7GVA?disableautoplay=true&hideevents=true&realtime=true
```

#### React Usage with Parameters

```jsx theme={null}
<RhombusSharedStream
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  options={{
    disableautoplay: 'true',
    hideevents: 'true',
    realtime: 'true',
    showheader: 'false',
    lowRes: 'false'
  }}
/>
```

### Embedding HLS Streams Directly

If you want to render live video in your own player rather than the hosted iframe, use `sharedLiveM3U8StreamUrl` from the same `createSharedLiveVideoStream` response. The manifest is a standard HLS `.m3u8` and is signed with a token in the URL itself — no `x-auth-apikey` header is needed when fetching it.

<Note>
  The HLS URL is single-org-scoped and time-limited. Treat it as a credential and don't expose it in public-facing pages without an expiration policy.
</Note>

Safari plays HLS natively. For other browsers, use [hls.js](https://github.com/video-dev/hls.js):

```html theme={null}
<video id="rhombus-live" controls autoplay muted style="width: 100%;"></video>

<script type="module">
  import Hls from 'https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js';

  async function startLive(cameraUuid, apiKey) {
    const response = await fetch('https://api2.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
      method: 'POST',
      headers: {
        'x-auth-scheme': 'api-token',
        'x-auth-apikey': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ cameraUuid })
    });

    const { sharedLiveM3U8StreamUrl } = await response.json();
    const video = document.getElementById('rhombus-live');

    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource(sharedLiveM3U8StreamUrl);
      hls.attachMedia(video);
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = sharedLiveM3U8StreamUrl;
    }
  }

  startLive('YOUR_CAMERA_UUID', 'YOUR_API_KEY');
</script>
```

Because creating a shared stream requires an API key, do the `createSharedLiveVideoStream` call from a server you control and pass only the resulting `sharedLiveM3U8StreamUrl` (or `sharedLiveVideoStreamUrl`) to the browser. Keep the API key out of client code.

## Recorded Video Access

Downloading recorded footage as a single MP4 file is **not exposed as a public API operation**. The Rhombus storage layer serves recorded video as a sequence of media segments rather than a pre-rendered MP4, so producing a downloadable clip requires fetching and concatenating segments client-side.

The supported way to do this is the [Rhombus CLI](/rhombus-cli):

```bash theme={null}
# Download the last hour of footage from a camera as MP4
rhombus footage --camera CAMERA_UUID --duration 1h --output footage.mp4

# Download the clip associated with a specific alert
rhombus alert download --alert ALERT_UUID --output alert.mp4
```

For alert-driven workflows, configure a [webhook](/webhooks) to deliver alert events to your service, then call the CLI (or replicate its segment-fetch logic) to retrieve the corresponding clip.

To list recent alerts programmatically, use `POST /api/event/getPolicyAlerts` — the previous `getPolicyAlertsV2` endpoint is deprecated.

## Complete Implementation Example

A React component supporting both thumbnail and live-stream view types:

```jsx theme={null}
import React, { useEffect, useRef, useState, useCallback } from 'react';

const RhombusCameraViewer = ({
  cameraUuid,
  apiKey,
  viewType = 'thumbnail', // 'thumbnail' | 'stream'
  autoRefresh = true,
  refreshInterval = 5000,
  streamOptions = {},
  width = "100%",
  height = "400px"
}) => {
  const [thumbnailUrl, setThumbnailUrl] = useState(null);
  const [streamUrl, setStreamUrl] = useState(null);
  const [cameraDetails, setCameraDetails] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const lastObjectUrl = useRef(null);

  const getCameraDetails = useCallback(async () => {
    const response = await fetch('https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList', {
      method: 'POST',
      headers: {
        'x-auth-scheme': 'api-token',
        'x-auth-apikey': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({})
    });

    if (!response.ok) throw new Error('Failed to list cameras');
    const data = await response.json();
    const camera = (data.cameraStates || []).find(c => c.uuid === cameraUuid);
    if (!camera) throw new Error('Camera not found');
    return camera;
  }, [cameraUuid, apiKey]);

  const loadThumbnail = useCallback(async (camera) => {
    const response = await fetch(
      `https://media.rhombussystems.com/media/${camera.uuid}/${camera.mediaRegion}/snapshot.jpeg`,
      { headers: { 'x-auth-scheme': 'api-token', 'x-auth-apikey': apiKey } }
    );
    if (!response.ok) throw new Error('Failed to load thumbnail');

    const blob = await response.blob();
    const url = URL.createObjectURL(blob);
    if (lastObjectUrl.current) URL.revokeObjectURL(lastObjectUrl.current);
    lastObjectUrl.current = url;
    setThumbnailUrl(url);
  }, [apiKey]);

  const createSharedStream = useCallback(async () => {
    const response = await fetch('https://api2.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
      method: 'POST',
      headers: {
        'x-auth-scheme': 'api-token',
        'x-auth-apikey': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ cameraUuid })
    });
    if (!response.ok) throw new Error('Failed to create shared stream');

    const data = await response.json();
    const url = new URL(data.sharedLiveVideoStreamUrl);
    Object.entries(streamOptions).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });
    setStreamUrl(url.toString());
  }, [cameraUuid, apiKey, streamOptions]);

  useEffect(() => {
    let cancelled = false;
    let interval = null;

    (async () => {
      try {
        setIsLoading(true);
        const camera = await getCameraDetails();
        if (cancelled) return;
        setCameraDetails(camera);

        if (viewType === 'thumbnail') {
          await loadThumbnail(camera);
          if (autoRefresh) {
            interval = setInterval(() => {
              loadThumbnail(camera).catch(err => setError(err.message));
            }, refreshInterval);
          }
        } else if (viewType === 'stream') {
          await createSharedStream();
        }

        setError(null);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setIsLoading(false);
      }
    })();

    return () => {
      cancelled = true;
      if (interval) clearInterval(interval);
      if (lastObjectUrl.current) {
        URL.revokeObjectURL(lastObjectUrl.current);
        lastObjectUrl.current = null;
      }
    };
  }, [viewType, autoRefresh, refreshInterval, getCameraDetails, loadThumbnail, createSharedStream]);

  if (error) {
    return (
      <div style={{ padding: '20px', border: '1px solid #ff6b6b', borderRadius: '4px' }}>
        <p>Error: {error}</p>
      </div>
    );
  }

  if (isLoading) {
    return <div style={{ padding: '20px', textAlign: 'center' }}>Loading camera...</div>;
  }

  return (
    <div style={{ width, height }}>
      {viewType === 'thumbnail' && thumbnailUrl && (
        <img
          src={thumbnailUrl}
          alt={cameraDetails?.name || `Camera ${cameraUuid}`}
          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
        />
      )}
      {viewType === 'stream' && streamUrl && (
        <iframe
          src={streamUrl}
          width="100%"
          height="100%"
          frameBorder="0"
          allow="fullscreen"
          title={`Camera ${cameraUuid} Stream`}
        />
      )}
    </div>
  );
};

export default RhombusCameraViewer;
```

### Usage Examples

```jsx theme={null}
// Thumbnail view with auto-refresh
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="thumbnail"
  autoRefresh={true}
  refreshInterval={3000}
  width="640px"
  height="480px"
/>

// Live stream with custom options
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="stream"
  streamOptions={{
    disableautoplay: 'false',
    hideevents: 'false',
    realtime: 'true',
    showheader: 'true',
    showfooter: 'true'
  }}
  width="100%"
  height="600px"
/>
```

## Best Practices

### Performance Optimization

* **Thumbnail caching**: Cache thumbnails client-side and refresh on a sensible interval rather than polling continuously.
* **Lazy loading**: Load video content only when needed or when in viewport.
* **Quality selection**: Use `lowRes=true` on the iframe player for grids and dashboards.

### Security Considerations

* **Keep API keys server-side**: Never expose `x-auth-apikey` in browser code. Call `createSharedLiveVideoStream` from your backend and pass only the resulting URLs to the client.
* **HTTPS only**: Always use HTTPS for API calls and shared stream URLs.
* **Scope keys narrowly**: Limit each API key to the minimum permissions required.
* **Set expirations on shared streams**: Pass `expirationTimeSecs` when creating shared streams so URLs don't outlive the use case.

### Error Handling

```javascript theme={null}
class RhombusVideoError extends Error {
  constructor(message, code, cameraUuid) {
    super(message);
    this.name = 'RhombusVideoError';
    this.code = code;
    this.cameraUuid = cameraUuid;
  }
}

async function fetchWithRetry(url, init, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 10000);
    try {
      const response = await fetch(url, { ...init, signal: controller.signal });
      clearTimeout(timer);
      if (!response.ok) {
        throw new RhombusVideoError(`HTTP ${response.status}`, response.status, init?.cameraUuid);
      }
      return response;
    } catch (error) {
      clearTimeout(timer);
      if (attempt === retries) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
}
```

### Accessibility

```jsx theme={null}
<video
  controls
  aria-label={`Live feed from camera ${cameraName}`}
  poster={thumbnailUrl}
>
  <track kind="captions" src="captions.vtt" srcLang="en" label="English" />
  Your browser does not support the video tag.
</video>
```

## Troubleshooting

**Thumbnail not loading**

* Verify `cameraUuid` and `mediaRegion` came from a recent `getMinimalCameraStateList` response — `mediaRegion` can change.
* Check that the API key has access to the camera's location.
* Confirm the camera is online (`connectionStatus` in the camera state response).

**Stream connection failures**

* Check network connectivity to `*.rhombussystems.com`.
* For HLS playback, confirm the browser supports HLS natively or that `hls.js` is loaded.
* Inspect firewall/proxy rules — corporate proxies frequently block media segment requests.

**Poor video quality**

* Pass `lowRes=true` for bandwidth-constrained clients.
* Check upstream bandwidth at the camera site.

## Next Steps

<CardGroup cols={3}>
  <Card title="Video Player" icon="play" href="/implementations/video-player">
    Build a custom video player with HLS playback and timeline controls.
  </Card>

  <Card title="LAN Realtime Detection" icon="vector-square" href="/implementations/lan-realtime-detection-overlay">
    Parse AI bounding boxes from the LAN H.264 WebSocket stream and overlay them on live video.
  </Card>

  <Card title="Backup Footage" icon="download" href="/implementations/backup-footage-local-storage">
    Download and archive camera footage to local storage.
  </Card>
</CardGroup>
