fix: video content fullscreen (borderless window), load actual duration via KVO+async

This commit is contained in:
yumoqing 2026-06-21 23:31:12 +08:00
parent 2a5e3b8ad7
commit bf4cb64286

View File

@ -61,8 +61,15 @@ final class PlayerBridge: ObservableObject {
private var timeObserver: Any? private var timeObserver: Any?
private var endObserver: Any? private var endObserver: Any?
private var itemStatusObserver: NSKeyValueObservation?
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
//
private var fullscreenWindow: NSWindow?
//
private var cachedDuration: Double = 0
// MARK: - // MARK: -
func setup() { func setup() {
@ -216,6 +223,18 @@ final class PlayerBridge: ObservableObject {
player.replaceCurrentItem(with: playerItem) player.replaceCurrentItem(with: playerItem)
player.play() player.play()
//
cachedDuration = 0
// item
itemStatusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] item, _ in
if item.status == .readyToPlay {
Task { @MainActor in
self?.loadDuration(item)
}
}
}
// //
loadTrackInfo(playerItem) loadTrackInfo(playerItem)
@ -271,12 +290,33 @@ final class PlayerBridge: ObservableObject {
guard let eng = engine, let item = player.currentItem else { return } guard let eng = engine, let item = player.currentItem else { return }
let current = time.seconds let current = time.seconds
let total = item.duration.seconds var total = item.duration.seconds
// duration
if !total.isFinite || total <= 0 {
total = cachedDuration
} else {
cachedDuration = total
}
if total.isFinite && total > 0 { if total.isFinite && total > 0 {
eng.store.setValue(id: "time_current", value: formatTime(current)) eng.store.setValue(id: "time_current", value: formatTime(current))
eng.store.setValue(id: "time_total", value: formatTime(total)) eng.store.setValue(id: "time_total", value: formatTime(total))
eng.store.setValue(id: "progress_slider", value: "\(current)/\(total)") eng.store.setValue(id: "progress_slider", value: "\(current)/\(total)")
} else {
eng.store.setValue(id: "time_current", value: formatTime(current))
}
}
private func loadDuration(_ item: AVPlayerItem) {
Task {
if let dur = try? await item.asset.load(.duration) {
let secs = dur.seconds
if secs.isFinite && secs > 0 {
cachedDuration = secs
engine?.store.setValue(id: "time_total", value: formatTime(secs))
}
}
} }
} }
@ -339,14 +379,38 @@ final class PlayerBridge: ObservableObject {
showToast("循环模式: \(repeatMode.rawValue)") showToast("循环模式: \(repeatMode.rawValue)")
} }
// MARK: - // MARK: -
func toggleFullscreen() { func toggleFullscreen() {
isFullscreen.toggle()
#if os(macOS) #if os(macOS)
if let window = NSApp.keyWindow { if let fw = fullscreenWindow {
window.toggleFullScreen(nil) fw.close()
fullscreenWindow = nil
isFullscreen = false
return
} }
guard let screen = NSScreen.main else { return }
let win = NSWindow(contentRect: screen.frame, styleMask: .borderless, backing: .buffered, defer: false)
win.level = .screenSaver
win.backgroundColor = .black
win.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
win.hasShadow = false
win.ignoresMouseEvents = false
let hostView = NSHostingView(rootView:
VideoPlayerRepresentable(player: player)
.background(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.onTapGesture { [weak self] in self?.toggleFullscreen() }
)
hostView.frame = screen.frame
win.contentView = hostView
win.makeKeyAndOrderFront(nil)
NSApp.hide(nil)
fullscreenWindow = win
isFullscreen = true
#endif #endif
} }
@ -435,5 +499,6 @@ final class PlayerBridge: ObservableObject {
if let observer = timeObserver { if let observer = timeObserver {
player.removeTimeObserver(observer) player.removeTimeObserver(observer)
} }
itemStatusObserver?.invalidate()
} }
} }