When I started building the downloader on ramrom.io, I assumed every video on the internet was an MP4. A URL points at a file, you fetch the bytes, you hand them to the visitor. Simple. Then I started handling real provider responses and discovered that a large chunk of video on the web is not a file at all — it is a playlist.
That playlist is HLS: HTTP Live Streaming. Instead of one video file, the server gives you a small text file, an .m3u8 manifest, that lists dozens of tiny segments — usually .ts files a few seconds long each, in several quality levels. The player downloads the segments in order and stitches them together in real time. TikTok, Instagram, Facebook and YouTube all use it or something like it for playback.
This matters to a downloader because "give me the video URL" and "give me the HLS manifest" are two very different problems.
A progressive MP4 is honest. It is one file, one set of bytes, and you can proxy it straight through your server with a Content-Disposition: attachment header so the browser saves it instead of playing it. You can verify the bytes before you hand them over — I check the first few bytes for the ftyp box so I know it is a real MP4 and not an HTML error page wearing a .mp4 name. You can also tell the visitor the file size up front, because the file has a Content-Length.
HLS gives you none of that. The manifest is a text file. There is no single file size, because the video is hundreds of segments. You cannot just point a download attribute at the .m3u8 and expect the browser to save a video — most browsers will shrug. And the segments often expire: the URLs inside a manifest are frequently signed and die after minutes, which is why provider APIs return fresh manifests per request and why my server resolves them live instead of caching links.
So what does a downloader actually do with HLS? There are two honest approaches, and I ended up needing both.
The first is stitching: fetch the manifest, download every segment server-side, and concatenate them into one MP4 the visitor can actually save. That works when the segments are plain .ts files with no encryption. It is heavy, though — on shared hosting with modest bandwidth, stitching a 200-segment video is slow and memory-hungry, and if the provider encrypts segments with AES-128 (the key is in the manifest, fetched with a separate request), you have to handle decryption too. I tried this and learned quickly why most downloader APIs do not ship stitched files: it turns a stateless endpoint into a media pipeline.
The second is delegation: hand the visitor the manifest URL directly and let their browser or app deal with it. A lot of mobile players open .m3u8 links fine. This is also where my proxy's "direct" links come from — the provider tunnel returns a stream the visitor's browser can play, and rather than pretend my server can improve on it, I flag it as direct and let the visitor's device handle the streaming. It is the honest option when the infrastructure cannot stitch.
The trap is pretending there is no difference. A site that offers "Download HD MP4" and then serves an .m3u8 manifest to a desktop browser is not giving the visitor a video — it is giving them homework. Every format button on the site has to mean what it says: a real progressive MP4 I verified, or a stream link labelled as a stream link.
A few practical lessons from working with both:
-
Check the URL suffix before you promise anything. If the resolved URL ends in
.m3u8, you are holding a playlist, not a file. Do not label it MP4. - Signed URLs mean resolve fresh, every time. A manifest you cached an hour ago may point at dead segments. The resolve step and the download step should happen close together.
- Report what you can prove. I can verify an MP4's magic bytes and content length. For HLS I can only verify the manifest parses and the first segments return 200. The button label should reflect that gap.
- Know your host's limits. Stitching is a real engineering answer on a VPS; on shared hosting it is a timeout waiting to happen. My constraint pushed me to the direct-link approach for HLS, and the site is better for the honesty.
Most visitors will never think about any of this — and they should not have to. But when the download button says MP4 and delivers an MP4, that is not an accident. It is because somewhere in the code, a check noticed the URL was a playlist and told the truth about it.
Top comments (0)