---
# === IDENTITY ===
id: software/system-design/video-streaming-platform/2026
canonical_question: "How do I design a video streaming platform (Netflix/YouTube clone)?"
aliases:
  - "design Netflix system architecture"
  - "build a video streaming service like YouTube"
  - "video on demand platform system design"
  - "Netflix clone architecture guide"
  - "YouTube clone system design"
  - "video streaming platform architecture"
  - "design a scalable video delivery system"
  - "OTT platform system design"
entity_type: software_reference
domain: software > system-design > video_streaming_platform
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.91
freshness: quarterly
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "AV1 codec gaining mainstream encoder support (2024)"
  next_review: 2026-08-22
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Never serve video directly from origin servers — always use a CDN or edge cache; origin-direct serving collapses under load and doubles egress costs"
  - "Always transcode uploads into multiple bitrate renditions (encoding ladder) — serving a single resolution wastes bandwidth on mobile and buffers on slow connections"
  - "DRM (Widevine + FairPlay + PlayReady) is mandatory for licensed/premium content — serving unencrypted streams violates content licensing agreements"
  - "Segment duration must be 2-10 seconds for adaptive streaming — segments <2s create manifest bloat, >10s cause slow quality switching"
  - "Never store video metadata and video blobs in the same database — metadata needs low-latency queries (SQL/NoSQL) while blobs need object storage (S3/GCS)"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User wants to build a live streaming / real-time broadcast platform (Twitch-style)"
    use_instead: "Live streaming requires WebRTC/RTMP ingest, ultra-low-latency protocols (LL-HLS, WebRTC), and different CDN configuration"
  - condition: "User needs a simple video player embedded in an existing app without backend infrastructure"
    use_instead: "Use a managed service (Mux, Cloudflare Stream, AWS MediaConvert + CloudFront) instead of building from scratch"
  - condition: "User wants video conferencing (Zoom-style) not video-on-demand"
    use_instead: "Video conferencing requires SFU/MCU architecture with WebRTC, not VOD streaming"

