Project Architecture
This document is for developers who want to understand FlowPick's internal design in depth, covering system architecture, module responsibilities, data flow, and key design decisions.

System Architecture Overview
FlowPick consists of two main product forms: Browser Extension and Online Tool Website. Both share the core download engine but differ in media detection and network request handling.
┌─────────────────────────────────────────────────────────┐
│ FlowPick │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ Browser Extension │ │ Online Tool Website │ │
│ │ │ │ │ │
│ │ · Network request │ │ · M3U8 Downloader │ │
│ │ monitoring │ │ · DASH Downloader │ │
│ │ · Auto media │ │ · Documentation System │ │
│ │ detection │ │ · Homepage │ │
│ │ · Popup UI │ │ │ │
│ │ · Cross-origin │ │ │ │
│ │ request ability│ │ │ │
│ └────────┬─────────┘ └────────────┬─────────────┘ │
│ │ │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ Core Download Engine │ │
│ │ │ │
│ │ · useStreamMerge.ts │ │
│ │ · useFFmpeg.ts │ │
│ │ · Segmented download & │ │
│ │ concurrency control │ │
│ │ · AES-128 decryption │ │
│ │ · Streaming file write │ │
│ │ · Format conversion │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Positioning of Two Product Forms:
| Dimension | Browser Extension | Online Tool Website |
|---|---|---|
| Target users | Daily high-frequency users | Occasional users, environments where extension cannot be installed |
| Core advantage | Auto-detection, one-click download | No installation needed, cross-platform |
| Technical constraints | Manifest V3 limitations | Same-origin policy restrictions |
| Access method | Click toolbar icon | Directly visit web URL |
Module Details
Core Download Engine
Download engine consists of two composables located in app/composables/.

