QR 스캐너 게이트 수정: DeviceType 휴리스틱 오판(실기기에서 스캐너 미표시) — iOS 시뮬레이터만 폴백, Android 는 항상 스캐너

This commit is contained in:
Yun Chan 2026-08-16 18:32:41 +09:00
parent 0cb16c8830
commit bbbd83fe58
6 changed files with 5 additions and 1 deletions

View file

@ -1,246 +0,0 @@
# Video Downloader — 모바일 연동 + 모바일 앱 계획서 (v1)
> **목표**: Windows 비디오 다운로더(내장 브라우저)에 **모바일 앱을 만들고 PC↔모바일을 연동**한다. 핵심 질문 — "모바일에서 이 앱과 무엇을 하게 할 것인가?"에 대한 조사 기반 답변.
>
> **작성일**: 2026-08-15 · **관련 문서**: `BROWSER_FEATURES_PLAN.md` (v2, 데스크톱 브라우저 기능) · `DEPLOYMENT_PLAN.md` (Win/macOS/Linux/모바일 배포 체계)
>
> **조사 방법론**: 4개 전문 조사팀 병렬 심층 조사 — ① 모바일 브라우저 UX(Chrome/Safari/Firefox/Samsung/Brave 모바일) ② PC↔모바일 연동 패턴(20여 개 제품: Chrome Send Tab, Handoff, Phone Link, LocalSend, KDE Connect, Jellyfin 등) ③ 모바일 영상 생태계(iOS/Android 제약, 다운로더 앱 생존 모델, 미디어 서버) ④ 기술 스택 검증(**실제 코드베이스 grep 포함**). 총 73회 웹 검색 + 공식 문서 열람.
---
## 0. 핵심 결론 (Executive Summary)
1. **제품 형태 = "컴패니언 앱" 우선**. PC가 일꾼(추출·다운로드), 폰은 리모컨+뷰어. 이는 Jellyfin/VLC 리모컨/Chromecast 모델로 검증된 구조이며, 양쪽 앱스토어 정책 리스크도 최소화함.
- 폰에서: ① 링크 보내 PC가 받게 하기 ② 다운로드 큐 원격 제어(진행률 실시간) ③ PC 라이브러리 스트리밍 시청 ④ 완료된 영상 폰으로 저장(오프라인)
2. **아키텍처 = 클라우드 없는 LAN 직결**. 데스크톱 앱에 Kestrel 서버를 내장(REST + SignalR), QR 코드로 페어링(주소+토큰 동시 전달). LocalSend/KDE Connect가 증명한 계정 없는 지속 가능한 모델. 원격 접근은 자체 릴레이 대신 Tailscale/VPN 문서화.
3. **기술 스택 = .NET MAUI 10 (net10.0-android/ios)**. 기존 `VideoDownloader.Core`(net8.0) 엔진을 **~90% 그대로 재사용** — 모바일 비호환 지점은 단 4곳(§4 검증 표). Flutter/네이티브는 Core 재사용이라는 이 프로젝트의 존재 이유를 포기해야 하므로 기각. Avalonia는 데스크톱 실험용 유지.
4. **플랫폼 전략 = Android 먼저(APK/GitHub 배포), iOS는 컴패니언 프레이밍**.
- **Android**: 자유도 최대 — 나중에 독립 다운로더 모드 추가 가능(YoutubeExplode 순수 C#).
- **iOS**: WebKit 강제 사실상 지속 + **Apple이 Brave의 오프라인 영상 저장을 금지한 전례** → 기기 내 추출 코드 배제, "내 PC 라이브러리 원격 시청" 프레이밍만.
5. **제로 설치 웹 컴패니언 겸비**: 데스크톱 앱이 폰 브라우저용 페이지를 직접 서빙 → 앱 설치 없이 QR 찍으면 즉시 큐 모니터링 가능(1차 검증 수단).
---
## 1. 조사 결과 종합
### 1.1 모바일 브라우저 UX 관례 (앱 UI의 기준선)
| 관례 | 내용 | 출처 |
|---|---|---|
| **하단 주소창 기본** | Chrome 2025 정식 탑재(롱프레스로 전환), Safari/iOS 26도 상하단 선택. 단 Chrome의 실수(탭 버튼 숨김)는 반면교사 — 하단 바에 탭 버튼 유지 | Chrome/Safari |
| **전체 화면 탭 그리드** | 카드 썸네일 + 스와이프 닫기 + 탭 검색, 탭 수 배지 | 전 브라우저 |
| **제스처** | 화면 엣지 스와이프=뒤로/앞으로, 주소창 가로 스와이프=탭 전환, 당겨서 새로고침(비활성화 가능), 툴바 아래로 스와이프=몰입 모드 | Chrome/Safari/Samsung |
| **다운로드 관리자** | ⋮ 메뉴 속 풀페이지 화면(데스크톱 셸프와 다름). **우리 앱은 다운로드가 본업이므로 하단 바 전용 슬롯 + 감지 배지로 승격** | Chrome Android |
| **영상 감지 UI** | **Samsung Video Assistant**(영상 위 오버레이 → 팝업 플레이어), **Brave Playlist**(탭 아이콘 → 재생목록 추가) — 데스크톱 감지 배너의 모바일 등가물 | Samsung/Brave |
| **리더/다크** | URL 옆 아이콘(Safari 배치), 시스템 테마 추종 + 강제 다크 토글(Firefox Night Mode) | Safari/Firefox |
### 1.2 PC↔모바일 연동 — 제품별 매트릭스 (20개 제품 조사)
| 제품 | 전송물 | 페어링 | 구조 |
|---|---|---|---|
| Chrome Send Tab | 탭/링크 | **계정**(클라우드) | 클라우드 동기화+푸시 |
| Safari Handoff | 앱 상태 통째로 | iCloud+BLE 근접 | P2P Wi-Fi(AWDL) |
| Phone Link | 알림/SMS/사진 | BT+MS 계정 | 하이브리드 |
| Firefox Send Tab | 탭 | **QR 페어링**(FxA) | **E2E 암호화 릴레이** |
| **LocalSend** | 파일/텍스트 | 같은 Wi-Fi+PIN | **순수 LAN, 계정 없음** |
| **KDE Connect** | 파일/클립보드/알림/입력 | LAN+TLS 인증서 확인 | **순수 LAN** |
| **Jellyfin** | 라이브러리/제어 | 서버 URL+API 키 | **자체 호스팅 REST+WebSocket** |
| VLC 리모컨 | 재생 제어 | 없음(HTTP 인터페이스) | 순수 LAN |
| Chromecast | 미디어 URL 위임 | mDNS | **폰=리모컨만, 미디어는 직접** |
| Intel Unison | 파일/SMS/알림 | 계정 | **2025-06 서비스 종료** ⚠️ |
**도출된 원칙**:
- 계정 기반 클라우드 동기화(Chrome/Edge)는 인프라+유지 비용 — 인디 규모에서 Unison처럼 죽는다. **계정 없는 LAN 모델이 지속 가능한 니치**.
- **QR 페어링 = 발견+키 교환을 동시에** 해결하는 최고의 UX 트릭(WhatsApp Web/Firefox). `http://<ip>:<port>/#t=<토큰>`.
- LocalSend 보안 모델 차용: 자가서명 인증서 지문 양쪽 화면 표시(목측 검증), 선택적 PIN, 전송 전 수락 프롬프트("폰이 다운로드 3건 요청 — 수락?").
- **크로미캐스트 교훈**: 폰은 URL/제어 패킷만 보내고 미디어 바이트를 프록시하지 않는다. 폰 앱은 큐 대상 **stateless** 설계(앱을 꺼도 PC 다운로드는 계속).
- 미러링하지 말 것: 알림 미러링(특권+유지비), BLE 근접(데스크톱 전파 신뢰성 낮음), 상시 클립보드 동기화(프라이버시 표면).
### 1.3 모바일 영상 생태계 — 플랫폼 제약표
| 항목 | iOS | Android |
|---|---|---|
| 다운로더 정책 | 5.2.2 위반 시 리젝. **Brave Playlist 오프라인 저장 금지 전례**. 생존 모델 = "파일 관리자+브라우저"(Documents by Readdle) 프레이밍 | Play도 실질 금지(TubeMate 퇴출). **탈출구 = APK/F-Droid/GitHub 배포**(NewPipe·Seal·YTDLnis 전부) |
| 백그라운드 다운로드 | `NSURLSession` 백그라운드(앱 종료해도 계속, 강제종료 시 예외) | FGS `dataSync`(~6h 제한·폐지 예정) → **WorkManager가 정답**(YTDLnis 방식) |
| 백그라운드 재생/PiP | Background Modes + AVAudioSession `.playback` | `mediaPlayback` FGS + 상시 알림 |
| 파일 저장 | 샌드박스만. 사진 보관함/Files 내보내기 | MediaStore.Downloads(자기 파일은 권한 불필요) |
| 스트리밍 프로토콜 | **AVPlayer = HLS만, DASH 불가** | ExoPlayer/Media3 = HLS+DASH 네이티브 |
| yt-dlp 실행 | **불가능**(서브프로세스 금지) | 가능(Termux제 Python 번들) — 단 GPL, Play 배포 불가 |
- **미디어 서버 참고**: Jellyfin(REST+WebSocket, 다운로드/포지션 동기화), **Streamyfin**(클라이언트 측 HLS→로컬 파일 변환으로 "다운로드" 구현 — 재생 가능=다운로드 가능 구조), Plex(코덱 호환 시 원본 직접, 아니면 트랜스코드).
- **오프라인 라이브러리 UX 기대치**(Netflix/Spotify): 전용 다운로드 탭, 화질/크기 선택기+저장공간 대시보드, 백그라운드 큐, 재시작 생존.
- **수요 배경**: YouTube가 2026년 비프리미엄 백그라운드 재생 루트를 봉쇄 중 → "내 라이브러리 무제한 백그라운드/PiP"는 진짜 차별점.
### 1.4 기술 스택 검증 (실제 코드 grep 기반)
**스택 비교 평결**:
| 스택 | Core 재사용 | 모바일 생태계 | 평결 |
|---|---|---|---|
| **.NET MAUI 10** | ~100% (컴패니언+독립 모두) | SignalR 공식 지원, ZXing.Net.Maui, MediaElement | ✅ **채택** |
| Avalonia 12 모바일 | 동일 | 얇음(QR 스캐너/플레이어 없음, 네이티브 직접 붙여야) | 데스크톱 실험 유지 |
| Flutter | 0% (Dart 재작성) | 최고 | 기각 — Core 재사용 포기 |
| Kotlin/Swift 네이티브 | 0% | 최고 | 기각 — 코드 2벌 |
**버전 주의**: MAUI 8/9는 이미 지원 종료 → 신규 MAUI 앱은 **.NET 10 대상 필수**. 다행히 `net10.0-android`는 기존 `net8.0` Core 어셈블리를 그대로 참조 가능.
**`VideoDownloader.Core` 모바일 공유 분석 (파일:라인 검증 완료)**:
| 상태 | 위치 | 내용 |
|---|---|---|
| ✅ 그대로 사용 | `Hls/HlsDownloader`(병합 호출 제외), `M3u8Parser`, `Dash/DashParser·Models`, `Models/*`, `Social/CookieRecord·FormatOption`, `Util/FileNameUtil` | HttpClient 기반 — 모바일 완전 호환 |
| ⚠️ 추상화 필요 | `Social/YtDlpRunner.cs:131-181`, `DenoRunner.cs` | `Process.Start` → `IProcessRunner`/`IToolLocator` 인터페이스로 추출 |
| ⚠️ 추상화 필요 | `Hls/HlsDownloader.cs:237`, `Dash/DashDownloader.cs:71` | ffmpeg 서브프로세스 → `IMediaMuxer` |
| ⚠️ 추상화 필요 | `Config/AppConfig.cs` | `SpecialFolder.MyVideos` → 플랫폼 경로 주입 |
→ **추상화 4곳만 하면 Core ~90% 공유.** 이 리팩터는 BACKLOG의 Avalonia 이관 로드맵 2단계(공유 리팩터)와 **동일한 작업** — 한 번의 리팩터로 모바일+크로스플랫폼 모두 준비됨 (시너지).
**Android 독립 모드용 YouTube**: Python 번들 대신 **YoutubeExplode**(순수 C#, Tyrrrz의 YoutubeDownloader 15.8k★가 사용) — MAUI 안에서 동작.
---
## 2. 유즈케이스 정의 (컴패니언 앱)
| # | 유즈케이스 | 흐름 | 근원 패턴 |
|---|---|---|---|
| U1 | **링크 → PC로 받기** | 폰 공유 시트(YouTube/웹페이지) → "PC에서 받기" → PC 대기열 즉시 추가 + 수락 프롬프트 | Chrome Send Tab + LocalSend 수락 프롬프트 |
| U2 | **큐 원격 제어** | 큐 목록 실시간(진행률/속도), 일시정지/재개/취소/재시도 | Jellyfin 리모컨 |
| U3 | **라이브러리 스트리밍** | PC 저장 폴더 그리드(썸네일+메타) → 탭하여 시청(Range HTTP 스트리밍, 이어보기 위치 양방향 동기화) | Jellyfin/Streamyfin |
| U4 | **폰으로 저장(오프라인)** | 라이브러리 항목 다운로드 → 앱 오프라인 라이브러리(Neflix식 다운로드 탭) | Plex Downloads |
| U5 | **완료 알림** | PC에서 다운로드 완료 → 폰 푸시(연결 중일 때) | — |
| U6 | **웹 컴패니언(제로 설치)** | 폰 카메라로 QR → 폰 브라우저에서 바로 큐/라이브러리 열람 | PairDrop/Jellyfin 웹 |
| U7 | (후기) **Android 독립 다운로더** | PC 없이 폰에서 직접 HLS/DASH/YouTube 받기 | YTDLnis/YoutubeExplode |
---
## 3. 아키텍처 설계
```
┌────────────────── Windows 앱 (WPF) ──────────────────┐
│ WebView2 브라우저 + 감지 DownloadManager (Core) │
│ ▲ │
│ ┌───────────────┴────────────────┐ │
│ VideoDownloader.Server (신규 클래스 라이브러리) │
│ · Kestrel (동적 포트, ListenAnyIP) │
│ · Bearer 페어링 토큰 (256bit, QR로 배포) │
│ · mDNS 광고 (_videodl._tcp, Makaretu.Dns) │
│ · 정적 웹 컴패니언 페이지 서빙 (제로 설치 클라이언트) │
└──────┬───────────────┬──────────────┬────────────────┘
│ LAN HTTP(S) │ SignalR │ Range HTTP
┌──────▼─────┐ ┌──────▼─────┐ ┌─────▼──────┐
│ MAUI 앱 │ │ 폰 브라우저 │ │ 미디어 │
│ (Android→ │ │ (웹 컴패니언)│ │ 재생 │
│ iOS) │ └────────────┘ └────────────┘
└────────────┘
```
**API 설계안** (LocalSend 프로토콜 + Jellyfin 참고):
- `POST /api/pair` — 토큰 검증 + 기기명 등록
- `POST /api/queue` — URL 추가(U1; 데스크톱 수락 프롬프트 연동)
- `GET /api/queue` · `POST /api/queue/{id}/pause|resume|cancel|retry` — 큐 제어(U2)
- `GET /api/library` (+`/{id}/thumb`) — 메타데이터 카탈로그(U3; ffprobe/FFMpegCore 썸네일, 사이드카 캐시)
- `GET /stream/{id}` — MP4 Range 요청(206) 스트리밍(U3; `Results.File(enableRangeProcessing:true)`)
- `GET /file/{id}` — 폰 저장용 다운로드(U4)
- `/hubs/queue` — SignalR 진행률/로그 푸시(U2/U5; v1은 2초 폴링으로도 가능)
- `POST /api/position/{id}` — 이어보기 위치 동기화(U3)
**보안 모델**: 페어링 토큰(Bearer, 상수시간 비교) + 재페어링 시 재생성. LAN HTTP는 iOS ATS `NSAllowsLocalNetworking` 예외로 허용. 원격(외부망)은 자체 릴레이 대신 **Tailscale 문서화**(Syncthing 커뮤니티 표준 패턴).
**데스크톱 UI 연결점**: 설정에 "모바일 연동" 섹션 → QR 다이얼로그(QRCoder) + 페어링된 기기 목록 + 원격 액세스 토글. 브라우저 계획(v2)의 명령 팔레트에 "기기로 보내기" 액션 추가 가능.
---
## 4. 모바일 앱 기능 목록 (Tier)
### Tier M1 — 최소 컴패니언 (Android APK)
- [ ] QR 스캔 페어링(ZXing.Net.Maui) + 수동 IP:포트 폴백
- [ ] 큐 목록 + 실시간 진행률(SignalR or 폴링) + 일시정지/재개/취소/재시도
- [ ] 공유 타겟: 다른 앱에서 URL 공유 → "PC에서 받기" (U1)
- [ ] 링크 붙여넣기 직접 추가
- [ ] 웹 컴패니언 페이지(데스크톱이 서빙) — 앱 없이 검증용
### Tier M2 — 라이브러리 + 시청
- [ ] 라이브러리 그리드(썸네일·제목·해상도·크기·추가일)
- [ ] MediaElement 스트리밍 재생(Range HTTP) — 백그라운드 오디오
- [ ] 이어보기 위치 동기화
- [ ] 폰으로 다운로드(오프라인 저장) + 다운로드 탭(Neflix식) + Android 진행 알림
- [ ] Android 재생중 미디어 알림(FFS mediaPlayback)
### Tier M3 — iOS 확장
- [ ] net10.0-ios 타겟 + Info.plist 키 3종(ATS/로컬네트워크/Bonjour)
- [ ] TestFlight 배포, "내 PC 라이브러리 리모컨" 프레이밍 (기기 내 추출 코드 엄격 배제)
### Tier M4 — Android 독립 다운로더 (선택, PC 없는 모드)
- [ ] Core HLS/DASH/직접 다운로더 + 모바일 muxer 어댑터
- [ ] YoutubeExplode로 YouTube 직접 수신
- [ ] WorkManager 백그라운드 큐 + 재시작 생존
- [ ] 내장 브라우저 탭(하단 주소창·탭 그리드·제스처 관례 준수, §1.1) + 감지 배지
- [ ] 저장: MediaStore.Downloads
### 시기상조/미차용 (명시적 제외)
- 클라우드 동기화/계정, 알림 미러링, 클립보드 동기, BLE 근접 전송, 자체 릴레이 서버, iOS 기기 내 yt-dlp(기술적·정책적 불가)
---
## 5. 로드맵 (의존성 순)
### Phase M0 — 데스크톱 준비 (필수 선행, 1~2주)
- [ ] **Core 추상화 리팩터**: `IProcessRunner`·`IToolLocator`·`IMediaMuxer`·경로 제공자 인터페이스 추출 (데스크톱 구현은 무변화, 기존 4개 프로젝트 무영향. BACKLOG Avalonia 로드맵 2단계와 동일 작업)
- [ ] **`VideoDownloader.Server` 신규 라이브러리**: Kestrel 내장 + 토큰 인증 + `/api/pair`·`/api/queue` + SignalR 허브
- [ ] WPF 설정에 QR 페어링 다이얼로그(QRCoder) + mDNS 광고(Makaretu.Dns)
- [ ] **검증 관문**: 폰 브라우저로 QR 스캔 → 웹 컴패니언에서 큐 조회/추가 동작 (앱 개발 전 서버 완성 입증)
- ⚠️ git 아님 → `git init` 후 착수 (브라우저 계획 Phase A와 동일 전제)
### Phase M1 — Android 컴패니언 앱 (2~3주)
- MAUI 10(net10.0-android) 신규 프로젝트 `VideoDownloader.Mobile` + Core 참조
- Tier M1 전체. **APK/GitHub Releases 배포** (Play 정책 회피 — YTDLnis 선례)
### Phase M2 — 라이브러리/시청 (2~3주)
- 데스크톱: ffprobe 메타데이터 + 썸네일 캐시, `/api/library`·`/stream/{id}`(Range)
- 앱: Tier M2 전체
### Phase M3 — iOS (1~2주 + 심사)
- Tier M3. TestFlight 우선.
### Phase M4 — Android 독립 모드 (선택, M2 이후 언제든)
- Tier M4. 브라우저 계획(BROWSER_FEATURES_PLAN v2)의 탭 아키텍처 경험을 모바일 WebView UI에 재활용.
**브라우저 계획과의 순서 권고**: 데스크톱 브라우저 기능(탭 등)과 모바일은 독립 트랙. 다만 **Core 추상화(M0-1)만 먼저** 해두면 양쪽이 자유로워짐. 서버(M0-2)는 브라우저 작업과 무관하게 병행 가능.
---
## 6. 리스크 & 대응
| 리스크 | 대응 |
|---|---|
| MAUI 품질(iOS 성능/바인딩 이슈 보고) | 컴패니언 규모(리스트·플레이어·스캐너)는 수용 가능한 범위. Android 먼저 출시해 리스크 분산 |
| iOS 5.2.2 리젝 (다운로더 오인) | 기기 내 추출 코드 배제 + "내 PC 리모컨" 프레이밍 + Brave 전례 문서화. TestFlight로 시작 |
| Windows 방화벽 첫 프롬프트 | 최초 비로컬 바인딩 시점에 안내 UI("허용" 가이드) |
| 멀티캐스트 차단 네트워크(기업/캠퍼스) | QR 페어링이 주 경로(발견 불필요) + 로컬 IP 프로브 폴백(LocalSend 방식) |
| iOS 로컬 네트워크 권한 거부 시 무음 실패 | 권한 거부 감지 → 설정 안내 화면 |
| Play 정책(독립 모드) | APK/GitHub 배포로 회피. Play 출시 시 사이트 특화 추출 코드 제거 빌드 |
| AVPlayer DASH 불가 | 컴패니언은 MP4/HLS 스트리밍만(앱 원본은 대부분 MP4). DASH 원본은 데스크톱에서 필요 시 ffmpeg 실시간 트랜스코드(후순위, Jellyfin 계층 구조 참고) |
| yt-dlp 인자 인젝션(YTDLnis RCE 전례, SonarSource) | U1 공유 URL 화이트리스트 검증 + 인자 배열 방식(이미 데스크톱 `OpenInExplorer`에서 동일 교훈 적용 완료) |
---
## 7. 성공 기준 (완료 정의)
- [ ] PC 앱 켠 상태에서 폰으로 QR 스캔 → 설치 없는 웹 컴패니언에서 큐 조회/URL 추가 성공 (M0)
- [ ] Android 앱: 공유 시트로 YouTube 링크 → PC 대기열 자동 추가 + PC 수락 → 다운로드 시작 (M1)
- [ ] Android 앱: 큐 진행률 실시간 갱신 + 일시정지/재개/취소 동작 (M1)
- [ ] 폰에서 PC 라이브러리 그리드 탐색 → 스트리밍 재생(탐색/시크 포함) → 앱 종료 후 재개 시 이어보기 (M2)
- [ ] 라이브러리 항목 폰 저장 → 오프라인 재생 (M2)
- [ ] iOS TestFlight 빌드 시동 + 페어링 + 큐 조회 (M3)
- [ ] `dotnet build` 전체 0 오류 — 기존 4개 프로젝트(WPF/Avalonia/Cli/Core) 회귀 없음 (M0~전체)
- [ ] 외부망에서 연결 시도 → 명확한 "같은 네트워크 필요/Tailscale 안내" 메시지 (UX)
---
## 출처 (핵심)
- **연동 패턴**: [LocalSend 프로토콜 v2.2](https://github.com/localsend/protocol) · [KDE Connect](https://github.com/kde/kdeconnect-kde) · [Firefox FxA 페어링 아키텍처](https://mozilla.github.io/ecosystem-platform/explanation/pairing-flow-architecture) · [Mozilla Sync E2E](https://hacks.mozilla.org/2018/11/firefox-sync-privacy/) · [Syncthing P2P 분석](https://advancedweb.hu/how-syncthing-provides-secure-file-syncing-without-sharing-your-files-with-a-third-party/) · [Intel Unison 종료](https://www.pcworld.com/article/2836520/intels-excellent-unison-pc-to-phone-app-shuts-down-for-good.html)
- **모바일 브라우저 UX**: [Chrome 하단 주소창](https://blog.google/products-and-platforms/products/chrome/address-bar-position-change/) · [Samsung Video Assistant](https://r2.community.samsung.com/t5/Tech-Talk/Samsung-Internet-web-browser-Video-Assistant/td-p/5923119) · [Brave Playlist](https://brave.com/playlist/) · [iOS 오프라인 금지 공지](https://community.brave.app/t/notice-playlist-offline-video-on-ios-disallowed-by-apple/654965)
- **플랫폼 제약**: [Apple 5.2.2 가이드라인](https://developer.apple.com/app-store/review/guidelines/) · [Android 14 FGS 타입](https://developer.android.com/about/versions/14/changes/fgs-types-required) · [iOS 백그라운드 다운로드](https://developer.apple.com/documentation/foundation/downloading-files-in-the-background) · [TN3179 로컬 네트워크 프라이버시](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy) · [AVPlayer HLS-only](https://mux.com/articles/hls-vs-dash-what-s-the-difference-between-the-video-streaming-protocols)
- **생태계**: [youtubedl-android](https://github.com/yausername/youtubedl-android) · [YTDLnis](https://github.com/deniscerri/ytdlnis) · [YTDLnis 인자 인젝션 RCE](https://www.sonarsource.com/blog/ytdlnis-argument-injection-rce/) · [Streamyfin](https://github.com/streamyfin/streamyfin) · [Jellyfin 트랜스코딩](https://jellyfin.org/docs/general/post-install/transcoding/) · [Documents by Readdle](https://support.readdle.com/documents/built-in-browser-download-manager/documents-internal-web-browser)
- **기술 스택**: [MAUI 지원 정책](https://dotnet.microsoft.com/en-us/platform/support/policy/maui) · [Rick Strahl: 데스크톱 내장 ASP.NET 서버](https://weblog.west-wind.com/posts/2023/Nov/27/Embedding-a-minimal-ASPNET-Web-Server-into-a-Desktop-Application) · [SignalR 지원 플랫폼](https://learn.microsoft.com/en-us/aspnet/core/signalr/supported-platforms) · [ZXing.Net.Maui](https://github.com/redth/ZXing.Net.Maui) · [MAUI MediaElement](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/views/mediaelement) · [Makaretu.Dns.Multicast](https://richardschneider.github.io/net-mdns/articles/sd.html) · [QRCoder](https://github.com/codebude/QRCoder) · [YoutubeExplode](https://github.com/tyrrrz/youtubeexplode) · [Kestrel Range 처리](https://github.com/dotnet/aspnetcore/issues/25230)