프로젝트 아키텍처
이 문서는 FlowPick의 내부 설계를 깊이 이해하고 싶은 개발자를 대상으로 하며, 시스템 아키텍처, 모듈 역할, 데이터 흐름 및 핵심 설계 결정을 다룹니다.

시스템 아키텍처 개요
FlowPick는 두 가지 주요 제품 형태로 구성됩니다: 브라우저 확장 프로그램과 온라인 도구 웹사이트. 두 제품은 핵심 다운로드 엔진을 공유하지만, 미디어 검출 및 네트워크 요청 처리 방식에는 차이가 있습니다.
┌─────────────────────────────────────────────────────────┐
│ FlowPick │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ 브라우저 확장 │ │ 온라인 도구 웹사이트 │ │
│ │ │ │ │ │
│ │ · 네트워크 요청 모니터링 │ │ · M3U8 다운로더 │ │
│ │ · 미디어 자동 검출 │ │ · DASH 다운로더 │ │
│ │ · 팝업창 UI │ │ · 문서 시스템 │ │
│ │ · 크로스 오리진 요청 능력│ │ · 홈페이지 표시 │ │
│ └────────┬─────────┘ └────────────┬─────────────┘ │
│ │ │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ 핵심 다운로드 엔진 │ │
│ │ │ │
│ │ · useStreamMerge.ts │ │
│ │ · useFFmpeg.ts │ │
│ │ · 세그먼트 다운로드 및 동시성 제어 │ │
│ │ · AES-128 복호화 │ │
│ │ · 스트리밍 파일 쓰기 │ │
│ │ · 형식 변환 │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
두 가지 제품 형태의 포지셔닝:
| 차원 | 브라우저 확장 | 온라인 도구 웹사이트 |
|---|---|---|
| 대상 사용자 | 일상적인 고빈도 사용자 | 일시적 사용자, 확장 프로그램 설치 불가능한 환경 |
| 핵심 장점 | 자동 검출, 원클릭 다운로드 | 설치 불필요, 크로스 플랫폼 |
| 기술적 제약 | Manifest V3 제한 | 동일 출처 정책 제한 |
| 진입 방식 | 도구모음 아이콘 클릭 | 웹 URL 직접 접근 |
모듈 상세 설명
핵심 다운로드 엔진
다운로드 엔진은 두 개의 composable로 구성되며, app/composables/에 위치합니다.

