<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[FlowPick]]></title><description><![CDATA[FlowPick]]></description><link>https://flowpick.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a60ea18a01b32c2f75d76b0/eaec7b1b-f26a-44b9-8fd6-a72b695552e2.png</url><title>FlowPick</title><link>https://flowpick.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 23:01:18 GMT</lastBuildDate><atom:link href="https://flowpick.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Build and Run a Privacy-First, Browser-Based Stream Downloader with FlowPick]]></title><description><![CDATA[A practical, step-by-step guide to running FlowPick locally, downloading your first HLS/DASH stream, deploying it, and customizing the engine.
Why this tutorial exists
Most "download this video" solut]]></description><link>https://flowpick.hashnode.dev/how-to-build-and-run-a-privacy-first-browser-based-stream-downloader-with-flowpick</link><guid isPermaLink="true">https://flowpick.hashnode.dev/how-to-build-and-run-a-privacy-first-browser-based-stream-downloader-with-flowpick</guid><category><![CDATA[FlowPick]]></category><category><![CDATA[open source beginners guide]]></category><category><![CDATA[m3u8-downloader]]></category><dc:creator><![CDATA[FlowPick Team]]></dc:creator><pubDate>Sun, 26 Jul 2026 13:54:08 GMT</pubDate><content:encoded><![CDATA[<p><em>A practical, step-by-step guide to running FlowPick locally, downloading your first HLS/DASH stream, deploying it, and customizing the engine.</em></p>
<h2>Why this tutorial exists</h2>
<p>Most "download this video" solutions fall into two buckets, and both are awkward:</p>
<ol>
<li><p><strong>Online parser websites</strong> — you paste your video link into <em>someone else's</em> server. That means handing over <em>what</em> you're watching, plus whatever metadata travels with the request.</p>
</li>
<li><p><strong>Desktop clients</strong> — powerful, but they usually want an account, sometimes upload data, and often feel like more attack surface than you signed up for.</p>
</li>
</ol>
<p><strong>FlowPick</strong> takes a different route. It's an open-source downloader where <em>all</em> processing — sniffing, downloading, decrypting, merging, transcoding — happens in your browser via FFmpeg compiled to WebAssembly. Your media never leaves your machine. In this guide we'll go from zero to a running, customizable instance.</p>
<blockquote>
<p>🔗 Source: <a href="https://github.com/ezwebtools/flowpick">https://github.com/ezwebtools/flowpick</a> · Hosted tools: <a href="https://flowpick.net">https://flowpick.net</a></p>
</blockquote>
<hr />
<h2>Learning objectives</h2>
<p>By the end of this tutorial you will be able to:</p>
<ul>
<li><p>Run FlowPick locally with <code>pnpm dev</code></p>
</li>
<li><p>Download an HLS (<code>.m3u8</code>) and a DASH (<code>.mpd</code>) stream</p>
</li>
<li><p>Build and deploy the static (or Node) output</p>
</li>
<li><p>Customize copy, engine behavior, and config</p>
</li>
</ul>
<hr />
<h2>Background: HLS and DASH in 30 seconds</h2>
<ul>
<li><p><strong>HLS</strong> exposes a <code>.m3u8</code> manifest listing small segments (usually <code>.ts</code>). Common for live streams.</p>
</li>
<li><p><strong>DASH</strong> exposes a <code>.mpd</code> manifest; video and audio typically arrive as separate <code>.m4s</code> tracks.</p>
</li>
</ul>
<p>Neither is a single file — both are playlists of tiny segments. A downloader fetches every segment, decrypts if necessary, and stitches them into one file. FlowPick does exactly this, client-side.</p>
<hr />
<h2>What FlowPick is</h2>
<p>FlowPick is a Nuxt 4 application with two product shapes that share one core engine:</p>
<ol>
<li><p>A <strong>browser extension</strong> that monitors the active tab's network requests and lists detected media.</p>
</li>
<li><p>An <strong>online tool website</strong> with dedicated M3U8 and DASH downloaders.</p>
</li>
</ol>
<p>It supports HLS, DASH, plain video/audio, and images. AES-128 encrypted HLS is decrypted in-browser with the Web Crypto API — the key is used locally and never uploaded.</p>
<hr />
<h2>Prerequisites</h2>
<table>
<thead>
<tr>
<th>Requirement</th>
<th>Version</th>
</tr>
</thead>
<tbody><tr>
<td>Node.js</td>
<td>20 LTS or newer</td>
</tr>
<tr>
<td>pnpm</td>
<td>latest</td>
</tr>
<tr>
<td>Git</td>
<td>any recent</td>
</tr>
</tbody></table>
<pre><code class="language-bash">node -v   # v20.x+
pnpm -v
</code></pre>
<hr />
<h2>Step 1 — Clone and install</h2>
<pre><code class="language-bash">git clone https://github.com/ezwebtools/flowpick.git
cd flowpick
pnpm install
</code></pre>
<p>The <code>postinstall</code> hook runs <code>nuxt prepare</code>, which generates type declarations and the route manifest. The first install is slower than usual because of this — expected. If it's interrupted, recover with <code>pnpm exec nuxt prepare</code>.</p>
<hr />
<h2>Step 2 — Run locally</h2>
<pre><code class="language-bash">pnpm dev
</code></pre>
<p>Visit <strong><a href="http://localhost:3000">http://localhost:3000</a></strong>. Useful routes:</p>
<ul>
<li><p><code>/m3u8-downloader</code> — HLS</p>
</li>
<li><p><code>/dash-downloader</code> — DASH</p>
</li>
<li><p><code>/docs</code> — documentation</p>
</li>
<li><p><code>/blog</code> — tutorials</p>
</li>
</ul>
<p>If you'd rather not install anything yet, the hosted tools are available at <strong><a href="https://flowpick.net/m3u8-downloader">https://flowpick.net/m3u8-downloader</a></strong> and <strong><a href="https://flowpick.net/dash-downloader">https://flowpick.net/dash-downloader</a></strong>.</p>
<blockquote>
<p>🔗 Live: <a href="https://flowpick.net">https://flowpick.net</a> · M3U8: <a href="https://flowpick.net/m3u8-downloader">https://flowpick.net/m3u8-downloader</a> · DASH: <a href="https://flowpick.net/dash-downloader">https://flowpick.net/dash-downloader</a></p>
</blockquote>
<hr />
<h2>Step 3 — Download your first stream</h2>
<h3>HLS</h3>
<ol>
<li><p>On a page serving <code>.m3u8</code>, open DevTools (F12) → <strong>Network</strong>, filter <code>m3u8</code>.</p>
</li>
<li><p>Play the video a few seconds so the manifest is requested.</p>
</li>
<li><p>Right-click the <code>.m3u8</code> request → <strong>Copy link address</strong>.</p>
</li>
<li><p>Paste it into the M3U8 Downloader and click <strong>Parse</strong>.</p>
</li>
<li><p>Select a quality and click <strong>Start Download</strong>.</p>
</li>
</ol>
<p>FlowPick downloads segments, decrypts (if AES-128), and merges. For MP4 it remuxes in-browser:</p>
<pre><code class="language-bash">ffmpeg -i input.ts -c copy output.mp4
</code></pre>
<p>No re-encoding, no quality loss, no server round-trip.</p>
<h3>DASH</h3>
<p>Paste the <code>.mpd</code> URL into the DASH Downloader, pick video and audio tracks, and FlowPick merges A/V automatically — necessary because DASH keeps the two streams separate.</p>
<hr />
<h2>Step 4 — Build and deploy</h2>
<pre><code class="language-bash">pnpm build
pnpm preview
</code></pre>
<p>The default Nitro preset is <code>cloudflare-pages-static</code>, so the build outputs static assets in <code>.output/</code>. Deploy them to any static host (Cloudflare Pages, Netlify, Vercel, or your own cloud). For a Node server, set <code>nitro.preset</code> to <code>node-server</code>, rebuild, and run <code>node .output/server/index.mjs</code>.</p>
<p><strong>Critical:</strong> the downloader routes emit two headers —</p>
<pre><code class="language-plaintext">Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
</code></pre>
<p>These enable <strong>cross-origin isolation</strong>, required for FFmpeg WASM multithreading (<code>SharedArrayBuffer</code>) and for StreamSaver large-file writes. Removing them degrades large downloads.</p>
<hr />
<h2>Step 5 — Customize</h2>
<ul>
<li><p><strong>Content</strong>: <code>content/</code> holds Markdown/YAML, validated by Zod. Edit copy or add pages; changes are live on reload.</p>
</li>
<li><p><strong>Engine</strong>: <code>app/composables/useStreamMerge.ts</code> controls concurrency (default <code>2</code>, range <code>1–8</code>), retry (exponential backoff, max <code>3</code>), and the write fallback chain.</p>
</li>
<li><p><strong>Config</strong>: <code>nuxt.config.ts</code> defines i18n (en / zh-Hans / zh-Hant / ja / ko), analytics env vars (<code>NUXT_PUBLIC_GA_ID</code>, <code>NUXT_PUBLIC_CLARITY_ID</code>), and the route headers.</p>
</li>
</ul>
<p>The engine's input contract is small and explicit:</p>
<pre><code class="language-typescript">export interface StreamMergeOptions {
  segments: ArrayBuffer[] | AsyncGenerator&lt;ArrayBuffer&gt;
  totalSegments: number
  filename: string
  outputFormat?: 'mp4' | 'ts'
  onProgress?: (progress: StreamMergeProgress) =&gt; void
  signal?: AbortSignal
}
</code></pre>
<hr />
<h2>How the engine works</h2>
<p>Two composables do the heavy lifting:</p>
<ul>
<li><p><code>useStreamMerge.ts</code> — downloads segments with a <strong>Worker Pool</strong> (shared counter, so workers never idle), decrypts AES-128 via Web Crypto, concatenates (TS) or remuxes (MP4) via FFmpeg, then writes.</p>
</li>
<li><p><code>useFFmpeg.ts</code> — loads <code>@ffmpeg/ffmpeg</code> (WASM), detects multithreading, runs merge/remux.</p>
</li>
</ul>
<p>The write strategy degrades gracefully:</p>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>When</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>FSA</td>
<td>Chrome/Edge 86+</td>
<td>Streaming, unlimited size, constant memory</td>
</tr>
<tr>
<td>StreamSaver</td>
<td>Broad support</td>
<td>Service Worker proxy, large files OK</td>
</tr>
<tr>
<td>Blob</td>
<td>Fallback</td>
<td>Memory-limited; &gt; ~1.5 GB rejected</td>
</tr>
</tbody></table>
<hr />
<h2>Caveats</h2>
<ul>
<li><p><strong>DRM (Widevine, etc.) cannot be decoded</strong> — by design.</p>
</li>
<li><p><strong>Web tool is CORS-limited</strong> — if the stream server lacks cross-origin headers, use the extension.</p>
</li>
<li><p><strong>FFmpeg WASM is ~8 MB first load</strong> (cached after).</p>
</li>
<li><p><strong>Chrome/Edge give the best large-file experience</strong>; Safari uses Blob mode.</p>
</li>
</ul>
<p>Download only content you're permitted to. Respect platform terms and local laws; keep files for personal/offline use.</p>
<hr />
<h2>Key takeaways</h2>
<ul>
<li><p>FlowPick processes everything client-side — no uploads, no accounts.</p>
</li>
<li><p>It handles HLS, DASH, audio, and images, with in-browser AES-128 decryption.</p>
</li>
<li><p>Deployment is flexible: static host or Node server, both needing the COOP/COEP headers.</p>
</li>
<li><p>The engine is small, typed, and easy to tweak.</p>
</li>
</ul>
<hr />
<h2>Wrap-up</h2>
<p>You now have the full loop: clone → run → download → build → deploy → customize. FlowPick is open source at <strong><a href="https://github.com/ezwebtools/flowpick">https://github.com/ezwebtools/flowpick</a></strong>, and the hosted tools are ready at <strong><a href="https://flowpick.net">https://flowpick.net</a></strong> (M3U8: <strong><a href="https://flowpick.net/m3u8-downloader">https://flowpick.net/m3u8-downloader</a></strong>, DASH: <strong><a href="https://flowpick.net/dash-downloader">https://flowpick.net/dash-downloader</a></strong>).</p>
<p>If you found this useful, consider starring the repo and sharing it with a teammate who's tired of sketchy parser sites.</p>
<hr />
]]></content:encoded></item></channel></rss>