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

# Build HLS Video Playback

> Upload a video, create an adaptive HLS stream, clip it, and play it back from your app.

This guide creates a stream-ready video asset from a file in Convertly Storage. Use it when your app needs adaptive playback instead of a single downloadable video.

<div className="convertly-callout">
  Adaptive video streaming is available on every plan, with source-minute and delivery allowances that scale by plan.
</div>

## 1. Upload the source video

Create a direct upload session, upload the bytes to storage, then complete the upload. The completed file ID becomes `sourceFileId`.

```bash theme={"system"}
curl -X POST "https://convertly.sh/api/uploads" \
  -H "Authorization: Bearer $CONVERTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "lesson.mp4",
    "contentType": "video/mp4",
    "sizeBytes": 104857600
  }'
```

After upload completion, keep the returned `file.id`.

## 2. Create the HLS/DASH stream

```bash theme={"system"}
curl -X POST "https://convertly.sh/api/video/streams" \
  -H "Authorization: Bearer $CONVERTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceFileId": "5e8b2c74-19d3-4a6f-b082-c7e5419d36af",
    "profile": "hd",
    "packageFormats": ["hls", "dash"],
    "access": "signed",
    "segmentDuration": 6,
    "allowedDomains": ["app.example.com"]
  }'
```

For a clip:

```json theme={"system"}
{
  "clip": {
    "start": 80,
    "duration": 45,
    "mode": "accurate"
  }
}
```

Use `mode: "fast"` for cheaper keyframe-boundary clips when exact frame boundaries do not matter.

## 3. Wait for readiness

Poll the asset:

```bash theme={"system"}
curl "https://convertly.sh/api/video/streams/<asset-id>" \
  -H "Authorization: Bearer $CONVERTLY_API_KEY"
```

Or subscribe a webhook to:

* `video.stream.started`
* `video.stream.ready`
* `video.stream.failed`

## 4. Play the stream

When ready, use `hlsManifestUrl` in any HLS-capable player, or `dashManifestUrl` with dash.js.

```html theme={"system"}
<video controls poster="https://convertly.sh/video/v1/pb_.../poster.jpg?expires=...&token=...">
  <source src="https://convertly.sh/video/v1/pb_.../master.m3u8?expires=...&token=..." type="application/vnd.apple.mpegurl" />
</video>
```

Safari plays HLS natively. For Chrome/Firefox desktop, use a player such as hls.js.

```js theme={"system"}
import Hls from "hls.js";

const video = document.querySelector("video");
const manifestUrl = "https://convertly.sh/video/v1/pb_.../master.m3u8?expires=...&token=...";

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

## Recommended defaults

Start with:

* `profile: "standard"` for most product video.
* `profile: "hd"` when 1080p matters.
* `access: "signed"` for user/private content.
* `segmentDuration: 6` unless you have a playback reason to change it.

Custom ladders are best for products with strict bandwidth budgets or known playback environments.

## Usage pricing

Video streaming uses two meters:

| Meter            | How it is counted                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| Source minutes   | Input duration counted once, regardless of the number of generated renditions.                 |
| Delivery minutes | Viewer watch time. A 10-minute video watched fully by 100 viewers uses 1,000 delivery minutes. |

Free and Starter stop at their included allowance. Pro and Business can continue with optional overage when enabled in billing settings. Enterprise plans use contracted limits. See [Limits](/limits) and [Video streaming](/docs/video-streaming).