useStreamMerge.ts
스트리밍 미디어 세그먼트의 다운로드, 복호화, 병합 및 쓰기를 담당합니다. 이는 전체 시스템에서 가장 핵심적인 모듈입니다.
주요 역할:
| 기능 | 구현 방식 |
|---|---|
| 세그먼트 동시 다운로드 | Worker Pool 모드, 공유 작업 카운터 |
| 재시도 메커니즘 | 지수 백오프, 최대 3회 |
| AES-128 복호화 | Web Crypto API |
| 세그먼트 연결 | 이진 직접 연결(TS) 또는 FFmpeg 리무싱(MP4) |
| 파일 쓰기 | FSA → StreamSaver → Blob 3단계 폴백 |
| 진행률 추적 | 슬라이딩 윈도우 속도 계산 + 남은 시간 추정 |
| 메모리 관리 | 샘플링 예측 + 임계값 확인 + 전략 선택 |
내보내기 인터페이스:
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
FFmpeg WASM의 로딩, 수명 주기 관리 및 미디어 처리 명령 실행을 담당합니다.
주요 역할:
| 기능 | 구현 방식 |
|---|---|
| WASM 로딩 | 요청 시 로딩, 멀티스레드/싱글스레드 자동 전환 지원 |
| 멀티스레드 감지 | SharedArrayBuffer 및 crossOriginIsolated 확인 |
| TS → MP4 리무싱 | ffmpeg -i input.ts -c copy output.mp4 |
| 오디오/비디오 병합 | 각각 가상 FS에 쓰고 병합 명령 실행 |
| FMP4 재구성 | DASH의 초기화 세그먼트 + 미디어 세그먼트 처리 |
| 진행률 콜백 | FFmpeg 출력의 time 정보 파싱 |
내보내기 인터페이스:
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>
}
}
페이지 모듈
M3U8 다운로더 (m3u8-downloader.vue)
HLS 스트리밍 프로토콜을 처리하는 다운로드 페이지입니다.
처리 흐름:
사용자 URL 입력
↓
fetch M3U8 내용
↓
m3u8-parser 파싱
↓
┌─ Master Playlist? ──→ 화질 목록 표시 → 사용자 선택
│
└─ Media Playlist? ──→ 세그먼트 목록 추출
↓
#EXT-X-KEY 확인(암호화 정보)
↓
키 파일 다운로드(있는 경우)
↓
동시 세그먼트 다운로드 + 복호화
↓
병합/리무싱
↓
디스크에 쓰기
**
**핵심 의존성**:
- `m3u8-parser`: M3U8 매니페스트 파싱
- `useStreamMerge`: 세그먼트 다운로드 및 병합
- `useFFmpeg`: 형식 변환
::note
HLS 프로토콜의 Master Playlist와 Media Playlist의 차이점에 대해서는 [비디오 스니핑 — HLS 스트림](/ko/docs/features/video-sniffing#hlsm3u8-流)을 참조하세요. 온라인 도구에서 M3U8 다운로더의 사용 방법에 대해서는 [온라인 도구](/ko/docs/advanced/online-tools)를 참조하세요.
::
#### DASH 다운로더 (`dash-downloader.vue`)
DASH 스트리밍 프로토콜을 처리하는 다운로드 페이지입니다.
**처리 흐름**:
사용자 URL 입력 ↓ fetch MPD 내용 ↓ mpd-parser 파싱 ↓ ContentProtection 확인(DRM) ↓ AdaptationSet 추출(비디오/오디오) ↓ 스트림 목록 표시 → 사용자 선택 ↓ 초기화 세그먼트 다운로드(있는 경우) ↓ 동시 미디어 세그먼트 다운로드 + 복호화 ↓ FMP4 재구성 / 오디오/비디오 병합 ↓ 디스크에 쓰기
**핵심 의존성**:
- `mpd-parser`: MPD 매니페스트 파싱
- `useStreamMerge`: 세그먼트 다운로드 및 병합
- `useFFmpeg`: FMP4 재구성 및 오디오/비디오 병합
::note
DASH 스트림의 검출 원리 및 ContentProtection 처리에 대해서는 [비디오 스니핑 — DASH 스트림](/ko/docs/features/video-sniffing#dashmpd-流)을 참조하세요. DRM 보호 콘텐츠의 제한 사항에 대해서는 [알려진 제한 — DRM 보호 콘텐츠](/ko/docs/troubleshooting/known-issues#drm-保护内容)를 참조하세요.
::
---
### 문서 시스템
Nuxt Content v3를 기반으로 구축되었으며, 문서는 Markdown 파일 형태로 `content/` 디렉토리에 저장됩니다.
**디렉토리 구조**:
content/zh-Hans/ ├── 1.docs/ # 문서 본체 │ ├── 1.getting-started/ # 빠른 시작 │ ├── 2.features/ # 기능 특성 │ ├── 3.advanced/ # 고급 가이드 │ ├── 5.troubleshooting/ # 문제 해결 │ └── 6.developer/ # 개발자 문서 ├── 4.changelog/ # 변경 로그 └── 6.legal/ # 법률 조항 **
네비게이션 시스템: 각 디렉토리 하위의 .navigation.yml이 해당 분류의 제목과 아이콘을 정의합니다. Nuxt Content가 모든 문서를 자동으로 수집하여 사이드바 네비게이션을 생성합니다.
문서 레이아웃: app/layouts/docs.vue가 통일된 문서 페이지 레이아웃을 제공하며, 상단 네비게이션, 사이드바 및 콘텐츠 영역을 포함합니다.
데이터 흐름
완전한 다운로드 흐름
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 매니페스트 가져오기 │ → │ 매니페스트 파싱 │ → │ 세그먼트 다운로드 │ → │ 병합 쓰기 │
└─────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
fetch() m3u8-parser Worker Pool useStreamMerge
mpd-parser AES-128 복호화 useFFmpeg
fetch() 동시 FSA/SS/Blob
**
**각 단계 상세 설명**:
| 단계 | 입력 | 처리 | 출력 | 가능한 실패 원인 |
|------|------|------|------|---------------|
| 매니페스트 가져오기 | M3U8/MPD URL | HTTP GET 요청 | 매니페스트 텍스트 | 네트워크 오류, URL 만료, 404 |
| 매니페스트 파싱 | 매니페스트 텍스트 | m3u8/mpd-parser 파싱 | 세그먼트 목록 + 메타데이터 | 비표준 형식, DRM 보호 |
| 세그먼트 다운로드 | 세그먼트 URL 목록 | Worker Pool 동시 다운로드 | ArrayBuffer 배열 | CORS, 속도 제한, token 만료 |
| 병합 쓰기 | ArrayBuffer 배열 | 연결/리무싱 + 쓰기 | 디스크 파일 | 메모리 부족, 쓰기 권한 |
::note
다운로드 엔진의 완전한 데이터 흐름(오류 분류 및 재시도 메커니즘 포함)에 대해서는 [다운로드 엔진 아키텍처 — 데이터 흐름 전경](/ko/docs/advanced/download-engine#数据流全景)을 참조하세요. 각 단계 실패 시 해결 방법에 대해서는 [일반적인 문제 해결](/ko/docs/troubleshooting/common-issues)를 참조하세요.
::
### 쓰기 전략 선택 흐름
쓰기 시작 │ ▼ File System Access API 사용 가능? │ ├── 예 → FSA 스트리밍 쓰기 사용(최적) │ · 16MB 버퍼 │ · 임의 크기 지원 │ └── 아니오 → StreamSaver 사용 가능? │ ├── 예 → StreamSaver 스트리밍 쓰기 사용 │ · Service Worker 프록시 │ · 대용량 파일 지원 │ └── 아니오 → 파일 크기 예측 │ ├── < 1.5GB → Blob 모드 │ · 전체 메모리에 로드 │ · 브라우저 다운로드 트리거 │ └── > 1.5GB → 다운로드 거부 · 브라우저 전환 안내
::note
3단계 쓰기 전략의 상세 비교(각 전략의 적용 시나리오 및 제한 사항 포함)에 대해서는 [다운로드 엔진 아키텍처 — 전략 비교 요약](/ko/docs/advanced/download-engine#策略对比总结)을 참조하세요. 각 브라우저의 FSA API 지원 현황에 대해서는 [브라우저 호환성 — 기능 지원 매트릭스](/ko/docs/advanced/browser-compatibility)를 참조하세요.
::
## 핵심 설계 결정
### FFmpeg WASM을 선택하고 서버 측 처리하지 않은 이유?
**결정**: 모든 미디어 처리를 브라우저 측에서 수행합니다.
**이유**:
- 개인 정보 보호: 사용자 파일이 서버에 업로드되지 않음
- 제로 서버 비용: 트랜스코딩 서버 클러스터 유지 불필요
- 즉시 사용 가능: 업로드 및 대기 시간 없음
- 오프라인 능력: 이론적으로 PWA 모드에서 오프라인 사용 가능
**대가**:
- 원본 FFmpeg보다 성능 낮음(약 10-20%)
- 첫 로딩 시 WASM 파일 다운로드 필요(약 8MB)
- 브라우저 메모리 제한 受限
::note
FFmpeg WASM과 원본 버전의 성능 비교 데이터에 대해서는 [알려진 제한 — FFmpeg WASM 성능](/ko/docs/troubleshooting/known-issues#ffmpeg-wasm-性能)을 참조하세요. 개인 정보 보호의 완전한 설명에 대해서는 [개인 정보 및 보안](/ko/docs/features/privacy-security)을 참조하세요.
::
### 3단계 쓰기 폴백 전략을 사용하는 이유?
**결정**: FSA → StreamSaver → Blob 우선순위 체인.
**이유**:
- FSA API가 최적의 솔루션이지만 Chrome/Edge만 지원
- StreamSaver 호환성이 더 넓지만 Service Worker에 의존
- Blob이 최후의 수단으로 모든 브라우저에서 다운로드 가능하도록 보장
**대가**:
- 3세트의 쓰기 로직 유지 필요
- 폴백 동작으로 인해 사용자 경험 불일치 가능
::note
3단계 전략의 상세 구현 및 폴백 트리거 조건에 대해서는 [다운로드 엔진 아키텍처 — 파일 쓰기 모듈](/ko/docs/advanced/download-engine#文件写入模块)을 참조하세요. 각 브라우저의 쓰기 능력 비교에 대해서는 [브라우저 호환성 — 기능 폴백 전략](/ko/docs/advanced/browser-compatibility#功能降级策略)를 참조하세요.
::
### 동시 다운로드에 Worker Pool을 사용하고 미리 분할하지 않은 이유?
**결정**: 공유 카운터 + 동적 작업 할당 사용.
**이유**:
- 세그먼트 크기가 고르지 않을 때 미리 분할하면 일부 Worker가 유휴 상태가 됨
- 공유 카운터는 모든 Worker가 작업 완료까지 지속적으로 작업하도록 보장
- 구현이 간단하며 복잡한 부하均衡 로직 불필요
::note
Worker Pool의 동시성 제어 구현 및 성능 분석에 대해서는 [다운로드 엔진 아키텍처 — 동시성 수와 성능의 관계](/ko/docs/advanced/download-engine#并发数与性能的关系)을 참조하세요. 동시성 수 설정 방법에 대해서는 [설정 참조](/ko/docs/getting-started/configuration)를 참조하세요.
::
### 확장 프로그램과 온라인 도구가 핵심 엔진을 공유하는 이유?
**결정**: `useStreamMerge`와 `useFFmpeg`를 독립 composable로 확장 API에 의존하지 않음.
**이유**:
- 코드 재사용, 두 세트의 다운로드 로직 유지 불필요
- 온라인 도구가 확장 기능의 데모 및 폴백 솔루션으로 활용 가능
- 테스트 용이: 핵심 로직을 일반 웹 페이지 환경에서 디버깅 가능
::note
확장 프로그램과 온라인 도구의 CORS 처리 차이에 대해서는 [알려진 제한 — CORS 크로스 오리GIN 제한](/ko/docs/troubleshooting/known-issues#cors-跨域限制)을 참조하세요. 두 가지 제품 형태의 기능 비교에 대해서는 [온라인 도구 — 확장과의 비교](/ko/docs/advanced/online-tools)를 참조하세요.
::
## 의존성 관계도
m3u8-downloader.vue ──────┐ ├──→ useStreamMerge.ts ──→ Web Crypto API dash-downloader.vue ──────┤ │ fetch API │ ├──→ useFFmpeg.ts ──→ @ffmpeg/ffmpeg 확장 팝업창 ──────────────┘ │ @ffmpeg/util ├──→ File System Access API ├──→ StreamSaver.js └──→ Blob API
문서 시스템 ──→ @nuxt/content ──→ Markdown 파일 └──→ @nuxt/ui ──→ Tailwind CSS **
의존성 관계 설명: