MP4 to M3U8 Tutorial: FFmpeg HLS Slicing and AES-128 Encryption
Converting MP4 to M3U8 (HLS format) is essential for web streaming. HLS is the standard for adaptive bitrate streaming, supported by Safari natively and other browsers via hls.js. This tutorial covers FFmpeg commands for slicing, AES-128 encryption, and deployment.
What is HLS?
HLS (HTTP Live Streaming) is Apple's streaming protocol (RFC 8216). It works by:
- Encoding video at multiple bitrates/resolutions
- Splitting each version into small TS segments (2-10 seconds)
- Generating M3U8 playlist files describing the segments
- Serving everything over standard HTTP
Players download the M3U8, then request segments based on available bandwidth, switching quality as conditions change.
For M3U8 format details, see our M3U8 file complete guide.
Basic MP4 to M3U8 conversion
Stream copy (fastest, lossless)
ffmpeg -i input.mp4 \
-codec copy \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "seg_%03d.ts" \
playlist.m3u8Parameters:
-codec copy: Copy streams without re-encoding (fastest, lossless)-hls_time 6: 6-second segments-hls_playlist_type vod: Video-on-demand playlist-hls_segment_filename "seg_%03d.ts": Segment naming pattern (seg_000.ts, seg_001.ts, etc.)playlist.m3u8: Output playlist file
This runs in seconds for most videos.
Re-encode to H.264
If source video isn't H.264/AAC (or you want to change quality):
ffmpeg -i input.mp4 \
-c:v libx264 -crf 23 -preset medium \
-c:a aac -b:a 128k \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "seg_%03d.ts" \
playlist.m3u8Multi-bitrate HLS (adaptive streaming)
For adaptive bitrate streaming, generate multiple versions and a master playlist:
ffmpeg -i input.mp4 \
-map 0:v:0 -map 0:a:0 -map 0:v:0 -map 0:a:0 -map 0:v:0 -map 0:a:0 \
-c:v:0 libx264 -b:v:0 800k -s:v:0 640x360 \
-c:v:1 libx264 -b:v:1 2000k -s:v:1 1280x720 \
-c:v:2 libx264 -b:v:2 5000k -s:v:2 1920x1080 \
-c:a aac -b:a 128k \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-master_pl_name master.m3u8 \
-hls_segment_filename "v%v/seg_%03d.ts" \
"v%v/playlist.m3u8"This creates:
master.m3u8: Master playlist referencing all versionsv0/playlist.m3u8: 360p playlistv1/playlist.m3u8: 720p playlistv2/playlist.m3u8: 1080p playlist
Segment duration guide
| Use case | Duration | Pros | Cons |
|---|---|---|---|
| VOD | 6-10 seconds | Fewer files, better compression | Higher latency |
| Live streaming | 2-4 seconds | Lower latency | More files, encoding overhead |
| LL-HLS | 1-2 seconds | Sub-2-second latency | Requires special server support |
| Mobile | 4-6 seconds | Balance for cellular networks | - |
AES-128 encryption
AES-128 encryption provides basic content protection. Not as strong as DRM but enough to prevent casual downloading.
Generate encryption key
# Generate 16-byte random key
openssl rand 16 > enc.key
# Generate 16-byte IV
openssl rand -hex 16Create key info file
Create enc.keyinfo:
https://example.com/enc.key
enc.key
00000000000000000000000000000001Format:
- Line 1: URL where players will fetch the key
- Line 2: Local path to key file (for FFmpeg to use)
- Line 3: IV (hex string)
Encrypt HLS with FFmpeg
ffmpeg -i input.mp4 \
-codec copy \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "seg_%03d.ts" \
-hls_key_info_file enc.keyinfo \
playlist.m3u8The resulting M3U8 will contain:
#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/enc.key",IV=0x00000000000000000000000000000001Auto-generate key with FFmpeg
ffmpeg -i input.mp4 \
-codec copy \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "seg_%03d.ts" \
-hls_enc 1 \
-hls_enc_key enc.key \
-hls_enc_key_url https://example.com/enc.key \
-hls_enc_iv 1234567890abcdef1234567890abcdef \
playlist.m3u8Live streaming HLS
# From a live source (e.g., RTMP input)
ffmpeg -i rtmp://example.com/live/stream \
-c:v libx264 -preset veryfast -b:v 2500k \
-c:a aac -b:a 128k \
-hls_time 4 \
-hls_list_size 6 \
-hls_flags delete_segments \
-hls_segment_filename "live_%03d.ts" \
live.m3u8-hls_list_size 6: Keep only 6 segments in playlist (sliding window)-hls_flags delete_segments: Delete old segments from disk-hls_time 4: 4-second segments for lower latency
LL-HLS (Low-Latency HLS)
ffmpeg -i input.mp4 \
-c:v libx264 -crf 23 -preset veryfast -tune zerolatency \
-c:a aac -b:a 128k \
-hls_time 2 \
-hls_playlist_type vod \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "seg_%03d.m4s" \
-hls_flags independent_segments \
playlist.m3u8-hls_segment_type fmp4: Use fragmented MP4 (CMAF) instead of TS-hls_flags independent_segments: Each segment is independently decodable-tune zerolatency: Reduce encoding latency
HLS with subtitles
ffmpeg -i input.mp4 -i subtitles.srt \
-map 0:v -map 0:a -map 1:s \
-c:v copy -c:a copy -c:s webvtt \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "seg_%03d.ts" \
-hls_subtitle_path "sub_%v_%03d.vtt" \
playlist.m3u8Deployment
Static file hosting
HLS works over standard HTTP. Upload M3U8 and TS files to any web server:
# Upload to S3
aws s3 sync ./hls-output/ s3://your-bucket/videos/
# Set correct MIME types (most servers auto-detect, but verify)
# .m3u8 → application/vnd.apple.mpegurl
# .ts → video/mp2tNginx configuration
server {
location /hls/ {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www;
add_header Access-Control-Allow-Origin *;
add_header Cache-Control "public, max-age=3600";
}
}CDN deployment
For production, use a CDN:
- CloudFront: AWS CDN, integrates with S3
- Cloudflare: Free tier, global network
- Akamai: Enterprise CDN with HLS specialization
Browser playback
Safari (native support)
<video controls>
<source src="https://example.com/playlist.m3u8" type="application/vnd.apple.mpegurl">
</video>Other browsers (hls.js)
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video controls id="video"></video>
<script>
const video = document.getElementById('video');
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('https://example.com/playlist.m3u8');
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari native
video.src = 'https://example.com/playlist.m3u8';
}
</script>For hls.js details, see our HLS.js complete guide.
Common issues
| Issue | Cause | Solution |
|---|---|---|
| Segments out of order | Naming pattern wrong | Use %03d for zero-padded numbering |
| CORS errors | Server missing CORS headers | Add Access-Control-Allow-Origin: * |
| Audio missing | Multiple audio tracks | Use -map to select audio |
| Encryption fails | Key info file format wrong | Check 3-line format |
| Live playlist not updating | Wrong playlist type | Remove -hls_playlist_type vod |
| Segments too large | Wrong -hls_time | Use 2-4 seconds for live |
| Poor quality | Source low quality | Re-encode with -crf 23 |
Summary
HLS is the standard for web streaming, and FFmpeg makes MP4 to M3U8 conversion straightforward:
- Basic conversion:
ffmpeg -i input.mp4 -codec copy -hls_time 6 -hls_playlist_type vod -hls_segment_filename "seg_%03d.ts" playlist.m3u8 - Adaptive streaming: Multiple bitrates with master playlist
- Encryption: AES-128 with key info file
- Live streaming: Use
-hls_list_sizeanddelete_segments - Low latency: Use fMP4 segments and shorter duration
Quick reference:
- VOD:
-hls_playlist_type vod -hls_time 6 - Live:
-hls_list_size 6 -hls_flags delete_segments -hls_time 4 - Encryption:
-hls_key_info_file enc.keyinfo - LL-HLS:
-hls_segment_type fmp4 -hls_time 2
For FFmpeg basics, see our FFmpeg Getting Started Tutorial.