๐Ÿ› ๏ธ How to Run a Privacy-First, Browser-Based Stream Downloader (FlowPick) โ€” A Hands-On Tutorial

dev.to

Hey folks ๐Ÿ‘‹

If you've ever wanted to save a video lecture, a livestream replay, or a podcast episode for offline listening, you've probably run into the usual options: sketchy "online video parser" websites that ask you to paste your link into their server, or desktop apps that want you to sign up and upload stuff. Neither feels great when the whole point is your content.

I went looking for something better and ended up working with FlowPick โ€” an open-source, privacy-first media downloader that runs entirely in your browser. No uploads, no accounts, no telemetry. Everything (sniffing, downloading, merging, transcoding) happens client-side with FFmpeg compiled to WebAssembly.

In this tutorial we'll:

  • Clone and run FlowPick locally
  • Download our first HLS (.m3u8) and DASH (.mpd) stream
  • Build and deploy it
  • Poke at the internals so we can customize it

If you just want to try it without installing anything, there's a hosted version at https://flowpick.net (more below). The full source is on GitHub: https://github.com/ezwebtools/flowpick.

๐Ÿ”— Repo: https://github.com/ezwebtools/flowpick ยท Live tools: https://flowpick.net


A 30-second primer: what are HLS and DASH?

Before we touch code, two words you'll see everywhere in this space:

  • HLS (HTTP Live Streaming) uses a .m3u8 manifest that lists small .ts (or fMP4) segments. Common for live streams and a lot of video platforms.
  • DASH (Dynamic Adaptive Streaming over HTTP) uses a .mpd manifest; video and audio usually travel as separate .m4s tracks. YouTube and Bilibili lean on this.

The key idea: the "video" isn't one file. It's a playlist pointing at dozens (sometimes hundreds) of tiny segments. A downloader's job is to fetch all the segments, decrypt them if needed, and stitch them back into one playable file. That's exactly what FlowPick does โ€” in the browser.


What FlowPick is, in one paragraph

FlowPick is a Nuxt 4 app that ships in two shapes:

  1. A browser extension that sniffs media from the current tab's network requests.
  2. An online tool website with dedicated M3U8 and DASH downloaders.

Both share the same core download engine (a couple of Vue composables). It handles HLS, DASH, plain video/audio files, and images. Encrypted HLS (AES-128) is decrypted in-browser via the Web Crypto API โ€” the key never leaves your machine.


Prerequisites

  • Node.js 20 LTS or newer
  • pnpm (the repo scripts assume it; npm/yarn work if you align the lockfile)
  • Git
node -v   # should be v20.x or higher
pnpm -v
Enter fullscreen mode Exit fullscreen mode

Step 1 โ€” Clone and install

git clone https://github.com/ezwebtools/flowpick.git
cd flowpick
pnpm install
Enter fullscreen mode Exit fullscreen mode

Heads up: the postinstall script runs nuxt prepare automatically, which generates type declarations and the route manifest. The first install takes a bit longer โ€” that's expected. If it ever gets interrupted, just run pnpm exec nuxt prepare to catch up.


Step 2 โ€” Run it locally

pnpm dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000. The pages you care about:

  • /m3u8-downloader โ€” for HLS streams
  • /dash-downloader โ€” for DASH streams
  • /docs โ€” full documentation
  • /blog โ€” tutorials and deep dives

Prefer not to install anything? The same tools are live at https://flowpick.net/m3u8-downloader and https://flowpick.net/dash-downloader. That's the fastest way to test the workflow before you self-host.

๐Ÿ”— Try it now: https://flowpick.net ยท M3U8 tool: https://flowpick.net/m3u8-downloader ยท DASH tool: https://flowpick.net/dash-downloader


Step 3 โ€” Download your first stream

HLS example

  1. Find a page that serves an .m3u8 stream. Open DevTools (F12) โ†’ Network โ†’ filter m3u8.
  2. Play the video for a few seconds so the manifest actually gets requested.
  3. Right-click the .m3u8 request โ†’ Copy link address.
  4. Paste it into the M3U8 Downloader (localhost or the hosted one).
  5. Hit Parse. FlowPick reads the manifest and lists available qualities.
  6. Pick a quality, hit Start Download.

Under the hood FlowPick downloads every segment, decrypts if needed (AES-128), and merges them. For MP4 output it runs a remux โ€”

