Skip to content

Commit 44f7681

Browse files
committed
Improved stream resolution handling
Invidious now reports the actual resolution and doesn’t hardcode them anymore. See: iv-org/invidious#4586 - Extended the list of possible resolutions in the StreamModel - trigger videoLoadFailureHandler if no streams are available - more logging for backend.bestPlayable Signed-off-by: Toni Förster <[email protected]>
1 parent b1a2712 commit 44f7681

File tree

3 files changed

+274
-20
lines changed

3 files changed

+274
-20
lines changed

Model/Player/Backends/PlayerBackend.swift

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import CoreMedia
22
import Defaults
33
import Foundation
4+
import Logging
45
#if !os(macOS)
56
import UIKit
67
#endif
@@ -75,6 +76,10 @@ protocol PlayerBackend {
7576
}
7677

7778
extension PlayerBackend {
79+
var logger: Logger {
80+
return Logger(label: "stream.yattee.player.backend")
81+
}
82+
7883
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
7984
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
8085
seek(to: time, seekType: seekType, completionHandler: completionHandler)
@@ -140,63 +145,95 @@ extension PlayerBackend {
140145
}
141146

142147
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
143-
// filter out non-HLS streams and streams with resolution more than maxResolution
148+
logger.info("Starting bestPlayable function")
149+
logger.info("Total streams received: \(streams.count)")
150+
logger.info("Max resolution allowed: \(String(describing: maxResolution.value))")
151+
logger.info("Format order: \(formatOrder)")
152+
153+
// Filter out non-HLS streams and streams with resolution more than maxResolution
144154
let nonHLSStreams = streams.filter {
145-
$0.kind != .hls && $0.resolution <= maxResolution.value
155+
let isHLS = $0.kind == .hls
156+
let isWithinResolution = $0.resolution <= maxResolution.value
157+
logger.info("Stream ID: \($0.id) - Kind: \(String(describing: $0.kind)) - Resolution: \(String(describing: $0.resolution)) - Bitrate: \($0.bitrate ?? 0)")
158+
logger.info("Is HLS: \(isHLS), Is within resolution: \(isWithinResolution)")
159+
return !isHLS && isWithinResolution
146160
}
161+
logger.info("Non-HLS streams after filtering: \(nonHLSStreams.count)")
147162

148-
// find max resolution and bitrate from non-HLS streams
163+
// Find max resolution and bitrate from non-HLS streams
149164
let bestResolutionStream = nonHLSStreams.max { $0.resolution < $1.resolution }
150165
let bestBitrateStream = nonHLSStreams.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
151166

167+
logger.info("Best resolution stream: \(String(describing: bestResolutionStream?.id)) with resolution: \(String(describing: bestResolutionStream?.resolution))")
168+
logger.info("Best bitrate stream: \(String(describing: bestBitrateStream?.id)) with bitrate: \(String(describing: bestBitrateStream?.bitrate))")
169+
152170
let bestResolution = bestResolutionStream?.resolution ?? maxResolution.value
153171
let bestBitrate = bestBitrateStream?.bitrate ?? bestResolutionStream?.resolution.bitrate ?? maxResolution.value.bitrate
154172

155-
return streams.map { stream in
173+
logger.info("Final best resolution selected: \(String(describing: bestResolution))")
174+
logger.info("Final best bitrate selected: \(bestBitrate)")
175+
176+
let adjustedStreams = streams.map { stream in
156177
if stream.kind == .hls {
178+
logger.info("Adjusting HLS stream ID: \(stream.id)")
157179
stream.resolution = bestResolution
158180
stream.bitrate = bestBitrate
159181
stream.format = .hls
160182
} else if stream.kind == .stream {
183+
logger.info("Adjusting non-HLS stream ID: \(stream.id)")
161184
stream.format = .stream
162185
}
163186
return stream
164187
}
165-
.filter { stream in
166-
stream.resolution <= maxResolution.value
188+
189+
let filteredStreams = adjustedStreams.filter { stream in
190+
let isWithinResolution = stream.resolution <= maxResolution.value
191+
logger.info("Filtered stream ID: \(stream.id) - Is within max resolution: \(isWithinResolution)")
192+
return isWithinResolution
167193
}
168-
.max { lhs, rhs in
194+
195+
logger.info("Filtered streams count after adjustments: \(filteredStreams.count)")
196+
197+
let bestStream = filteredStreams.max { lhs, rhs in
169198
if lhs.resolution == rhs.resolution {
170199
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
171200
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
172201
else {
173-
print("Failed to extract lhsFormat or rhsFormat")
202+
logger.info("Failed to extract lhsFormat or rhsFormat for streams \(lhs.id) and \(rhs.id)")
174203
return false
175204
}
176205

177206
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
178207
let rhsFormatIndex = formatOrder.firstIndex(of: rhsFormat) ?? Int.max
179208

209+
logger.info("Comparing formats for streams \(lhs.id) and \(rhs.id) - LHS Format Index: \(lhsFormatIndex), RHS Format Index: \(rhsFormatIndex)")
210+
180211
return lhsFormatIndex > rhsFormatIndex
181212
}
182213

214+
logger.info("Comparing resolutions for streams \(lhs.id) and \(rhs.id) - LHS Resolution: \(String(describing: lhs.resolution)), RHS Resolution: \(String(describing: rhs.resolution))")
215+
183216
return lhs.resolution < rhs.resolution
184217
}
218+
219+
logger.info("Best stream selected: \(String(describing: bestStream?.id)) with resolution: \(String(describing: bestStream?.resolution)) and format: \(String(describing: bestStream?.format))")
220+
221+
return bestStream
185222
}
186223

187224
func updateControls(completionHandler: (() -> Void)? = nil) {
188-
print("updating controls")
225+
logger.info("updating controls")
189226

190227
guard model.presentingPlayer, !model.controls.presentingOverlays else {
191-
print("ignored controls update")
228+
logger.info("ignored controls update")
192229
completionHandler?()
193230
return
194231
}
195232

196233
DispatchQueue.main.async(qos: .userInteractive) {
197234
#if !os(macOS)
198235
guard UIApplication.shared.applicationState != .background else {
199-
print("not performing controls updates in background")
236+
logger.info("not performing controls updates in background")
200237
completionHandler?()
201238
return
202239
}

Model/Player/PlayerQueue.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,32 @@ extension PlayerModel {
127127
var streamByQualityProfile: Stream? {
128128
let profile = qualityProfile ?? .defaultProfile
129129

130+
// First attempt: Filter by both `canPlay` and `isPreferred`
130131
if let streamPreferredForProfile = backend.bestPlayable(
131132
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
132133
maxResolution: profile.resolution, formatOrder: profile.formats
133134
) {
134135
return streamPreferredForProfile
135136
}
136137

137-
return backend.bestPlayable(availableStreams.filter { backend.canPlay($0) }, maxResolution: profile.resolution, formatOrder: profile.formats)
138+
// Fallback: Filter by `canPlay` only
139+
let fallbackStream = backend.bestPlayable(
140+
availableStreams.filter { backend.canPlay($0) },
141+
maxResolution: profile.resolution, formatOrder: profile.formats
142+
)
143+
144+
// If no stream is found, trigger the error handler
145+
guard let finalStream = fallbackStream else {
146+
let error = RequestError(
147+
userMessage: "No supported streams available.",
148+
cause: NSError(domain: "stream.yatte.app", code: -1, userInfo: [NSLocalizedDescriptionKey: "No supported streams available"])
149+
)
150+
videoLoadFailureHandler(error, video: currentVideo)
151+
return nil
152+
}
153+
154+
// Return the found stream
155+
return finalStream
138156
}
139157

140158
func advanceToNextItem() {

0 commit comments

Comments
 (0)