# === AGENT HINTS ===
inputs_needed:
  - key: scale
    question: "What is your expected concurrent viewer count?"
    type: choice
    options: ["Small (<1K)", "Medium (1K-100K)", "Large (100K-1M)", "Massive (>1M)"]
  - key: content_type
    question: "What type of content will be streamed?"
    type: choice
    options: ["User-generated (YouTube-style)", "Licensed/premium (Netflix-style)", "Both"]
  - key: budget
    question: "Are you building from scratch or using managed cloud services?"
    type: choice
    options: ["Build from scratch (max control)", "Managed services (faster launch)", "Hybrid approach"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/system-design/video-streaming-platform/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/monolith-to-microservices/2026"
      label: "Monolith to Microservices Migration"
    - id: "software/system-design/cdn-architecture/2026"
      label: "CDN Architecture and Edge Caching"
  solves:
    - id: "software/system-design/media-encoding-pipeline/2026"
      label: "Media Encoding Pipeline Design"
  alternative_to:
    - id: "software/system-design/live-streaming-platform/2026"
      label: "Live Streaming Platform Design"
  often_confused_with:
    - id: "software/system-design/video-conferencing/2026"
      label: "Video Conferencing System Design (WebRTC)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Netflix Open Connect Overview"
    author: Netflix
    url: https://openconnect.netflix.com/Open-Connect-Overview.pdf
    type: official_docs
    published: 2024-01-15
    reliability: authoritative
  - id: src2
    title: "System Design of YouTube - A Complete Architecture"
    author: GeeksforGeeks
    url: https://www.geeksforgeeks.org/system-design/system-design-of-youtube-a-complete-architecture/
    type: community_resource
    published: 2025-03-10
    reliability: moderate_high
  - id: src3
    title: "Designing a Scalable Video Streaming Platform Like YouTube and Netflix"
    author: Design Gurus
    url: https://www.designgurus.io/blog/design-video-streaming-platform
    type: community_resource
    published: 2025-06-20
    reliability: moderate_high
  - id: src4
    title: "Netflix Live Origin"
    author: Netflix Technology Blog
    url: https://netflixtechblog.com/netflix-live-origin-41f1b0ad5371
    type: technical_blog
    published: 2025-12-15
    reliability: high
  - id: src5
    title: "Video DRM Protection Guide 2026: Widevine, FairPlay and More"
    author: Kinescope
    url: https://www.kinescope.com/blog/video-drm-protection-guide-2026
    type: industry_report
    published: 2026-01-08
    reliability: moderate_high
  - id: src6
    title: "YouTube Tech Stack: How 2.5B Users Stream Video at Scale"
    author: VdoCipher
    url: https://www.vdocipher.com/blog/youtube-tech-stack-architecture/
    type: technical_blog
    published: 2025-09-12
    reliability: moderate_high
  - id: src7
    title: "Adaptive Bitrate Streaming: How It Works for Developers"
    author: Mux
    url: https://www.mux.com/articles/adaptive-bitrate-streaming-how-it-works-and-how-to-get-it-right
    type: technical_blog
    published: 2025-04-18
    reliability: high
---

# Video Streaming Platform System Design

## TL;DR

- **Bottom line**: A video streaming platform requires five core subsystems: upload/ingest, transcoding pipeline, object storage, CDN edge delivery, and adaptive bitrate playback (HLS/DASH) — the CDN and transcoding pipeline are where most complexity and cost reside.
- **Key tool/command**: `ffmpeg -i input.mp4 -codec:v libx264 -preset medium -b:v 3000k -hls_time 6 -hls_playlist_type vod output.m3u8`
- **Watch out for**: Skipping the encoding ladder and serving a single bitrate — this causes buffering on slow connections and wastes bandwidth on mobile.
- **Works with**: Any cloud provider (AWS MediaConvert + CloudFront, GCP Transcoder + Cloud CDN, Azure Media Services), or self-hosted with FFmpeg + nginx + your own CDN.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Never serve video from origin servers directly — always use CDN/edge caching; a single 4K stream at 25 Mbps serving 10K concurrent users = 250 Gbps of origin bandwidth [src1]
- Always transcode into multiple renditions (encoding ladder) — Netflix uses 5-12 renditions per title, YouTube generates 6+ formats from 144p to 4K [src6]
- DRM encryption (Widevine + FairPlay + PlayReady) is mandatory for licensed content — unencrypted streams violate licensing agreements and enable piracy [src5]
- Segment duration for HLS/DASH must be 2-10 seconds — Apple recommends 6s for HLS; shorter creates manifest bloat, longer causes slow adaptation [src7]
- Video metadata (titles, thumbnails, user data) and video blobs must live in separate storage systems — SQL/NoSQL for metadata, object storage (S3/GCS) for blobs

## Quick Reference

| Component | Role | Technology Options | Scaling Strategy |
|---|---|---|---|
| **Upload Service** | Chunked upload, resumable uploads, deduplication | tus protocol, S3 multipart upload, GCS resumable | Horizontal — stateless workers behind load balancer |
| **Message Queue** | Decouples upload from transcoding, ensures at-least-once processing | Apache Kafka, AWS SQS, Google Pub/Sub, RabbitMQ | Partition by video ID; consumer groups per pipeline stage |
| **Transcoding Pipeline** | Encodes raw video into multiple bitrate/resolution renditions | FFmpeg, AWS MediaConvert, GCP Transcoder API, Handbrake | Horizontal — spin up workers per job; GPU instances for H.265/AV1 |
| **Object Storage** | Stores raw uploads and transcoded segments/manifests | AWS S3, Google Cloud Storage, Azure Blob, MinIO | Virtually unlimited; lifecycle policies for raw cleanup |
| **CDN / Edge Cache** | Serves video segments from edge PoPs closest to viewers | Netflix Open Connect, CloudFront, Cloudflare, Akamai, Fastly | 95%+ cache hit ratio; fill from origin on miss [src1] |
| **Metadata Service** | Video catalog, search, recommendations, user profiles | PostgreSQL + Elasticsearch, DynamoDB, Cassandra | Read replicas + caching (Redis); shard by user/region |
| **Thumbnail Service** | Generates preview thumbnails and trick-play images | FFmpeg scene detection, sprite sheets | Batch processing during transcode; cached on CDN |
| **DRM License Server** | Issues decryption keys for encrypted content | Widevine, FairPlay, PlayReady, BuyDRM, PallyCon | Multi-DRM per device; license server scales horizontally [src5] |
| **Adaptive Streaming** | Client-side bitrate adaptation based on bandwidth | HLS (Apple), DASH (MPEG), CMAF (unified) | Client-driven — player switches renditions automatically [src7] |
| **API Gateway** | Routes requests, rate limiting, auth, request aggregation | Kong, AWS API Gateway, nginx, Envoy | Horizontal behind L7 load balancer; edge-deployed |
| **Recommendation Engine** | Personalized content suggestions | Collaborative filtering, deep learning, Netflix-style two-phase (candidate gen + ranking) | Offline batch training + real-time feature store (Redis) |
| **Analytics Pipeline** | Playback quality metrics, engagement, A/B testing | Kafka + Spark/Flink, ClickHouse, BigQuery | Stream processing for real-time; batch for daily aggregates |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (38 lines)

```
START
|-- Expected concurrent viewers?
|   |-- <1K users
|   |   --> Simple stack: S3 + CloudFront + FFmpeg on EC2/Lambda
|   |   --> Single-server transcoding, managed CDN, no custom edge
# ... (see full script)
```

## Step-by-Step Guide

### 1. Design the upload and ingest pipeline

Handle large file uploads (often multi-GB) with chunked, resumable uploads. Upload directly to object storage to avoid proxying through your application servers. [src2]

```python
# Upload service endpoint — accepts chunked uploads via tus protocol
# Stores raw video in S3, publishes transcode job to Kafka
import boto3
from kafka import KafkaProducer
import json
import uuid

s3 = boto3.client("s3", region_name="us-east-1")
producer = KafkaProducer(
    bootstrap_servers=["kafka:9092"],
    value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)

BUCKET = "raw-video-uploads"

def handle_upload_complete(file_path: str, user_id: str, title: str) -> str:
    """Called after chunked upload completes. Returns video_id."""
    video_id = str(uuid.uuid4())
    s3_key = f"raw/{video_id}/{file_path.split('/')[-1]}"

    # Upload raw file to S3
    s3.upload_file(file_path, BUCKET, s3_key)

    # Publish transcode job to Kafka
    job = {
        "video_id": video_id,
        "s3_key": s3_key,
        "user_id": user_id,
        "title": title,
        "status": "uploaded",
        "renditions": ["360p", "480p", "720p", "1080p"],
    }
    producer.send("transcode-jobs", value=job, key=video_id.encode())
    producer.flush()

    return video_id
```

**Verify**: Check S3 bucket for raw file: `aws s3 ls s3://raw-video-uploads/raw/{video_id}/` -> file listed. Check Kafka topic: `kafka-console-consumer --topic transcode-jobs --from-beginning` -> job JSON visible.

### 2. Build the transcoding pipeline

Consume jobs from the message queue and transcode each video into multiple renditions using FFmpeg. Each rendition produces HLS segments (.ts files) and a playlist (.m3u8). [src3] [src7]

```python
# Transcoding worker — consumes from Kafka, transcodes with FFmpeg, uploads segments to S3
import subprocess
import os
import json
from kafka import KafkaConsumer
import boto3

s3 = boto3.client("s3", region_name="us-east-1")
OUTPUT_BUCKET = "transcoded-video-segments"

# Encoding ladder — resolution: (width, height, video_bitrate, audio_bitrate)
ENCODING_LADDER = {
    "360p":  (640, 360, "800k", "96k"),
    "480p":  (854, 480, "1400k", "128k"),
    "720p":  (1280, 720, "2800k", "128k"),
    "1080p": (1920, 1080, "5000k", "192k"),
}

def transcode_rendition(input_path: str, video_id: str, rendition: str) -> str:
    """Transcode a single rendition, return local output directory."""
    w, h, vbr, abr = ENCODING_LADDER[rendition]
    out_dir = f"/tmp/transcode/{video_id}/{rendition}"
    os.makedirs(out_dir, exist_ok=True)

    cmd = [
        "ffmpeg", "-i", input_path,
        "-vf", f"scale={w}:{h}",
        "-c:v", "libx264", "-preset", "medium", "-b:v", vbr,
        "-c:a", "aac", "-b:a", abr,
        "-hls_time", "6",                 # 6-second segments (Apple recommended)
        "-hls_playlist_type", "vod",
        "-hls_segment_filename", f"{out_dir}/seg_%04d.ts",
        f"{out_dir}/playlist.m3u8",
        "-y",
    ]
    subprocess.run(cmd, check=True, capture_output=True)
    return out_dir

def upload_rendition(video_id: str, rendition: str, local_dir: str):
    """Upload all segments and playlist to S3."""
    for fname in os.listdir(local_dir):
        local_path = os.path.join(local_dir, fname)
        s3_key = f"{video_id}/{rendition}/{fname}"
        s3.upload_file(local_path, OUTPUT_BUCKET, s3_key)
```

**Verify**: After transcode, check output: `ls /tmp/transcode/{video_id}/720p/` -> `playlist.m3u8, seg_0000.ts, seg_0001.ts, ...`. Validate HLS: `ffprobe /tmp/transcode/{video_id}/720p/playlist.m3u8` -> shows stream info.

### 3. Generate the master HLS manifest

Create a master playlist that references all rendition playlists, enabling adaptive bitrate switching. [src7]

```python
# Generate master.m3u8 that references all rendition playlists
def generate_master_manifest(video_id: str, renditions: list[str]) -> str:
    """Generate and upload master HLS manifest."""
    bandwidth_map = {
        "360p": 800000, "480p": 1400000,
        "720p": 2800000, "1080p": 5000000,
    }
    resolution_map = {
        "360p": "640x360", "480p": "854x480",
        "720p": "1280x720", "1080p": "1920x1080",
    }

    lines = ["#EXTM3U", "#EXT-X-VERSION:3"]
    for r in sorted(renditions, key=lambda x: bandwidth_map.get(x, 0)):
        bw = bandwidth_map[r]
        res = resolution_map[r]
        lines.append(
            f"#EXT-X-STREAM-INF:BANDWIDTH={bw},RESOLUTION={res}"
        )
        lines.append(f"{r}/playlist.m3u8")

    manifest = "\n".join(lines) + "\n"

    # Upload master manifest to S3
    s3.put_object(
        Bucket=OUTPUT_BUCKET,
        Key=f"{video_id}/master.m3u8",
        Body=manifest.encode("utf-8"),
        ContentType="application/vnd.apple.mpegurl",
    )
    return f"https://cdn.example.com/{video_id}/master.m3u8"
```

**Verify**: Fetch master manifest: `curl https://cdn.example.com/{video_id}/master.m3u8` -> lists all rendition playlists with BANDWIDTH and RESOLUTION tags.

### 4. Configure CDN and edge caching

Place your CDN in front of the object storage bucket. Configure cache headers so segments are cached at edge PoPs (they are immutable once created). [src1]

```nginx
# nginx CDN edge configuration for HLS segment caching
# Place in front of origin (S3/GCS) for edge PoPs

upstream origin_s3 {
    server transcoded-video-segments.s3.amazonaws.com:443;
}

server {
    listen 443 ssl http2;
    server_name cdn.example.com;

    # HLS segments are immutable — cache aggressively
    location ~* \.ts$ {
        proxy_pass https://origin_s3;
        proxy_cache video_cache;
        proxy_cache_valid 200 365d;            # Cache segments for 1 year
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header X-Cache-Status $upstream_cache_status;
    }

    # Manifests may update (live) — shorter cache for VOD safety
    location ~* \.m3u8$ {
        proxy_pass https://origin_s3;
        proxy_cache video_cache;
        proxy_cache_valid 200 60s;             # 60s cache for manifests
        add_header Cache-Control "public, max-age=60";
    }

    # Enable range requests for seek/scrub
    proxy_set_header Range $http_range;
    proxy_set_header If-Range $http_if_range;
}
```

**Verify**: Request a segment and check cache headers: `curl -I https://cdn.example.com/{video_id}/720p/seg_0001.ts` -> `X-Cache-Status: HIT` on second request, `Cache-Control: public, max-age=31536000, immutable`.

### 5. Implement the playback API and metadata service

Build the API that serves video metadata and playback URLs. The player requests the master manifest URL and handles adaptive switching. [src2] [src6]

```javascript
// Node.js playback API — returns video metadata + signed CDN URL
// Uses signed URLs so only authenticated users can access content
const express = require("express");
const { getSignedUrl } = require("@aws-sdk/cloudfront-signer");
const { Pool } = require("pg");

const app = express();
const db = new Pool({ connectionString: process.env.DATABASE_URL });

app.get("/api/v1/videos/:videoId/playback", async (req, res) => {
  const { videoId } = req.params;

  // Fetch metadata from PostgreSQL
  const result = await db.query(
    "SELECT id, title, duration_sec, renditions FROM videos WHERE id = $1 AND status = 'ready'",
    [videoId]
  );
  if (result.rows.length === 0) {
    return res.status(404).json({ error: "Video not found or not ready" });
  }
  const video = result.rows[0];

  // Generate signed CDN URL (expires in 4 hours)
  const manifestUrl = getSignedUrl({
    url: `https://cdn.example.com/${videoId}/master.m3u8`,
    keyPairId: process.env.CF_KEY_PAIR_ID,
    privateKey: process.env.CF_PRIVATE_KEY,
    dateLessThan: new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString(),
  });

  res.json({
    id: video.id,
    title: video.title,
    duration: video.duration_sec,
    manifest_url: manifestUrl,
    renditions: video.renditions,
  });
});

app.listen(3000);
```

**Verify**: `curl -H "Authorization: Bearer {token}" http://localhost:3000/api/v1/videos/{videoId}/playback` -> JSON with `manifest_url` containing signed CloudFront URL.

### 6. Add DRM encryption for premium content

For licensed content, encrypt segments during transcoding and set up a multi-DRM license server. [src5]

```bash
# Encrypt HLS segments with AES-128 for basic protection
# For production DRM, use Widevine/FairPlay via a service like BuyDRM or PallyCon

# Generate encryption key
openssl rand 16 > /tmp/enc.key
KEY_HEX=$(xxd -p /tmp/enc.key | tr -d '\n')

# Create key info file for FFmpeg
echo "https://keys.example.com/${VIDEO_ID}/enc.key" > /tmp/enc_keyinfo.txt
echo "/tmp/enc.key" >> /tmp/enc_keyinfo.txt

# Transcode with HLS encryption
ffmpeg -i input.mp4 \
  -c:v libx264 -preset medium -b:v 3000k \
  -c:a aac -b:a 128k \
  -hls_time 6 \
  -hls_playlist_type vod \
  -hls_key_info_file /tmp/enc_keyinfo.txt \
  -hls_segment_filename "seg_%04d.ts" \
  output_encrypted.m3u8
```

