How FlowPick Merges Hundreds of Video Segments into MP4 in the Browser
You click FlowPick's download button, wait a few minutes, and a complete MP4 shows up in your downloads folder.
This is a bit counterintuitive. Browsers aren't download tools, and they're certainly not video processing tools. Traditional streaming download solutions invariably rely on locally installed programs, or they send data to a server for processing and send it back.
FlowPick does everything inside a browser tab — no server, no app, no command line. This article explains why that's technically feasible and how it actually works.
First, Understand the Problem
An HLS video in transit is not a single complete file. It's a playlist file (M3U8) plus hundreds of 2-10 second segments (.ts files). The player reads the playlist, pulls segments in order, and stitches them together in real time for playback.
A 30-minute 1080P video with 10-second segments means 180 files. DASH is similar, except the segments are .m4s and audio and video travel in two separate streams.
What does "downloading" this video actually mean?
- Parse the M3U8/MPD to get all segment URLs
- Download hundreds of segments one by one
- If AES-128 encrypted, decrypt each segment
- Stitch hundreds of fragments into one continuous file
- For HLS TS segments, remux into MP4 (a player-friendly format)
- For DASH, also merge the video and audio tracks
This isn't a simple file download — it's a processing pipeline. Traditional tools hand this off to FFmpeg command line or a server. FlowPick moves the entire pipeline into the browser.
Detection Comes First: The webRequest API
Before processing segments, FlowPick needs to know what videos are on the page.
Browser extensions can use the webRequest API to listen to all requests at the network layer — not the DOM layer, but the actual HTTP request layer. Every network request made from the current tab, FlowPick can see the request URL and response headers.
FlowPick scans Content-Type and URL patterns, recording any requests that match HLS/DASH/direct video/audio/image signatures. When you open the FlowPick popup, the list you see is every media resource it has intercepted up to that moment.

