What Are M3U and M3U8?
M3U (Moving Picture Experts Group Audio Layer 3 URL, though the name predates MP3) is a plain-text playlist format originally developed by Winamp for managing audio playlists. An M3U file simply lists media file paths or URLs, one per line, with optional metadata comments beginning with #EXTM3U and #EXTINF.
M3U8 is the UTF-8 encoded version of M3U and is the playlist format used by HLS (HTTP Live Streaming), Apple's adaptive streaming protocol specified in RFC 8216. In HLS, M3U8 files have a fundamentally different role than simple playlists: they orchestrate the adaptive delivery of video/audio streams broken into short segments.
Basic M3U: Simple Media Playlists
A simple M3U playlist file:
#EXTM3U
#EXTINF:180,Artist Name - Track Title
/music/artist/album/01-track.mp3
#EXTINF:245,Another Artist - Another Song
https://stream.example.com/music/song.mp3
#EXTINF:-1,Live Radio Stream
https://radio.example.com/stream
Format rules:
- First line:
#EXTM3U(identifies extended M3U format) #EXTINF:duration,title— track duration in seconds (-1 for live/unknown) and display title- Next line: file path or URL to the media
- Lines starting with
#that are not#EXTM3Uor#EXTINFare comments
M3U files are opened by VLC, Windows Media Player, iTunes, Winamp, MPV, and virtually every media player.
HLS: How HTTP Live Streaming Works
HLS is an adaptive bitrate streaming protocol. The key idea: instead of serving one large video file, HLS breaks the stream into short segments (typically 2–10 seconds each as MPEG-2 TS or fMP4 files) and describes them in M3U8 manifest files. The client player fetches the manifest, reads the segment URLs, downloads segments sequentially, and assembles continuous playback.
HLS components:
Master Playlist (variant.m3u8)
├── Low quality stream → 360p.m3u8
│ ├── 360p_000.ts
│ ├── 360p_001.ts
│ └── 360p_002.ts
├── Medium quality stream → 720p.m3u8
│ ├── 720p_000.ts
│ └── ...
└── High quality stream → 1080p.m3u8
├── 1080p_000.ts
└── ...
Master Playlist (variant.m3u8)
The master playlist lists available quality variants:
#EXTM3U
#EXT-X-VERSION:3
# 360p variant
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.42E01E,mp4a.40.2"
360p.m3u8
# 720p variant
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720,CODECS="avc1.4D401F,mp4a.40.2"
720p.m3u8
# 1080p variant
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
1080p.m3u8
# Audio-only variant
#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="mp4a.40.2"
audio_only.m3u8
Media Playlist (720p.m3u8)
Each variant has its own media playlist listing the actual segments:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
720p_000.ts
#EXTINF:10.0,
720p_001.ts
#EXTINF:10.0,
720p_002.ts
#EXTINF:7.543,
720p_003.ts
#EXT-X-ENDLIST
Key HLS tags:
| Tag | Purpose |
|---|---|
#EXT-X-VERSION |
HLS protocol version (3 = widely supported, 7 = fMP4 segments) |
#EXT-X-TARGETDURATION |
Maximum segment duration in seconds |
#EXT-X-MEDIA-SEQUENCE |
Sequence number of the first segment |
#EXT-X-ENDLIST |
Marks a VOD (complete, not live) stream |
#EXT-X-STREAM-INF |
Describes a variant stream (in master playlist) |
#EXT-X-KEY |
Encryption key for DRM |
#EXT-X-DISCONTINUITY |
Marks timeline discontinuity (e.g., ad insertion) |
#EXT-X-MAP |
Initialization segment (needed for fMP4 segments) |
#EXT-X-PLAYLIST-TYPE:VOD |
Static VOD playlist (vs EVENT or LIVE) |
Creating HLS Streams with FFmpeg
FFmpeg is the standard tool for creating HLS content:
Basic HLS Segmentation
# Convert MP4 to HLS (single quality)
ffmpeg -i input.mp4 \
-codec:v libx264 -crf 20 -preset fast \
-codec:a aac -b:a 128k \
-hls_time 10 \
-hls_list_size 0 \
-hls_segment_filename "segment_%03d.ts" \
output.m3u8
Multi-Bitrate HLS (Adaptive)
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3]" \
-map "[v1]" -map 0:a -c:v:0 libx264 -b:v:0 5000k -s:v:0 1920x1080 \
-map "[v2]" -map 0:a -c:v:1 libx264 -b:v:1 2800k -s:v:1 1280x720 \
-map "[v3]" -map 0:a -c:v:2 libx264 -b:v:2 800k -s:v:2 640x360 \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_list_size 0 \
-hls_segment_filename "stream_%v/seg_%03d.ts" \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
stream_%v/playlist.m3u8
Live HLS Stream (Sliding Window)
# Live stream: keep only the last 10 segments in the playlist
ffmpeg -re -i input.mp4 \
-codec:v libx264 -preset veryfast \
-codec:a aac -b:a 128k \
-hls_time 6 \
-hls_list_size 10 \
-hls_flags delete_segments \
-hls_segment_filename "live/seg_%d.ts" \
live/stream.m3u8
Downloading HLS Streams
Using FFmpeg
# Download a complete VOD HLS stream to MP4
ffmpeg -i https://example.com/video/master.m3u8 \
-c copy -movflags +faststart output.mp4
# Download specific quality variant
ffmpeg -i https://example.com/video/720p.m3u8 \
-c copy output_720p.mp4
# Download with authentication (cookie or header)
ffmpeg -headers "Authorization: Bearer TOKEN" \
-i https://secure.example.com/stream/master.m3u8 \
-c copy output.mp4
Using yt-dlp
# Download HLS stream (yt-dlp handles all quality selection and merging)
yt-dlp https://example.com/watch?v=xyz
# Select specific quality
yt-dlp -f "best[height<=720]" https://example.com/watch?v=xyz
# Download HLS directly from .m3u8 URL
yt-dlp -f best https://example.com/stream/master.m3u8
Playing HLS in a Web Browser
Using hls.js (JavaScript)
<!-- Include hls.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video" controls width="800"></video>
<script>
const video = document.getElementById('video');
const src = 'https://example.com/stream/master.m3u8';
if (Hls.isSupported()) {
const hls = new Hls({
maxBufferLength: 30, // seconds to buffer ahead
startLevel: -1, // -1 = auto quality selection
enableWorker: true,
});
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => video.play());
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari has native HLS support
video.src = src;
video.play();
}
</script>
Safari and iOS/iPadOS have native HLS support and do not need hls.js. All other major browsers require hls.js.
Parsing M3U8 Playlists in Python
import urllib.request
import re
def parse_master_playlist(url: str) -> list[dict]:
"""Parse an HLS master playlist and return variant stream info."""
with urllib.request.urlopen(url) as f:
content = f.read().decode('utf-8')
variants = []
lines = content.splitlines()
i = 0
while i < len(lines):
line = lines[i]
if line.startswith('#EXT-X-STREAM-INF:'):
attrs = {}
# Parse key=value pairs
for match in re.finditer(r'(\w[\w-]*)=((?:"[^"]*"|\d+x\d+|[\d.]+))', line):
key, value = match.group(1), match.group(2).strip('"')
attrs[key] = value
# Next line is the variant playlist URL
i += 1
variant_url = lines[i].strip()
if not variant_url.startswith('http'):
# Relative URL — construct absolute
base = url.rsplit('/', 1)[0]
variant_url = f"{base}/{variant_url}"
attrs['url'] = variant_url
variants.append(attrs)
i += 1
return sorted(variants, key=lambda v: int(v.get('BANDWIDTH', 0)))
# Usage
variants = parse_master_playlist('https://example.com/stream/master.m3u8')
for v in variants:
bw_kbps = int(v.get('BANDWIDTH', 0)) // 1000
res = v.get('RESOLUTION', 'audio')
print(f"{res} @ {bw_kbps} kbps → {v['url']}")
Serving HLS with nginx
server {
listen 80;
server_name video.example.com;
location /hls {
alias /var/www/hls;
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
add_header Cache-Control "no-cache"; # M3U8 — never cache live
add_header Access-Control-Allow-Origin "*"; # Allow cross-origin players
}
location /hls/vod {
alias /var/www/hls/vod;
# VOD segments can be cached aggressively
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
Related conversions
Common video conversions that pair well with this guide: