From 6a0fe2fbbfc2b74600c0cbe76dfabfbeab8a38a0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 30 Jul 2026 23:58:05 +0200 Subject: [PATCH] Show and label WebDAV/SMB video streams in the quality selector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Media-source streams from WebDAV/SMB were invisible in the Video list: the filter required remote URLs to carry a resolution, which MediaFile.toStream never sets. Include non-adaptive remote streams without resolution (only media sources produce those), extend the mpv-track display enrichment to the currently playing stream regardless of URL scheme, and classify metadata-less streams as recommended — a nil codec previously counted as software-decoded, hiding the row behind advanced mode with a spurious warning. The warning now uses the enriched codec. --- .../Player/QualitySelectorView+Sections.swift | 24 ++++++++++++------- .../QualitySelectorView+StreamHelpers.swift | 17 +++++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Yattee/Views/Player/QualitySelectorView+Sections.swift b/Yattee/Views/Player/QualitySelectorView+Sections.swift index bbe2e36f..c4de31e1 100644 --- a/Yattee/Views/Player/QualitySelectorView+Sections.swift +++ b/Yattee/Views/Player/QualitySelectorView+Sections.swift @@ -695,15 +695,17 @@ extension QualitySelectorView { } } - /// A local file's Stream carries no resolution/codec/fps metadata - /// (`MediaFile.toStream` cannot know them before demux), which would label - /// the row "Unknown". Once mpv reports the file's video track, fill those - /// fields for display. The enriched copy is display-only — selection and - /// tap handling keep using the original stream (compared by URL). + /// A media-source Stream (local folder, WebDAV, SMB) carries no + /// resolution/codec/fps metadata (`MediaFile.toStream` cannot know them + /// before demux), which would label the row "Unknown". Once mpv reports + /// the loaded file's video track, fill those fields for display. Gated to + /// local files and the currently playing stream — the track info describes + /// whatever mpv has loaded. The enriched copy is display-only — selection + /// and tap handling keep using the original stream (compared by URL). func displayStream(for stream: Stream) -> Stream { - guard stream.url.isFileURL, - stream.resolution == nil, + guard stream.resolution == nil, !stream.isAudioOnly, + stream.url.isFileURL || stream.url == currentStream?.url, let track = embeddedVideoTrack, let width = track.width, let height = track.height else { @@ -731,14 +733,18 @@ extension QualitySelectorView { ? stream.url == currentStream?.url : stream.url == selectedVideoStream?.url let isPreferredQuality: Bool = stream.resolution == preferredQuality.maxResolution + // Metadata-less media-source streams get resolution/codec/fps filled + // from the mpv-reported video track for display; the warning must use + // the enriched codec too or a nil codec reads as software-decoded + let display: Stream = displayStream(for: stream) VideoStreamRowView( - stream: displayStream(for: stream), + stream: display, isSelected: isSelected, isPreferredQuality: isPreferredQuality, isDownloaded: isDownloadedStream, showAdvancedDetails: showAdvancedStreamDetails, - requiresSoftwareDecode: !stream.isMuxed && requiresSoftwareDecode(stream.videoCodec), + requiresSoftwareDecode: !display.isMuxed && display.videoCodec != nil && requiresSoftwareDecode(display.videoCodec), onTap: { handleVideoStreamTap(stream, isDownloaded: isDownloadedStream) } diff --git a/Yattee/Views/Player/QualitySelectorView+StreamHelpers.swift b/Yattee/Views/Player/QualitySelectorView+StreamHelpers.swift index c342f159..bf4427ae 100644 --- a/Yattee/Views/Player/QualitySelectorView+StreamHelpers.swift +++ b/Yattee/Views/Player/QualitySelectorView+StreamHelpers.swift @@ -35,8 +35,17 @@ extension QualitySelectorView { var videoStreams: [Stream] { let maxRes: StreamResolution? = preferredQuality.maxResolution - // Separate downloaded streams (always include them, shown first) - let downloadedStreams: [Stream] = streams.filter { $0.url.isFileURL && !$0.isAudioOnly } + // Separate downloaded/media-source streams (always include them, shown + // first). Local files are file URLs; WebDAV/SMB media-source streams + // are remote URLs that carry no resolution metadata — nothing else + // produces a non-adaptive remote stream without resolution, so that + // combination identifies them. + let downloadedStreams: [Stream] = streams.filter { (stream: Stream) -> Bool in + guard !stream.isAudioOnly else { return false } + if stream.url.isFileURL { return true } + let format = StreamFormat.detect(from: stream) + return stream.resolution == nil && format != .hls && format != .dash + } // Online streams need resolution to be shown let onlineVideoStreams: [Stream] = streams @@ -95,6 +104,9 @@ extension QualitySelectorView { let allowSoftware = allowSoftwareDecodedFormats return videoStreams.filter { (stream: Stream) -> Bool in if stream.url.isFileURL { return true } + // Media-source streams (no resolution metadata, nil codec) must + // not be misclassified as software-decoded and hidden + if stream.resolution == nil { return true } if stream.isMuxed { return true } if allowSoftware { return true } return !requiresSoftwareDecode(stream.videoCodec) @@ -105,6 +117,7 @@ extension QualitySelectorView { if allowSoftwareDecodedFormats { return [] } return videoStreams.filter { (stream: Stream) -> Bool in if stream.url.isFileURL { return false } + if stream.resolution == nil { return false } if stream.isMuxed { return false } return requiresSoftwareDecode(stream.videoCodec) }