fix: populate queue titles independently of playback extraction

The sidebar scheduler waited for playback and used full stream extraction for titles. Use independent utility-priority title requests, native YouTube oEmbed, and metadata-only extraction for generic sites. Preserve foreground title precedence and stale row protection.

Verified: make check-youtube check-queue check-resolved check-adaptive check-stream all.
This commit is contained in:
2026-10-22 18:28:49 +02:00
parent 98e6dcb3c5
commit 4576080955
8 changed files with 287 additions and 85 deletions
+10 -12
View File
@@ -187,6 +187,7 @@ final class PlayerModel: ObservableObject {
private var reportingErrorShown = false
private var reportingErrorBuffer = [CChar]()
private let youtubeResolver: @MainActor (URL, Int?) async throws -> YouTubeVideo
private let youtubeTitleResolver: @MainActor (URL) async throws -> String?
private let miruroResolver: @MainActor (String, Int) async throws -> ResolvedSource
private let miruroMetadataResolver: @MainActor (String, Int) async throws -> MiruroMetadata
private var reportingErrorMessage: String {
@@ -196,6 +197,7 @@ final class PlayerModel: ObservableObject {
init(defaults: UserDefaults = .standard, configuration: PlayerConfiguration = PlayerConfiguration(),
progressFile: String? = nil, streamQueue: StreamQueue? = nil,
youtubeResolver: @escaping @MainActor (URL, Int?) async throws -> YouTubeVideo = { try await YouTubeResolver.resolve($0, height: $1) },
youtubeTitleResolver: @escaping @MainActor (URL) async throws -> String? = { try await YouTubeResolver.resolveTitle($0) },
miruroResolver: @escaping @MainActor (String, Int) async throws -> ResolvedSource = { try await MiruroResolver.resolve(mediaID: $0, episode: $1) },
miruroMetadataResolver: @escaping @MainActor (String, Int) async throws -> MiruroMetadata = { try await MiruroMetadataResolver.resolve(mediaID: $0, episode: $1) },
qualityClock: @escaping () -> Double = { ProcessInfo.processInfo.systemUptime },
@@ -203,6 +205,7 @@ final class PlayerModel: ObservableObject {
self.qualityClock = qualityClock
self.skipClock = skipClock
self.youtubeResolver = youtubeResolver
self.youtubeTitleResolver = youtubeTitleResolver
self.miruroResolver = miruroResolver
self.miruroMetadataResolver = miruroMetadataResolver
keyBindings = configuration.keys
@@ -656,7 +659,6 @@ final class PlayerModel: ObservableObject {
}
private func cancelStreamResolution() {
cancelQueueMetadata()
guard transition.isResolving else { return }
objectWillChange.send()
transition.cancelResolution()
@@ -681,18 +683,14 @@ final class PlayerModel: ObservableObject {
}
private func resolveQueueMetadataIfNeeded() {
// Playback and quality changes own the network before sidebar enrichment.
guard !isBusy, !resolvingStream, !state.buffering else {
cancelQueueMetadata()
return
}
// One utility-priority title lookup runs alongside (never behind) stream extraction.
if let request = queueMetadataTask,
!queue.items.contains(where: { $0.id == request.id }) {
cancelQueueMetadata()
}
guard queueMetadataPending, queueMetadataTask == nil else { return }
guard let item = queue.items.first(where: {
!$0.url.isFileURL && $0.id != queue.currentID && !$0.metadataAttempted
!$0.url.isFileURL && !$0.metadataAttempted
}) else {
queueMetadataPending = false
return
@@ -701,16 +699,14 @@ final class PlayerModel: ObservableObject {
queue.updateLabel(nil, for: item.id)
return
}
let youtube = youtubeResolver
let youtube = youtubeTitleResolver
let miruro = miruroMetadataResolver
let task = Task(priority: .utility) { [weak self] in
let title: String?
do {
switch stream {
case .youtube(let url):
let video = try await youtube(url, nil)
guard video.webpageURL == url else { throw CocoaError(.fileReadCorruptFile) }
title = video.title
title = try await youtube(url)
case .miruro(let id, let episode):
title = try await miruro(id, episode).title
}
@@ -719,7 +715,8 @@ final class PlayerModel: ObservableObject {
title = nil
}
guard !Task.isCancelled, let self else { return }
if self.queue.currentID != item.id {
// Full playback/episode metadata may have populated this row while we waited.
if self.queue.items.first(where: { $0.id == item.id })?.metadataAttempted == false {
self.queue.updateLabel(title, for: item.id)
}
self.queueMetadataTask = nil
@@ -1559,6 +1556,7 @@ final class PlayerModel: ObservableObject {
if castDevice != nil { stopCasting(resume: false) }
castDiscovery.stop()
cancelStreamResolution()
cancelQueueMetadata()
replaceMetadata(for: nil)
selectSubtitle(nil)
systemPlayback?.shutdown()