Single File HTTP "Live" Streaming
Playing large video files on the web, short of uploading to YouTube or some other processing and hosting service, requires some preparation. For browsers to efficiently download just the portion surrounding what visitors are playing, you’ll generally want to rewrite the MP4 so its moov atom is at the beginning of the file (usually called “fast start”), allowing the browser to grab the metadata needed for playback and seeking without first having to download all the way to the end of the file.
One alternative is to provide the media in the HTTP Live Streaming format developed by Apple, which as the name suggests, was originally designed for live streaming. The format also supports post-live (oftentimes referred to as Video on Demand or VOD) playback of media. However, HTTP Live Streaming defaults to playing back pieces of the original media by chopping it up into segment files, since during a truly live recording, the end is not yet known, so segments are fetched moments after being captured and made available. It’s a convenient format for the modern web because the recording’s manifest and segments are delivered over the ubiquitous and typically network-allowed HTTP protocol instead of one of the more temperamental media streaming protocols like RTSP.
But for HTTP Live Streaming playback of post-live or pre-existing media, having to maintain a manifest and a whole pile of media segments can be kind of a pain. It’s way simpler to track and manage as few as two files instead of potentially a bunch. When producing an HTTP Live Stream with ffmpeg, you can set a couple of flags to cause it to produce just one .ts segment file:
ffmpeg \
-i input.mp4 \
-c:v libx264 \
-c:a aac \
-hls_time 6 \
-hls_segment_type mpegts \
-hls_flags single_file \
-hls_playlist_type vod \
index.m3u8
Setting -hls_flags single_file will cause ffmpeg to output index.m3u8 and index.ts, where index.ts contains the entire duration of the media rather than splitting it into several-second chunks and listing those in the index.m3u8 manifest. -hls_playlist_type vod adds #EXT-X-PLAYLIST-TYPE:VOD and #EXT-X-ENDLIST to the manifest, which tells the player the whole timeline is known up front so it can scrub freely. It also implies -hls_list_size 0, so you don’t need both. Everything else is ordinary encoding using whatever bitrate, preset, and profile settings you prefer, since none of that interacts with the single-file part.
The .m3u8 manifest text file is where the single-file mapping happens:
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:6.000000,
#EXT-X-BYTERANGE:1128144@0
index.ts
#EXTINF:6.000000,
#EXT-X-BYTERANGE:1093472@1128144
index.ts
#EXTINF:6.000000,
#EXT-X-BYTERANGE:1121368@2221616
index.ts
#EXT-X-ENDLIST
Unlike a live broadcast where each segment is added moments after it’s captured and encoded, the VOD playlist uses the same media file name on every line. The media is still logically divided into segments, but each segment is now a length@offset byte range into a single file rather than pointing to one of potentially many files on disk.
Single-file HTTP Live Streaming has been supported by playback clients, particularly iOS, since iOS 5. All that’s required to support this on the server side is accepting and respecting the HTTP Range header, which lets the client ask for a given range of bytes and the server respond with those bytes. The Range header has been part of HTTP/1.1 for decades and is supported by basically all HTTP servers, making serving single file HTTP Live Streaming quite convenient. This only works for media that’s already finished (i.e. isn’t live), though, since you can’t append to a file you’re actively serving byte ranges out of.
If you’re serving this with a CDN, individual segment files may still be the way to go. CDNs can have different caching and range-request behaviors, and a large single object can be less convenient to cache (or invalidate) than many smaller immutable segment objects. Depending on the CDN, a Range request may also result in more data being fetched from the origin than the client actually requested.
There’s also a variant of this that works with an MP4 container instead of an MPEG-2 transport stream, and you do get some nominal bandwidth savings. This requires iOS 10, which is long since available now, too.
ffmpeg \
-i input.mp4 \
-c:v libx264 \
-c:a aac \
-hls_time 6 \
-hls_segment_type fmp4 \
-hls_flags single_file \
-hls_playlist_type vod \
index.m3u8
Again, you get an index.m3u8 manifest, but now a index.m4s media file. The manifest gets an EXT-X-MAP tag pointing at index.m4s:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="index.m4s",BYTERANGE="900@0"
#EXTINF:6.000000,
#EXT-X-BYTERANGE:1119832@900
index.m4s
#EXTINF:6.000000,
#EXT-X-BYTERANGE:1086104@1120732
index.m4s
#EXT-X-ENDLIST
H.264/AVC can be carried in either MPEG-2 TS .ts files or fMP4’s .m4s, but H.265/HEVC, AV1, and Dolby Vision require fMP4. If you want to use one of those newer codecs, you’ll have to use the fMP4 segment type. As well, players like hls.js transmux transport streams into fMP4 before handing it to the browser’s media API, so giving it fMP4 in the first place lets them skip a step.
HTTP Live Streaming (segmented or single-file) is one of my favorite formats because it puts a complicated thing like video playback on top of a solid, well-supported protocol like HTTP. In addition to being pretty well-adopted by clients, there’s no special streaming server to run, no unusual ports to open, and nothing that breaks when someone tries to watch a video over a restrictive corporate or hotel type of network.






Carefully placed spotlights and torches regularly cast Indy’s recognizable shadow.
Light reveals Indy’s eyes when he’s making a connection or solving a puzzle.
Just marvel at the level of detail achieved here: The fine individual hair, the wear on the seams of the leather jacket, the distressed fur felt on the fedora, and the specular highlights on the sclera of the eyes.
Every region has loads to explore, little sub-plots to follow, and NPCs going about their business. Some casually relay details or hints towards as-yet-unsolved puzzles.
Attention to detail is everywhere in this game. Here, the number of remaining shots in the heads-up display corresponds to the number of visibly unfired rounds in the revolver. The reload animation has Indy individually pluck out spent rounds and replace them, rotating the cylinder for each.
Acquiring and reading Adventure Books boosts combat, survival, brawling, etc.
Even the Pause menu looks like it belongs. Also, hey, it’s me: I’m