**Verify**: Inspect manifest: `grep EXT-X-KEY output_encrypted.m3u8` -> shows `METHOD=AES-128,URI="https://keys.example.com/..."`. Attempting to play segments without the key should fail.

## Code Examples

### Python: Video Upload Pipeline with S3 and Kafka

> Full script: [python-video-upload-pipeline-with-s3-and-kafka.py](scripts/python-video-upload-pipeline-with-s3-and-kafka.py) (43 lines)

```python
# Input:  Raw video file path, user metadata
# Output: video_id, transcode job published to Kafka
import boto3
import json
import uuid
# ... (see full script)
```

### JavaScript: HLS Master Manifest Generator

```javascript
// Input:  video ID, list of completed renditions with metadata
// Output: M3U8 master manifest string

function generateMasterManifest(videoId, renditions) {
  // renditions: [{ name: "720p", bandwidth: 2800000,
  //   resolution: "1280x720", codecs: "avc1.64001f,mp4a.40.2" }]
  const lines = ["#EXTM3U", "#EXT-X-VERSION:3", ""];

  const sorted = [...renditions].sort((a, b) => a.bandwidth - b.bandwidth);
  for (const r of sorted) {
    lines.push(
      `#EXT-X-STREAM-INF:BANDWIDTH=${r.bandwidth},` +
      `RESOLUTION=${r.resolution},CODECS="${r.codecs}"`
    );
    lines.push(`${r.name}/playlist.m3u8`);
    lines.push("");
  }
  return lines.join("\n");
}

