Play direct media links without Yattee Server by probing the URL first

Debrid and CDN download links (TorBox, Real-Debrid, presigned S3) hide the
file behind an opaque path, so the extension-based direct media check missed
them and every one was sent to Yattee Server for yt-dlp extraction, failing
outright when no server is configured.

Add DirectMediaProbe: a HEAD request classifies the response by Content-Type
and Content-Disposition, and when the type is generic or HEAD is refused, a
ranged GET sniffs the first bytes for container signatures (Matroska/WebM,
ISO BMFF, Ogg, FLAC, MP3, RIFF, FLV, ASF, MPEG-PS/TS, HLS and DASH
manifests). Anything playable goes straight to mpv with the server-reported
filename as the title; everything else falls back to extraction as before.

The probe runs ahead of the Yattee Server path in ExternalVideoView, both
OpenLinkSheet flows and the deep-link download path, and PlayerService
rebuilds direct_media streams itself so replays from history no longer
depend on a server either.
This commit is contained in:
Arkadiusz Fal
2026-09-21 20:03:06 +02:00
parent 53fda7574c
commit 6efd60a18e
7 changed files with 515 additions and 8 deletions

View File

@@ -754,6 +754,12 @@ struct OpenLinkFormView: View {
return (video, [stream])
case .externalVideo, nil:
// A file mpv plays as-is needs no extraction
if let probe = await DirectMediaProbe.probe(url) {
let video = DirectMediaHelper.createVideo(from: url, probe: probe)
let stream = DirectMediaHelper.createStream(from: url, probe: probe)
return (video, [stream])
}
// External URL - use Yattee Server
guard let instance = appEnvironment.instancesManager.yatteeServerInstance else {
throw OpenLinkError.noYatteeServer
@@ -896,6 +902,12 @@ struct OpenLinkFormView: View {
return (video, [stream], [], [])
case .externalVideo, nil:
// A file mpv plays as-is needs no extraction, no captions/storyboards
if let probe = await DirectMediaProbe.probe(url) {
let video = DirectMediaHelper.createVideo(from: url, probe: probe)
let stream = DirectMediaHelper.createStream(from: url, probe: probe)
return (video, [stream], [], [])
}
// External URL - use Yattee Server (doesn't support storyboards)
guard let instance = appEnvironment.instancesManager.yatteeServerInstance else {
throw OpenLinkError.noYatteeServer