This also explains why FlowPick can't see a stream before the video starts playing — the M3U8 request is only made when the player initializes. FlowPick can only see requests that have already happened; it can't actively probe.
The Core Challenge: Turning TS Segments into MP4
Once the video segments are downloaded, the format problem hits.
HLS segments are in MPEG-TS format (.ts). If you just binary-concatenate 180 .ts files end to end, you get a big .ts file — some players can play it, but most devices, video editing software, and video platforms prefer MP4.
The core difference between TS and MP4 is the container format:
| MPEG-TS | MP4 | |
|---|---|---|
| Designed for | Broadcast transmission, loss-tolerant | File storage, random access |
| Each packet | Fixed 188 bytes | Variable-length Box structure |
| Index location | No standalone index, sequential read only | moov atom, can be placed at file start |
| Random seeking | Painful | Direct jump via moov index |
Turning TS into MP4 is essentially extracting the raw video and audio data inside and repackaging it into MP4's Box structure. This operation is called remuxing — it doesn't touch the codec. Every bit of video and audio stays exactly as-is; only the "shell" changes.
Remuxing doesn't need a GPU, and CPU load is minimal. Most of the time is spent on file I/O. This is the technical premise that makes in-browser remuxing viable — if it required re-encoding (transcoding), browser CPU power simply couldn't handle it.
The Two-Engine Selection Logic
FlowPick has two remuxing engines internally, switching automatically based on the scenario:
Engine 1: FFmpeg WASM
FFmpeg is the industry-standard command-line video processing tool, supporting virtually every format. WebAssembly (WASM) is a binary format that runs at near-native speed inside the browser. Compile FFmpeg to WASM, and you can invoke FFmpeg's full capabilities inside a browser tab.
When FlowPick calls FFmpeg WASM, the actual command executed looks roughly like this:
ffmpeg -f concat -safe 0 -i filelist.txt \
-c copy \
-movflags +faststart \
-bsf:a aac_adtstoasc \
output.mp4
Parameter breakdown:
| Parameter | What it does |
|---|---|
-f concat | Use the concat demuxer to handle multiple TS segments |
-c copy | Stream copy, zero re-encoding |
-movflags +faststart | Move the index to the file start for streaming playback |
-bsf:a aac_adtstoasc | Convert AAC packaging format (TS uses ADTS, MP4 uses ASC) |
-c copy is the key — it ensures FlowPick does pure remuxing with zero quality loss and no heavy CPU requirements.
WASM FFmpeg runs inside the browser's JavaScript engine, so there's a performance penalty. But remuxing is I/O-bound, not CPU-bound, so real-world performance is decent. Based on measured data (Chrome + Intel i7-13700 + 16GB RAM):
| Video | Segments | File Size | FFmpeg WASM Remux Time |
|---|---|---|---|
| 10 min 1080P | ~60 | ~200 MB | ~3 sec |
| 30 min 1080P | ~180 | ~600 MB | ~8 sec |
| 60 min 4K | ~360 | ~2.5 GB | ~30 sec |
| 120 min 4K | ~720 | ~5 GB | ~60 sec |
This speed is perfectly usable for everyday needs.
FFmpeg WASM limitations: WASM runs in a 32-bit address space with a theoretical 4GB memory ceiling. In practice, FFmpeg WASM needs to write all segments into its virtual filesystem (WASM memory). Large files eat up a lot of memory, and exceeding the limit causes a crash.
Engine 2: TSToMP4Muxer
This is FlowPick's custom pure-JavaScript streaming remuxer, purpose-built for the single TS → MP4 scenario, but with a completely different approach.
FFmpeg WASM's model is: gather all segments first, then process them together. TSToMP4Muxer's model is: download, process, and write to disk simultaneously.
Specifically, TSToMP4Muxer parses TS data packets (each fixed at 188 bytes) in real time as segments come in, extracts PES (Packetized Elementary Stream) data, converts it to MP4 Box format on the fly, and continuously writes to disk via the browser's Streams API.
The benefits are clear:
- Memory usage is independent of file size — processing a 5GB video uses roughly the same memory as a 200MB video
- No need to load FFmpeg WASM (a 30MB+ WASM file), so startup is faster
- Naturally handles large files — only a small chunk of data is in memory at any given time
The trade-off is narrower coverage: it only does single TS → MP4 and can't handle tasks like DASH audio/video merging that require two input streams.
FlowPick's switching logic: regular HLS prefers TSToMP4Muxer; DASH or dual-stream merging scenarios use FFmpeg WASM. Files over 2GB also prefer TSToMP4Muxer or direct TS output (skipping remux) to avoid WASM memory overflow.
Where the File Goes: File System Access API
Where does the processed video data go? This isn't a problem for traditional download tools, but browser JavaScript has no default permission to write directly to the filesystem.
FlowPick uses the File System Access API (FSA) provided by Chrome and Edge. After the user picks a save location, FSA provides a writable file handle that lets JavaScript write continuously in streaming mode — rather than accumulating all data in memory first and writing it out in one shot.
This means: even a 5GB 4K video only keeps the currently-processing chunk in memory at any time. FlowPick theoretically has no file size ceiling (limited only by disk space and WASM memory limits, the latter bypassed by TSToMP4Muxer).
Firefox also supports it, but uses a Blob-based approach with slightly different memory behavior and less stability for very large files.
AES-128 Decryption: Web Crypto API
Some HLS streams encrypt each segment. The M3U8 will have a line like:
#EXT-X-KEY:METHOD=AES-128,URI="https://api.example.com/key?token=..."
When FlowPick detects this tag, it first requests the key from the Key URI (carrying your current cookies and session — since the request comes from the browser extension, it's your browser's real session). Once it has the key, for each downloaded segment, it uses the browser's built-in Web Crypto API to perform AES-128-CBC decryption, then feeds the decrypted data into the remux pipeline.
The entire key retrieval and decryption process happens locally in the browser. The key never passes through any FlowPick server.
What FlowPick can't handle: Widevine, PlayReady, FairPlay, and other DRM systems. These bind decryption keys to dedicated hardware or OS modules that JavaScript can't access. This is a technical limitation, not a feature decision.
DASH: Download Audio and Video Separately, Then Merge
DASH adds a layer of complexity beyond HLS — audio and video are two independent streams, segmented and transmitted separately. What you download isn't a bunch of complete video segments, but a bunch of video-only segments (no sound) plus a bunch of audio-only segments (no picture).
FlowPick's DASH handling:
- Parse the MPD XML, identify video AdaptationSet and audio AdaptationSet
- Download both sets of segments simultaneously (in parallel)
- Download init segments for each (contains codec parameters, required for merging)
- Feed both streams into FFmpeg WASM for audio/video merging
The merge command is roughly:
ffmpeg \
-f concat -safe 0 -i video_list.txt \
-f concat -safe 0 -i audio_list.txt \
-c:v copy -c:a copy \
-movflags +faststart \
output.mp4
Again, -c copy — zero re-encoding. Most of the time is spent on concurrent segment downloads and the final merge I/O.
Concurrent Downloads: Segments Don't Have to Come One at a Time
If hundreds of segments were downloaded serially, speed would be entirely bottlenecked by individual HTTP request latency. FlowPick uses multi-threaded concurrent downloading, defaulting to 2 concurrent threads (a conservative value to avoid triggering server-side rate limiting), adjustable to 4-6 in settings.
Multiple segments download in parallel — whichever finishes first gets processed first, but writes to the file must be reordered by sequence number to ensure correct concatenation order. FlowPick maintains an internal download queue and ordered write buffer to handle this detail.
Failed segment downloads are automatically retried (default 3 attempts). Since segments are small, the cost of a single retry is negligible — no need to start over from scratch.
Practical Differences in Output Format
FlowPick supports two output options:
MP4: remuxed standard MP4 with the moov atom at the file start (faststart). Best compatibility, ideal for long-term storage, uploading, and video editing. The trade-off is the remux step, which adds a few dozen extra seconds for large videos.
TS: direct binary concatenation of all segments, zero CPU overhead, nearly instant. But the file is 5-15% larger than MP4 (TS container overhead), and some devices and platforms don't support TS as well as MP4.
If you're downloading to upload to Bilibili or import into CapCut, pick MP4. If you're just staging it temporarily or plan to process further with FFmpeg command line, pick TS to save time.
What It Costs to Do This in the Browser
Having covered what it can do, let's be clear about what it can't:
No re-encoding. FlowPick only swaps containers, never touches codecs. An H.265 video comes out as H.265 — if an old player doesn't support H.265, it still won't. FlowPick can't help with that.
FFmpeg WASM has a memory ceiling. 4GB of 32-bit WASM address space, minus browser overhead, means the usable amount is less than 4GB. The automatic strategy switch for files over 2GB (TSToMP4Muxer or direct TS output) is specifically to bypass this limit.
WASM is slower than native FFmpeg. WASM isn't native code; there's a performance penalty. A 60-minute 4K video takes FFmpeg WASM about 30 seconds to remux; native FFmpeg might do it in 5 seconds. For everyday use, 30 seconds is acceptable. If you process large volumes daily, native FFmpeg command line is more appropriate.
No hardware acceleration. Native FFmpeg can use NVENC/QSV/VideoToolbox for GPU-accelerated encoding/decoding; the WASM version can't. However, since -c copy doesn't involve encoding or decoding, this limitation has minimal impact on FlowPick's use case.
DRM is a hard wall. This is a hard limit, not a trade-off. Widevine is designed specifically to keep browser extensions from accessing keys.
Overall, FlowPick's ability to fully handle HLS/DASH downloads in the browser comes down to a combination of key technical pieces: WebAssembly lets FFmpeg run in the browser, -c copy keeps processing costs down to I/O only, TSToMP4Muxer solves the large-file memory problem, the File System Access API solves large-file disk writes, and the Web Crypto API handles AES-128 decryption. None of it is "black magic" — it's all direct application of modern browser standard capabilities.
Recommended Reading
- How to Download M3U8/HLS Streams: A Complete Beginner's Guide — now that you understand the principles, here's how to actually do it
- The Code Is Open Source! How I Built FlowPick in Three Months — the story behind how these principles were forced into existence
- What Is DASH Streaming? MPD File Explained — why DASH's audio/video separation design exists
- How to Download Bilibili Videos to Your Computer — Bilibili's DASH is a classic real-world case of the techniques above
- How to Batch Download Images from Any Website — same extension, images can be batch-handled too
- FlowPick vs. Video DownloadHelper — why "no CoApp needed" is a big deal