Guide · AWS MediaConvert · HLS Output
AWS MediaConvert HLS Output — Adaptive Bitrate Streaming, Renditions, and CloudFront Delivery
HLS (HTTP Live Streaming) is the dominant adaptive streaming format for web and mobile video delivery — MediaConvert produces HLS from any input format, handling the segmentation, manifest generation, and multi-rendition ladder in a single job. For MCP server video pipelines, HLS output is the correct choice when users play video in browsers or mobile apps and you want quality to adapt to their bandwidth — a viewer on a slow mobile connection gets 360p segments, a desktop viewer on fiber gets 1080p, and the player switches between them mid-playback. MediaConvert's HLS output group produces a master playlist (.m3u8), per-rendition variant playlists, and .ts segments — all written to S3. You then point CloudFront at the output S3 prefix for global low-latency delivery. Critical HLS decisions: segment duration (shorter = faster startup and adaptation, more S3 objects; 6s is the practical minimum for most CDNs), whether to use TS or fMP4 container for segments (fMP4 enables CMAF and Widevine DRM but requires modern players), and whether to use per-title encoding optimization (QVBR adapts per-scene, but per-title requires knowing the content type upfront).
TL;DR
Add an HLS_GROUP_SETTINGS output group to your job settings with SegmentLength: 6, MinSegmentLength: 0, and Destination pointing to your S3 output prefix. Add one output per rendition (1080p, 720p, 480p, 360p) with H_264 codec and matching resolution+bitrate. Audio can be shared across renditions using AudioOnlyContainer: AUTOMATIC with a single AAC output. MediaConvert generates the master manifest at <Destination>/<NameModifier>.m3u8. Serve via CloudFront with CacheBehavior matching *.m3u8 using short TTL (5-30s) and *.ts segments using long TTL (86400s+). See the S3 workflow guide for the Lambda trigger pattern that submits these jobs.
HLS output group structure
An HLS output group in MediaConvert settings contains the group-level configuration (segment duration, manifest format, destination) and an array of outputs, each representing one rendition. Each output has its own codec settings, resolution, and bitrate. MediaConvert writes one .ts file per segment per rendition and generates a variant playlist per rendition, plus a master playlist that references all variant playlists and advertises bandwidths so players can make adaptive decisions.
ENDPOINT="https://abc123def456.mediaconvert.us-east-1.amazonaws.com"
ROLE_ARN="arn:aws:iam::123456789012:role/MediaConvertJobRole"
aws mediaconvert create-job \
--endpoint-url "$ENDPOINT" \
--role "$ROLE_ARN" \
--settings '{
"Inputs": [{
"FileInput": "s3://my-video-input/uploads/raw.mp4",
"AudioSelectors": { "Audio Selector 1": { "DefaultSelection": "DEFAULT" } },
"VideoSelector": {}
}],
"OutputGroups": [{
"Name": "Apple HLS",
"OutputGroupSettings": {
"Type": "HLS_GROUP_SETTINGS",
"HlsGroupSettings": {
"SegmentLength": 6,
"MinSegmentLength": 0,
"Destination": "s3://my-video-output/hls/video-001/",
"DirectoryStructure": "SINGLE_DIRECTORY",
"ManifestDurationFormat": "INTEGER",
"OutputSelection": "MANIFESTS_AND_SEGMENTS",
"ProgramDateTime": "EXCLUDE",
"TimedMetadataId3Frame": "PRIV",
"TimedMetadataId3Period": 10,
"CaptionLanguageSetting": "OMIT"
}
},
"Outputs": [
{
"NameModifier": "_1080p",
"ContainerSettings": { "Container": "M3U8", "M3u8Settings": {} },
"VideoDescription": {
"Width": 1920, "Height": 1080,
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"RateControlMode": "QVBR",
"QvbrSettings": { "QvbrQualityLevel": 8 },
"MaxBitrate": 5000000,
"FramerateControl": "SPECIFIED",
"FramerateNumerator": 30000,
"FramerateDenominator": 1001,
"GopSize": 60,
"GopClosedCadence": 1,
"IdrInterval": 0,
"CodecProfile": "HIGH",
"CodecLevel": "AUTO"
}
}
},
"AudioDescriptions": [{
"AudioSourceName": "Audio Selector 1",
"CodecSettings": {
"Codec": "AAC",
"AacSettings": { "Bitrate": 128000, "CodingMode": "CODING_MODE_2_0", "SampleRate": 48000 }
}
}]
},
{
"NameModifier": "_720p",
"ContainerSettings": { "Container": "M3U8", "M3u8Settings": {} },
"VideoDescription": {
"Width": 1280, "Height": 720,
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"RateControlMode": "QVBR",
"QvbrSettings": { "QvbrQualityLevel": 7 },
"MaxBitrate": 3000000,
"FramerateControl": "SPECIFIED",
"FramerateNumerator": 30000,
"FramerateDenominator": 1001,
"GopSize": 60,
"GopClosedCadence": 1
}
}
},
"AudioDescriptions": [{
"AudioSourceName": "Audio Selector 1",
"CodecSettings": {
"Codec": "AAC",
"AacSettings": { "Bitrate": 96000, "CodingMode": "CODING_MODE_2_0", "SampleRate": 48000 }
}
}]
},
{
"NameModifier": "_480p",
"ContainerSettings": { "Container": "M3U8", "M3u8Settings": {} },
"VideoDescription": {
"Width": 854, "Height": 480,
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"RateControlMode": "QVBR",
"QvbrSettings": { "QvbrQualityLevel": 7 },
"MaxBitrate": 1500000,
"FramerateControl": "SPECIFIED",
"FramerateNumerator": 30000,
"FramerateDenominator": 1001,
"GopSize": 60
}
}
},
"AudioDescriptions": [{
"AudioSourceName": "Audio Selector 1",
"CodecSettings": {
"Codec": "AAC",
"AacSettings": { "Bitrate": 64000, "CodingMode": "CODING_MODE_2_0", "SampleRate": 48000 }
}
}]
}
]
}]
}'
The output S3 structure MediaConvert produces with SINGLE_DIRECTORY: all segments and manifests land in s3://my-video-output/hls/video-001/ as flat files. The master manifest is video-001.m3u8 and variant manifests are video-001_1080p.m3u8, video-001_720p.m3u8, etc. Segments are video-001_1080p00001.ts, video-001_1080p00002.ts, and so on. Point your player at the master manifest URL.
GOP alignment between renditions
For adaptive bitrate switching to work correctly, all renditions must have identical segment boundaries. MediaConvert handles this by setting GopClosedCadence: 1 (closed GOP at every segment boundary) and keeping GopSize equal to or a divisor of SegmentLength × framerate. For 6-second segments at 30fps, GopSize should be 180 (6 × 30). Setting GopClosedCadence: 1 forces MediaConvert to insert an IDR frame at every segment boundary even if the GOP calculation doesn't land cleanly — this prevents players from needing cross-segment reference frames when switching renditions mid-playback.
# Correct GOP alignment for 6s segments at 29.97 fps (30000/1001):
# GopSize = SegmentLength * FramerateNumerator / FramerateDenominator
# = 6 * 30000 / 1001 ≈ 179.82 → round to 180
"H264Settings": {
"GopSize": 180, # in frames (not seconds)
"GopClosedCadence": 1, # IDR at every segment boundary
"IdrInterval": 0, # let MediaConvert handle IDR placement
"SegmentControl": "SEGMENTED_FILES", # separate .ts files per segment
"FramerateControl": "SPECIFIED",
"FramerateNumerator": 30000,
"FramerateDenominator": 1001
}
# For content with mixed frame rates (24fps film + 30fps bumpers),
# set FramerateConversionAlgorithm: "DUPLICATE_DROP" to normalize
# before segmentation, or set FramerateControl: "INITIALIZE_FROM_SOURCE"
# and let MediaConvert match the source frame rate
When GopSize is set in frames (not seconds), it is stable across frame rate variations in the input. Setting GopSizeUnits: SECONDS is convenient but can produce non-integer frame counts at the GOP boundary — prefer frames for precise control. The interplay between GOP, segment length, and IDR placement is the most common source of HLS playback glitches where players stutter or fail to switch renditions cleanly.
Adding thumbnail output to the same job
MediaConvert can generate JPEG thumbnails from the video in the same job as the HLS output by adding a second output group of type FILE_GROUP_SETTINGS with a single JPEG output. The thumbnail output does not interfere with the HLS output — both output groups process concurrently from the same input decode. Set FrameCaptureSettings to extract a frame at a specific position (e.g., 10% into the video for a representative frame rather than the first black frame).
# Add thumbnail output group to a job that also has HLS
"OutputGroups": [
{
"Name": "Apple HLS",
"OutputGroupSettings": { "Type": "HLS_GROUP_SETTINGS", "HlsGroupSettings": { ... } },
"Outputs": [ ...renditions... ]
},
{
"Name": "Thumbnails",
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "s3://my-video-output/thumbnails/video-001/"
}
},
"Outputs": [{
"ContainerSettings": { "Container": "RAW" },
"VideoDescription": {
"Width": 1280,
"Height": 720,
"CodecSettings": {
"Codec": "FRAME_CAPTURE",
"FrameCaptureSettings": {
"FramerateNumerator": 1,
"FramerateDenominator": 30,
"MaxCaptures": 3,
"Quality": 80
}
}
},
"NameModifier": "_thumb"
}]
}
]
With FramerateNumerator: 1 and FramerateDenominator: 30, MediaConvert captures one frame every 30 seconds of content. MaxCaptures: 3 limits output to 3 thumbnails per video. Output files are named video-001_thumb.0000001.jpg, video-001_thumb.0000002.jpg, etc. For a single representative thumbnail, set MaxCaptures: 1 and FramerateDenominator to the approximate timestamp in seconds where you want the frame captured.
Serving HLS from CloudFront
Point CloudFront at the S3 output bucket using an Origin Access Control (OAC) policy so segments are served with the S3 bucket remaining private. Create two cache behaviors: one for *.m3u8 manifests with a short TTL (10–30 seconds for live, 300 seconds for VOD) and one for *.ts segments with a long TTL (86400–604800 seconds). Manifests must be re-fetched frequently for live streams; for VOD they are static but players expect reasonable freshness. Segments are immutable once written.
# Create CloudFront distribution for HLS delivery
aws cloudfront create-distribution \
--distribution-config '{
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "hls-s3-origin",
"DomainName": "my-video-output.s3.us-east-1.amazonaws.com",
"OriginAccessControlId": "ORIGIN_ACCESS_CONTROL_ID",
"S3OriginConfig": { "OriginAccessIdentity": "" }
}]
},
"DefaultCacheBehavior": {
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "MANAGED_CACHING_OPTIMIZED_ID",
"AllowedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] },
"Compress": true
},
"CacheBehaviors": {
"Quantity": 1,
"Items": [{
"PathPattern": "*.m3u8",
"ViewerProtocolPolicy": "redirect-to-https",
"DefaultTTL": 30,
"MaxTTL": 300,
"MinTTL": 0,
"CachePolicyId": "MANAGED_CACHING_DISABLED_ID"
}]
},
"Enabled": true,
"Comment": "HLS video delivery"
}'
For VOD delivery, set Cache-Control: max-age=86400 on segment objects in S3. You can do this by adding a metadata instruction to the MediaConvert job's HlsGroupSettings or by running an S3 batch operation on the output prefix post-transcode. For encrypted HLS (AES-128 or SAMPLE-AES), add Encryption to HlsGroupSettings with a SpekeKeyProvider pointing to your DRM key server — MediaConvert does not manage encryption keys directly but integrates with SPEKE-compliant providers including AWS Elemental MediaPackage.
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| Player cannot switch renditions (stutter on quality change) | Video freezes or reloads when bandwidth changes cause rendition switch | Renditions have misaligned segment boundaries — ensure GopClosedCadence: 1 and identical GopSize (in frames) across all rendition outputs |
| Master manifest missing renditions | Player starts at lowest quality and never upgrades | Each output must have a different NameModifier — duplicate modifiers cause MediaConvert to overwrite the variant playlist; also verify OutputSelection: MANIFESTS_AND_SEGMENTS (not SEGMENTS_ONLY) |
| Segments not in S3 after job completes | Master manifest exists but .ts files return 403 or 404 | MediaConvert writes to the exact destination path prefix — verify the S3 path in HlsGroupSettings.Destination is an S3 URI (s3://bucket/prefix/ not an HTTPS URL) and the IAM role has write access to that prefix |
| Buffering on mobile at expected bandwidth | Player buffers despite available bandwidth matching highest rendition bitrate | Segment duration too long for mobile networks — reduce SegmentLength from 10 to 6 seconds; also verify MaxBitrate in H264Settings is at most 10% above target average bitrate so peak segments don't cause buffer stalls |
| Audio sync drift over long videos | Audio desynchronizes from video after 10–15 minutes | Audio and video segment boundaries must align — use the same SegmentLength for audio outputs and set AudioGroupId to group audio with its video rendition; in HLS, each variant stream should have video + audio in the same segment file |
| CloudFront serving stale manifest | New segments available in S3 but player replays old content | Set DefaultTTL: 0 and MaxTTL: 30 for the *.m3u8 cache behavior; also send Cache-Control: no-cache headers on manifest objects in S3 |