releases/docs/MOBILE_PLAN.md

23 KiB

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). 기존 Paca.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 어셈블리를 그대로 참조 가능.

Paca.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)   │
│                          ▲                            │
│          ┌───────────────┴────────────────┐           │
│  Paca.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 폴링) + 일시정지/재개/취소/재시도 — 앱은 2초 폴링, 서버에 /hubs/queue SignalR 푸시도 제공
  • 공유 타겟: 다른 앱에서 URL 공유 → "PC에서 받기" (U1)
  • 링크 붙여넣기 직접 추가
  • 웹 컴패니언 페이지(데스크톱이 서빙) — 앱 없이 검증용

Tier M2 — 라이브러리 + 시청

  • 라이브러리 그리드(썸네일·제목·해상도·크기·추가일)
  • MediaElement 스트리밍 재생(Range HTTP) — 백그라운드 오디오는 컴패니언 구조상 제외(앱 내 재생 한정, IMPLEMENTATION_AUDIT.md §2 문서화)
  • 이어보기 위치 동기화
  • 폰으로 다운로드(오프라인 저장) + 다운로드 탭(Neflix식) + Android 진행 알림
  • Android 재생중 미디어 알림 — 백그라운드 재생이 없어 FGS 대신 ongoing 노티로 충족(감사 문서 문서화)

Tier M3 — iOS 확장

  • net10.0-ios 타겟 + Info.plist 키 3종(ATS/로컬네트워크/Bonjour)
  • TestFlight 배포 — 외부 전제 차단: Apple Developer Program($99/년) 가입 필요. 시뮬레이터 실행·페어링·큐 조회까지는 실증 완료. "내 PC 라이브러리 리모컨" 프레이밍(기기 내 추출 코드 엄격 배제)은 유지

Tier M4 — Android 독립 다운로더 (2026-08-31 ~ 2026-09-04 완료)

  • Core HLS/DASH/직접 다운로더 + 순수 C# 모바일 엔진 (MobileDownloadEngine.cs)
  • 인앱 웹뷰 스니퍼 + 스푸핑 엔진으로 YouTube/SNS 직접 수신 (MediaPlaybackBypassScript.cs, PacaAndroidWebViewCustomizer.cs, VideoSnifferParser.cs)
  • 모바일 다운로드 큐 + 앱 오프라인 영속화 (MobileDownloadEngine.cs, AppOfflineStore.cs)
  • 내장 브라우저 탭(하단 툴바·탭 그리드 카드 스위처·원터치 UA 전환) + 감지 배지 (BrowserPage.cs, TabManagerPage.cs, VideoSnifferModal.cs)
  • 저장: 로컬 비디오 및 다운로드 디렉터리 분기 보관 (Movies/Paca)

시기상조/미차용 (명시적 제외)

  • 클라우드 동기화/계정, 알림 미러링, 클립보드 동기, BLE 근접 전송, 자체 릴레이 서버, iOS 기기 내 yt-dlp(기술적·정책적 불가)

5. 로드맵 (의존성 순)

Phase M0 — 데스크톱 준비 (필수 선행, 1~2주)

  • Core 추상화 리팩터: IProcessRunner·IToolLocator·IMediaMuxer·경로 제공자 인터페이스 추출 (데스크톱 구현은 무변화, 기존 4개 프로젝트 무영향. BACKLOG Avalonia 로드맵 2단계와 동일 작업)
  • Paca.Server 신규 라이브러리: Kestrel 내장 + 토큰 인증 + /api/pair·/api/queue + SignalR 허브(/hubs/queue — 큐 변경 시 queueUpdated 푸시, E2E 검증)
  • WPF 설정에 QR 페어링 다이얼로그(QRCoder) + mDNS 광고(Makaretu.Dns _videodl._tcp — 광고→발견 실증)
  • 검증 관문: 폰 브라우저로 QR 스캔 → 웹 컴패니언에서 큐 조회/추가 동작 (앱 개발 전 서버 완성 입증)
  • ⚠️ git 아님 → git init 후 착수 → git 저장소 전환 완료

Phase M1 — Android 컴패니언 앱 (2~3주)

  • MAUI 10(net10.0-android) 신규 프로젝트 Paca.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) — 시뮬레이터에서 시동·페어링·큐 조회 실증 완료, TestFlight 배포만 Apple Developer Program 가입 대기
  • dotnet build 전체 0 오류 — 기존 4개 프로젝트(WPF/Avalonia/Cli/Core) 회귀 없음 (M0~전체)
  • 외부망에서 연결 시도 → 명확한 "같은 네트워크 필요/Tailscale 안내" 메시지 (UX)

출처 (핵심)