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

@@ -2181,6 +2181,16 @@ final class PlayerService {
let stream = try await createStreamForMediaSource(video: video, url: originalURL, extractor: extractor)
return (video, [stream], [], [])
}
// Direct media (a file mpv plays as-is): rebuild the stream from the
// URL. A probe fills in the format/size for opaque CDN links; if it
// fails (offline, expired token) still hand mpv the URL and let it
// report the real error.
if extractor == DirectMediaHelper.provider {
let probe = await DirectMediaProbe.probe(originalURL)
let stream = DirectMediaHelper.createStream(from: originalURL, probe: probe)
return (video, [stream], [], [])
}
}
guard let instance = try await findInstance(for: video) else {