FFmpeg Recipe

Package video for HLS streaming with FFmpeg API

Convert MP4 files to HLS format for adaptive bitrate streaming. Generate playlists, segments, and multiple quality renditions without managing FFmpeg infrastructure.

What HLS is and why product teams use it

HLS (HTTP Live Streaming) is Apple's streaming protocol that breaks video into small chunks and serves them over HTTP. Instead of downloading a single large file, the player downloads 2-10 second segments one at a time. This enables:

  • Adaptive bitrate streaming — The player automatically switches between quality levels based on network conditions
  • Fast startup — Playback begins after downloading just the first segment, not the entire file
  • CDN-friendly delivery — Small static files are easy to cache and distribute globally
  • Live streaming support — New segments can be added to the playlist as they're created

Product teams use HLS for video platforms, course content, live events, and any scenario where viewers have varying network speeds. It's supported natively by Safari and iOS, and works everywhere with JavaScript players like Video.js or HLS.js.

The raw FFmpeg command for HLS packaging

Here's the basic FFmpeg command to convert an MP4 file into HLS format with multiple quality renditions:

ffmpeg -i input.mp4 \
  -filter_complex \
  "[0:v]split=3[v1][v2][v3]; \
   [v1]scale=w=1920:h=1080[v1out]; \
   [v2]scale=w=1280:h=720[v2out]; \
   [v3]scale=w=854:h=480[v3out]" \
  -map "[v1out]" -c:v:0 libx264 -b:v:0 5M \
  -map "[v2out]" -c:v:1 libx264 -b:v:1 3M \
  -map "[v3out]" -c:v:2 libx264 -b:v:2 1.5M \
  -map a:0 -c:a:0 aac -b:a:0 128k \
  -map a:0 -c:a:1 aac -b:a:1 128k \
  -map a:0 -c:a:2 aac -b:a:2 96k \
  -f hls -hls_time 6 -hls_playlist_type vod \
  -hls_segment_filename "v%v/segment_%03d.ts" \
  -master_pl_name master.m3u8 \
  -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
  output_%v.m3u8

This command creates three quality levels (1080p, 720p, 480p), segments each into 6-second chunks, and generates both a master playlist and individual variant playlists.

Why this is complex: You need to manage filter graphs, map streams correctly, configure segment length, handle variant playlists, and ensure output files are organized correctly. Getting this right takes trial and error.

The API version (curl + JSON request)

With FFmpeg Micro, you send a single JSON request describing what you want, and the API handles the FFmpeg complexity, segment generation, and file organization:

curl -X POST https://api.ffmpeg-micro.com/v1/jobs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input_url": "https://yourbucket.s3.amazonaws.com/input.mp4",
    "output_format": "hls",
    "hls_options": {
      "segment_duration": 6,
      "playlist_type": "vod",
      "renditions": [
        { "width": 1920, "height": 1080, "video_bitrate": "5M" },
        { "width": 1280, "height": 720, "video_bitrate": "3M" },
        { "width": 854, "height": 480, "video_bitrate": "1.5M" }
      ]
    },
    "webhook_url": "https://yourapp.com/webhooks/ffmpeg"
  }'

The API returns a job ID immediately. Processing happens asynchronously. When the job completes, you'll receive a webhook notification with the master playlist and segment URLs.

Example request and response with playlist + segment URLs

Here's what the initial API response looks like when you submit the job:

{
  "job_id": "job_abc123",
  "status": "processing",
  "created_at": "2024-01-15T10:30:00Z",
  "estimated_completion": "2024-01-15T10:32:30Z"
}

When the job completes, the webhook payload includes the master playlist and all segment URLs:

{
  "job_id": "job_abc123",
  "status": "completed",
  "completed_at": "2024-01-15T10:32:15Z",
  "output": {
    "format": "hls",
    "master_playlist_url": "https://cdn.ffmpeg-micro.com/output/job_abc123/master.m3u8",
    "variant_playlists": [
      {
        "resolution": "1920x1080",
        "bitrate": "5M",
        "playlist_url": "https://cdn.ffmpeg-micro.com/output/job_abc123/1080p/playlist.m3u8"
      },
      {
        "resolution": "1280x720",
        "bitrate": "3M",
        "playlist_url": "https://cdn.ffmpeg-micro.com/output/job_abc123/720p/playlist.m3u8"
      },
      {
        "resolution": "854x480",
        "bitrate": "1.5M",
        "playlist_url": "https://cdn.ffmpeg-micro.com/output/job_abc123/480p/playlist.m3u8"
      }
    ],
    "total_segments": 42,
    "duration_seconds": 252
  }
}

Segmenting, playlists, renditions

Understanding the HLS output structure helps you integrate streaming into your app:

Master Playlist (master.m3u8)

The entry point. Lists all available quality levels. The player uses this to discover variants and switch between them based on bandwidth.

Variant Playlists (1080p/playlist.m3u8, etc.)

One per quality level. Contains the list of segment files for that rendition, along with segment duration and sequence metadata.

Segments (segment_001.ts, segment_002.ts, etc.)

Small video chunks (typically 2-10 seconds each). The player downloads these sequentially during playback. Each rendition has its own set of segments.

Common segment duration choices:

  • 2-4 seconds — Faster quality switching, more HTTP requests
  • 6-10 seconds — Balanced approach (most common)
  • 10+ seconds — Fewer requests, slower adaptation to network changes

Webhook flow for async HLS jobs

HLS packaging can take several minutes for long videos with multiple renditions. The webhook pattern lets you submit the job and handle the result asynchronously:

  1. Submit job — POST to /v1/jobs with webhook_url in the request
  2. Save job_id — Store the returned job_id in your database linked to the user's upload
  3. Show processing state — Display "Processing video..." in your UI while the job runs
  4. Receive webhook — When complete, FFmpeg Micro POSTs the full result to your webhook URL
  5. Update your records — Save the master_playlist_url and mark the video as ready
  6. Stream to users — Point your video player to the master playlist URL

For webhook security, verify the request signature using your API key. See the quickstart guide for webhook verification examples.

Storage and delivery considerations (signed URLs, CDN)

After HLS packaging, you need to serve the master playlist and segments to end users. Common approaches:

Option 1: Public CDN URLs

The simplest approach. Segments and playlists are publicly accessible via CDN. Works for free or ad-supported content. No authentication required.

Option 2: Signed URLs

Generate time-limited signed URLs for the master playlist and segments. Prevents unauthorized sharing. Requires URL signing logic in your backend.

Option 3: Token-based authentication

Serve playlists and segments through your own proxy endpoint that checks user session tokens. Full control over access, but adds latency and infrastructure.

FFmpeg Micro provides signed URL generation helpers in the API response when you enable the signed_urls option. URLs expire after a configurable duration (default: 24 hours).

Pro tip: Use a CDN with origin shield to cache segments close to users. HLS segments are immutable, so they're perfect for aggressive caching (max-age 1 year).

Related recipes

Start packaging HLS with FFmpeg API

Convert your first video to HLS in minutes. No FFmpeg servers to manage, no infrastructure to scale.