// Example usage
const manifest = generateMasterManifest("abc123", [
  { name: "360p", bandwidth: 800000, resolution: "640x360", codecs: "avc1.42e00a,mp4a.40.2" },
  { name: "720p", bandwidth: 2800000, resolution: "1280x720", codecs: "avc1.64001f,mp4a.40.2" },
  { name: "1080p", bandwidth: 5000000, resolution: "1920x1080", codecs: "avc1.640028,mp4a.40.2" },
]);
console.log(manifest);
```

### Go: Transcoding Worker with Job Queue

> Full script: [go-transcoding-worker-with-job-queue.go](scripts/go-transcoding-worker-with-job-queue.go) (65 lines)

```go
// Input:  Transcode job from message queue (video_id, s3_key, renditions)
// Output: Transcoded HLS segments uploaded to S3
package main
import (
    "context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Synchronous transcoding during upload

```python
# BAD — blocks the upload response until transcoding completes (minutes to hours)
@app.post("/upload")
def upload_video(file: UploadFile):
    save_to_disk(file)
    transcode_all_renditions(file.filename)    # Blocks for 5-30 minutes!
    return {"status": "ready"}                 # User waits forever, request times out
```

### Correct: Async transcoding via message queue

```python
# GOOD — upload returns immediately, transcoding happens asynchronously
@app.post("/upload")
def upload_video(file: UploadFile):
    video_id = save_to_s3(file)
    publish_to_queue("transcode-jobs", {"video_id": video_id})
    return {"status": "processing", "video_id": video_id}   # Returns in <1s
    # Client polls GET /videos/{video_id}/status for progress
```

### Wrong: Single bitrate streaming

```python
# BAD — serves a single 1080p file regardless of user bandwidth
def get_video_url(video_id):
    return f"https://cdn.example.com/{video_id}/video_1080p.mp4"
    # Mobile users on 3G get constant buffering
    # Desktop users on gigabit waste no bandwidth, but mobile suffers
```

### Correct: Adaptive bitrate with HLS/DASH encoding ladder

```python
# GOOD — serves master manifest; player picks the right rendition automatically
def get_video_url(video_id):
    return f"https://cdn.example.com/{video_id}/master.m3u8"
    # Player starts at lowest quality, ramps up as bandwidth permits
    # Switches down on congestion — no buffering
```

### Wrong: Storing video blobs in a database

```python
# BAD — stores video binary in PostgreSQL BYTEA column
def store_video(video_id, video_bytes):
    db.execute(
        "INSERT INTO videos (id, data) VALUES (%s, %s)",
        (video_id, video_bytes)
    )
    # Database bloats to TB, backups take hours, queries slow to a crawl
```

### Correct: Object storage for blobs, database for metadata only

```python
# GOOD — S3 for video files, PostgreSQL for metadata only
def store_video(video_id, filepath, title, user_id):
    s3_key = f"raw/{video_id}/{os.path.basename(filepath)}"
    s3.upload_file(filepath, "video-bucket", s3_key)
    db.execute(
        "INSERT INTO videos (id, title, user_id, s3_key, status) VALUES (%s, %s, %s, %s, 'uploaded')",
        (video_id, title, user_id, s3_key)
    )
```

### Wrong: No CDN — serving directly from origin

```python
# BAD — all viewers worldwide hit the single origin region
def get_stream_url(video_id):
    return f"https://us-east-1.s3.amazonaws.com/videos/{video_id}/master.m3u8"
    # Viewers in Tokyo: 200ms+ latency per segment request
    # Origin bandwidth: 10K viewers x 5 Mbps = 50 Gbps (!!)
    # S3 egress cost: $0.09/GB = $22,500/hour at that throughput
```

### Correct: CDN edge delivery with origin shielding

```python
# GOOD — CDN serves from 200+ edge PoPs, origin only handles cache misses
def get_stream_url(video_id):
    return f"https://cdn.example.com/{video_id}/master.m3u8"
    # Viewer in Tokyo: <20ms from local edge PoP
    # 95%+ cache hit ratio — origin handles <5% of requests [src1]
    # CDN egress: $0.01-0.02/GB vs $0.09/GB from S3 direct
```

## Common Pitfalls

- **Not implementing resumable uploads**: Users uploading multi-GB files over unreliable connections lose all progress on network interruption. Fix: Use the `tus` protocol or S3 multipart upload with client-side retry per chunk. [src2]
- **Fixed encoding ladder ignoring content complexity**: An animated show looks sharp at 1.5 Mbps, but an action movie needs 4+ Mbps at the same resolution. Fix: Implement per-title encoding optimization — analyze each video's complexity and customize the bitrate ladder (Netflix reported 20-30% bandwidth savings). [src3]
- **Ignoring cold start latency in playback**: First segment takes too long to load because the player starts at high quality. Fix: Configure the player to start at a low rendition and ramp up, or use a separate "fast start" segment at the beginning of each video. [src7]
- **Missing video processing status tracking**: Users see "processing" forever with no progress indication. Fix: Emit progress events from transcoding workers (e.g., percent complete per rendition) and expose via WebSocket or polling endpoint. [src6]
- **No origin shielding on the CDN**: Every edge PoP cache miss hits your origin directly, creating thundering herd on popular new releases. Fix: Add a shield/mid-tier cache layer between edge PoPs and origin — reduces origin load by 10-50x. [src1]
- **Skipping thumbnail and preview generation**: Users cannot preview content before watching, reducing engagement. Fix: Generate thumbnails at key intervals during transcoding (FFmpeg scene detection) and create sprite sheets for trick-play scrubbing. [src6]
- **Serving manifests with long cache TTLs**: If you need to update or remove content (DMCA, correction), long-cached manifests keep serving stale content. Fix: Set short TTLs (30-60s) on `.m3u8` manifests while caching `.ts` segments aggressively (immutable, 1-year TTL). [src7]
- **No geographic routing or multi-region deployment**: All traffic routes to a single region, adding 100-300ms latency for distant users. Fix: Deploy metadata/API services in multiple regions with DNS-based routing (Route53 latency routing, Cloudflare load balancing). [src4]

## Diagnostic Commands

```bash
# Validate HLS manifest structure
ffprobe -v quiet -print_format json -show_streams master.m3u8

# Check video codec, bitrate, and resolution of a file
ffprobe -v error -show_entries stream=codec_name,width,height,bit_rate -of csv input.mp4

# Test adaptive streaming by simulating bandwidth throttling (Chrome DevTools)
# Network tab -> Throttling -> Custom: set download to 1500 kbps

# Measure CDN cache hit ratio
curl -sI https://cdn.example.com/video_id/720p/seg_0001.ts | grep -i x-cache

# Monitor transcoding worker queue depth (Kafka)
kafka-consumer-groups --bootstrap-server kafka:9092 --group transcode-workers --describe

# Check S3 bucket size and object count for cost estimation
aws s3 ls s3://transcoded-video-segments --recursive --summarize | tail -2

# Test HLS playback with ffplay
ffplay https://cdn.example.com/video_id/master.m3u8

# Validate DRM encryption on segments
ffprobe -v error -show_entries format_tags -of json encrypted_segment.ts
```

## Version History & Compatibility

| Technology | Status | Notes |
|---|---|---|
| H.264/AVC + AAC | Universal standard | Maximum device compatibility; baseline for all platforms |
| H.265/HEVC | Widely supported (2020+) | 30-40% smaller files than H.264; Apple ecosystem strong; licensing fees apply |
| AV1 | Growing adoption (2023+) | 50-60% smaller than H.264; royalty-free; encoding is 50-100x slower than H.264 |
| HLS (HTTP Live Streaming) | Industry standard | Apple-developed; works everywhere with wide player support |
| DASH (Dynamic Adaptive Streaming) | Industry standard | MPEG-developed; primary protocol for non-Apple (Android, web); not supported natively on iOS |
| CMAF (Common Media Application Format) | Emerging standard | Unifies HLS + DASH segments; reduces storage by 50% (single segment format) |
| Widevine L1/L3 | Current DRM standard | Google-backed; covers Chrome, Android, smart TVs, Chromecast |
| FairPlay Streaming | Current DRM standard | Apple-only; required for Safari and iOS DRM playback |
| PlayReady SL2000/3000 | Current DRM standard | Microsoft-backed; covers Edge, Xbox, Windows, some smart TVs |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building a video-on-demand (VOD) platform at any scale | Building real-time video conferencing (Zoom/Teams) | WebRTC-based SFU/MCU architecture |
| Content is pre-recorded and can be transcoded ahead of time | Ultra-low latency live streaming is required (<3s) | LL-HLS, WebRTC, or RTMP-based live streaming |
| You need adaptive bitrate playback across diverse devices and networks | Streaming short clips (<30s) where quality switching has no time to help | Progressive download (single MP4 file) is simpler |
| Licensed content requires DRM protection | Internal/private video sharing within a small team | Simple S3 presigned URLs or Cloudflare Stream |
| >10K concurrent viewers expected | Hosting a few dozen training videos for a small org | Managed platforms (Vimeo, Wistia, YouTube unlisted) |

## Important Caveats

- AV1 encoding is extremely CPU-intensive (50-100x slower than H.264) — use hardware encoders (NVIDIA NVENC, Intel QSV) or cloud encoding services for production; CPU-only AV1 encoding is only viable for batch processing with long deadlines
- CDN egress costs dominate the bill at scale — Netflix spends ~$0.01/GB via Open Connect peering vs $0.05-0.09/GB on commercial CDNs; at 1PB/day of video delivery, this difference is millions per month [src1]
- DRM adds significant implementation complexity and ongoing licensing costs — evaluate whether your content actually needs DRM before committing; user-generated content platforms (YouTube-style) typically do not need DRM [src5]
- Per-title encoding optimization (analyzing each video's complexity to customize the encoding ladder) delivers 20-30% bandwidth savings but requires a sophisticated analysis pipeline — start with a fixed ladder and optimize later
- Video transcoding is one of the most compute-intensive workloads in software — budget for GPU instances or managed services; a 1-hour 4K video can take 4-8 hours to encode on a single CPU instance across all renditions

## Related Units

- [Monolith to Microservices Migration](/software/migrations/monolith-to-microservices/2026)
- [CDN Architecture and Edge Caching](/software/system-design/cdn-architecture/2026)
- [Media Encoding Pipeline Design](/software/system-design/media-encoding-pipeline/2026)
- [Live Streaming Platform Design](/software/system-design/live-streaming-platform/2026)
- [Video Conferencing System Design (WebRTC)](/software/system-design/video-conferencing/2026)
