diff --git a/Sources/PlayerBridge.swift b/Sources/PlayerBridge.swift index 0ceb6b2..3013caf 100644 --- a/Sources/PlayerBridge.swift +++ b/Sources/PlayerBridge.swift @@ -61,8 +61,15 @@ final class PlayerBridge: ObservableObject { private var timeObserver: Any? private var endObserver: Any? + private var itemStatusObserver: NSKeyValueObservation? private var cancellables = Set() + // 视频全屏 + private var fullscreenWindow: NSWindow? + + // 缓存时长 + private var cachedDuration: Double = 0 + // MARK: - 初始化 func setup() { @@ -216,6 +223,18 @@ final class PlayerBridge: ObservableObject { player.replaceCurrentItem(with: playerItem) 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) @@ -271,12 +290,33 @@ final class PlayerBridge: ObservableObject { guard let eng = engine, let item = player.currentItem else { return } 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 { eng.store.setValue(id: "time_current", value: formatTime(current)) eng.store.setValue(id: "time_total", value: formatTime(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)") } - // MARK: - 全屏 + // MARK: - 全屏(视频内容全屏,非窗口全屏) func toggleFullscreen() { - isFullscreen.toggle() #if os(macOS) - if let window = NSApp.keyWindow { - window.toggleFullScreen(nil) + if let fw = fullscreenWindow { + 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 } @@ -435,5 +499,6 @@ final class PlayerBridge: ObservableObject { if let observer = timeObserver { player.removeTimeObserver(observer) } + itemStatusObserver?.invalidate() } }