useStreamMerge.ts
Responsible for downloading, decrypting, merging, and writing media segments. This is the most core module of the entire system.
Main Responsibilities:
| Feature | Implementation |
|---|---|
| Concurrent segment download | Worker Pool pattern with shared task counter |
| Retry mechanism | Exponential backoff, max 3 retries |
| AES-128 decryption | Web Crypto API |
| Segment concatenation | Binary direct concatenation (TS) or FFmpeg remuxing (MP4) |
| File writing | FSA → StreamSaver → Blob three-tier fallback |
| Progress tracking | Sliding window speed calculation + ETA estimation |
| Memory management | Sampling estimation + threshold check + strategy selection |
Exported Interface:
export interface StreamMergeOptions {
segments: ArrayBuffer[] | AsyncGenerator<ArrayBuffer>
totalSegments: number
filename: string
outputFormat?: 'mp4' | 'ts'
onProgress?: (progress: StreamMergeProgress) => void
signal?: AbortSignal
}
export interface StreamMergeProgress {
phase: 'downloading' | 'merging'
percent: number
downloadedBytes: number
totalBytes: number
speed: string
eta: string
}
useFFmpeg.ts
Responsible for FFmpeg WASM loading, lifecycle management, and media processing command execution.
Main Responsibilities:
| Feature | Implementation |
|---|---|
| WASM loading | On-demand loading, supports multithreading/single-thread auto-switching |
| Multithreading detection | Check SharedArrayBuffer and crossOriginIsolated |
| TS → MP4 remuxing | ffmpeg -i input.ts -c copy output.mp4 |
| Audio/video merge | Write to virtual FS separately, execute merge command |
| FMP4 reassembly | Handle DASH initialization segments + media segments |
| Progress callback | Parse time info from FFmpeg output |
Exported Interface:
export const useFFmpeg = () => {
return {
ffmpeg: Ref<FFmpegType | null>
loaded: Ref<boolean>
loading: Ref<boolean>
error: Ref<string | null>
load: () => Promise<void>
mergeToMP4: (options: MergeOptions) => Promise<ArrayBuffer>
mergeAudioVideo: (options: MergeAudioVideoOptions) => Promise<ArrayBuffer>
mergeFmp4: (options: MergeFmp4Options) => Promise<ArrayBuffer>
}
}
Page Modules
M3U8 Downloader (m3u8-downloader.vue)
Handles HLS streaming protocol download page.
Processing Flow:
User inputs URL
↓
fetch M3U8 content
↓
m3u8-parser parsing
↓
┌─ Master Playlist? ──→ Show quality list → User selects
│
└─ Media Playlist? ──→ Extract segment list
↓
Check #EXT-X-KEY (encryption info)
↓
Download key file (if present)
↓
Concurrent segment download + decrypt
↓
Merge/remux
↓
Write to disk
Key Dependencies:
m3u8-parser: Parse M3U8 manifestuseStreamMerge: Segment download & merginguseFFmpeg: Format conversion
DASH Downloader (dash-downloader.vue)
Handles DASH streaming protocol download page.
Processing Flow:
User inputs URL
↓
fetch MPD content
↓
mpd-parser parsing
↓
Check ContentProtection (DRM)
↓
Extract AdaptationSet (video/audio)
↓
Show stream list → User selects
↓
Download initialization segments (if present)
↓
Concurrent media segment download + decrypt
↓
FMP4 reassembly / A/V merge
↓
Write to disk
Key Dependencies:
mpd-parser: Parse MPD manifestuseStreamMerge: Segment download & merginguseFFmpeg: FMP4 reassembly & A/V merging
Documentation System
Built on Nuxt Content v3, documentation stored as Markdown files under content/ directory.
Directory Structure:
content/zh-Hans/
├── 1.docs/ # Main documentation
│ ├── 1.getting-started/ # Getting Started
│ ├── 2.features/ # Features
│ ├── 3.advanced/ # Advanced Guide
│ ├── 5.troubleshooting/ # Troubleshooting
│ └── 6.developer/ # Developer Documentation
├── 4.changelog/ # Changelog
└── 6.legal/ # Legal Terms
Navigation System: Each directory's .navigation.yml defines category title and icon. Nuxt Content automatically collects all documents and generates sidebar navigation.
Document Layout: app/layouts/docs.vue provides unified document page layout, including top navigation, sidebar, and content area.
Data Flow
Complete Download Flow
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Get Manifest│ → │Parse Manifest│ → │Download Segments│ → │Merge & Write│
└─────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
fetch() m3u8-parser Worker Pool useStreamMerge
mpd-parser AES-128 decrypt useFFmpeg
fetch() concurrent FSA/SS/Blob
Detailed Description of Each Stage:
| Stage | Input | Processing | Output | Possible Failure Reasons |
|---|---|---|---|---|
| Get Manifest | M3U8/MPD URL | HTTP GET request | Manifest text | Network error, URL expired, 404 |
| Parse Manifest | Manifest text | m3u8/mpd-parser parsing | Segment list + metadata | Non-standard format, DRM protection |
| Download Segments | Segment URL list | Worker Pool concurrent download | ArrayBuffer array | CORS, rate limiting, token expired |
| Merge & Write | ArrayBuffer array | Concatenation/remuxing + write | Disk file | Insufficient memory, write permission |
Write Strategy Selection Flow
Start writing
│
▼
File System Access API available?
│
├── Yes → Use FSA streaming write (optimal)
│ · 16MB buffer
│ · Supports any size
│
└── No → StreamSaver available?
│
├── Yes → Use StreamSaver streaming write
│ · Service Worker proxy
│ · Supports large files
│
└── No → Estimate file size
│
├── < 1.5GB → Blob mode
│ · Load all into memory
│ · Trigger browser download
│
└── > 1.5GB → Reject download
· Prompt to switch browser
Key Design Decisions
Why Choose FFmpeg WASM Instead of Server-side Processing?
Decision: All media processing completed client-side in browser.
Reasons:
- Privacy protection: User files not uploaded to server
- Zero server cost: No need to maintain transcoding server cluster
- Instant availability: No need to wait for upload and queuing
- Offline capability: Theoretically usable offline in PWA mode
Trade-offs:
- Performance lower than native FFmpeg (~10-20%)
- First load requires downloading WASM files (~8MB)
- Subject to browser memory limitations
Why Use Three-Tier Write Fallback Strategy?
Decision: Priority chain of FSA → StreamSaver → Blob.
Reasons:
- FSA API is optimal solution but only supported by Chrome/Edge
- StreamSaver has wider compatibility but depends on Service Worker
- Blob is fallback solution ensuring all browsers can download
Trade-offs:
- Need to maintain three sets of write logic
- Fallback behavior may lead to inconsistent user experience
Why Does Concurrent Download Use Worker Pool Instead of Pre-segmentation?
Decision: Use shared counter + dynamic task assignment.
Reasons:
- When segment sizes are uneven, pre-segmentation causes some Workers to idle
- Shared counter ensures all Workers keep working until task completion
- Simple implementation without complex load balancing logic
Why Do Extension and Online Tools Share Core Engine?
Decision: useStreamMerge and useFFmpeg as independent composables, not dependent on extension APIs.
Reasons:
- Code reuse, avoid maintaining two sets of download logic
- Online tools can serve as demo and fallback for extension features
- Easy testing: core logic debuggable in regular web environment
Dependency Graph
m3u8-downloader.vue ──────┐
├──→ useStreamMerge.ts ──→ Web Crypto API
dash-downloader.vue ──────┤ │ fetch API
│ ├──→ useFFmpeg.ts ──→ @ffmpeg/ffmpeg
Extension Popup ──────────┘ │ @ffmpeg/util
├──→ File System Access API
├──→ StreamSaver.js
└──→ Blob API
Documentation ──→ @nuxt/content ──→ Markdown Files
└──→ @nuxt/ui ──→ Tailwind CSS
Dependency Relationship Explanation:
| Dependency | Type | Description |
|---|---|---|
useStreamMerge → useFFmpeg | Optional dependency | Only needed for MP4 output or A/V merging |
useStreamMerge → Web Crypto API | Conditional dependency | Only needed for AES-128 encrypted streams |
useStreamMerge → FSA / StreamSaver / Blob | Mutually exclusive selection | Select one write method by priority |
Page → useStreamMerge | Direct dependency | All download pages depend on core engine |
Documentation → @nuxt/content | Framework dependency | Nuxt Content module |
Extension Architecture
Structure of extension part (not in this repository):
flowpick-extension/
├── manifest.json # Manifest V3 configuration
├── background/
│ └── service-worker.js # Service Worker (network monitoring)
├── popup/
│ ├── popup.html # Popup window
│ ├── popup.js # Popup window logic
│ └── popup.css # Popup window styles
├── content/
│ └── content.js # Content script (page injection)
└── assets/
└── icons/ # Extension icons
Extension monitors network requests via webRequest API, detects media resources, then sends URL list to popup via message passing. Popup reuses online tool's core download logic.
Extension Communication Flow:
Content Script ←→ Background Service Worker ←→ Popup UI
│ │ │
Page DOM access Network request monitor User interaction UI
Media element detect M3U8/MPD filtering Download trigger & manage
Related Documentation
- Contributing Guide — Development environment setup, code standards, and PR workflow
- Download Engine Architecture — Complete technical implementation of core download engine
- Video Sniffing — HLS/DASH manifest parsing and encryption detection
- Format Conversion — FFmpeg WASM engine and output formats
- Browser Compatibility — Browser API support and degradation strategies
- Online Tools — Online tool usage and limitations
- Configuration Reference — Concurrency, filter, and other settings
- Installation Guide — Extension installation and activation
- Known Limitations — Current version technical limitations and edge cases
- Common Issues Troubleshooting — Diagnostic tools and troubleshooting steps
- Privacy & Security — Permission explanation and privacy protection