0a099c3fc9
YouTubeKit returns stream URLs only; its own README punts on the progressive-vs-adaptive problem. Most modern YouTube videos above 360p serve video and audio as separate DASH tracks, so the previous "any MP4" fallback silently grabbed a video-only track and the downloaded file had no audio. VideoDownloadService now resolves a DownloadPlan up front. Progressive MP4 (when one exists) still streams straight to the final destination. Otherwise the service downloads the best MP4 video track + best M4A audio track to temp files, then combines them into a single MP4 via AVMutableComposition + AVAssetExportSession. Passthrough preset first (lossless container rewrite) with a fallback to highestQuality if the codec combination requires re-encoding. Temp files are cleaned up on both success and failure. DownloadStatus replaces the bare Double progress so the UI can show per-phase labels (Video %, Audio %, Finalizing…). Muxing progress is rendered as an indeterminate spinner since AVAssetExportSession's progress property doesn't cross actor boundaries cleanly. Also retires the deprecated Stream.subtype comparison in favor of Stream.fileExtension, matching YouTubeKit's current API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>