ffmpeg -i input.ts -c copy output.mp4
Enter fullscreen mode Exit fullscreen mode

โ€” entirely in the browser. No re-encoding, no quality loss, and your file never touches a server.

DASH example

Paste the .mpd URL into the DASH Downloader, choose your video and audio tracks, and FlowPick auto-merges A/V for you. Because DASH keeps video and audio in separate streams, this merge step is what turns it into a single watchable file.

๐Ÿ’ก The hosted tools at https://flowpick.net are perfect when you're on a machine where you can't install the extension.


Step 4 โ€” Build and deploy

Production build:

pnpm build
pnpm preview
Enter fullscreen mode Exit fullscreen mode

The default Nitro preset is cloudflare-pages-static, so pnpm build emits static assets in .output/. You can drop that on any static host โ€” Cloudflare Pages, Netlify, Vercel, or your own cloud. Want a Node server instead? Switch nitro.preset to node-server, rebuild, and run node .output/server/index.mjs.

One thing you must keep: the downloader routes set two response headers โ€”

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Enter fullscreen mode Exit fullscreen mode

These enable cross-origin isolation, which is what lets FFmpeg WASM use multiple threads (via SharedArrayBuffer) and lets large files stream to disk via StreamSaver. Drop them and big downloads silently degrade.


Step 5 โ€” Customize it

Low-effort tweaks that go a long way:

  • Edit docs / copy: everything in content/ is Markdown or YAML, validated by a Zod schema. Change text or add a page; it's live on reload.
  • Tune the engine: open app/composables/useStreamMerge.ts. Default concurrency is 2 (range 1โ€“8); retries use exponential backoff (max 3); writes fall back FSA โ†’ StreamSaver โ†’ Blob automatically.
  • Config: nuxt.config.ts holds i18n (en / zh-Hans / zh-Hant / ja / ko), runtime config for analytics (NUXT_PUBLIC_GA_ID, NUXT_PUBLIC_CLARITY_ID), and the route headers above.

A concrete example โ€” bump the default concurrency for fast broadband by tweaking the settings defaults in the extension/online tool UI, or change the write buffer by editing useStreamMerge.ts. The composable's input shape is clean:

export interface StreamMergeOptions {
  segments: ArrayBuffer[] | AsyncGenerator<ArrayBuffer>
  totalSegments: number
  filename: string
  outputFormat?: 'mp4' | 'ts'
  onProgress?: (progress: StreamMergeProgress) => void
  signal?: AbortSignal
}
Enter fullscreen mode Exit fullscreen mode

How it actually works (the fun part)

The engine is two composables:

  • useStreamMerge.ts โ€” downloads segments with a Worker Pool (shared counter, so no worker sits idle), decrypts AES-128 with Web Crypto, concatenates (TS) or remuxes (MP4) via FFmpeg, then writes using the best available strategy.
  • useFFmpeg.ts โ€” loads @ffmpeg/ffmpeg (WASM), detects multithreading support, and runs merge/remux commands.

The write strategy is the clever bit:

Strategy When Notes
FSA Chrome/Edge 86+ Streaming write, any size, constant memory
StreamSaver Wide compatibility Service Worker proxy, supports large files
Blob Fallback Memory-limited; files over ~1.5 GB rejected

Caveats โ€” read before you ship

  • DRM-protected content (Widevine, etc.) can't be decoded. By design.
  • The web tool is subject to CORS. If the stream server doesn't send cross-origin headers, use the extension instead.
  • FFmpeg WASM is ~8 MB on first load (cached afterward).
  • Big-file experience is best on Chrome/Edge; Safari falls back to Blob mode.

Also: only download content you're allowed to. Respect each platform's terms and your local laws โ€” keep downloads for personal/offline use, don't redistribute.


Wrap-up

That's the whole loop: clone โ†’ run โ†’ download your first stream โ†’ build โ†’ deploy โ†’ customize. FlowPick is open source on https://github.com/ezwebtools/flowpick, and you can try the hosted tools right now at https://flowpick.net (M3U8: https://flowpick.net/m3u8-downloader, DASH: https://flowpick.net/dash-downloader).

If this was useful, drop a โค๏ธ or a comment, and tell me what you'd build on top of it. Happy hacking!

Source: dev.to

arrow_back Back to News