diff --git a/Model/Player/Backends/AVPlayerBackend.swift b/Model/Player/Backends/AVPlayerBackend.swift index 990bb0c1..27dcb317 100644 --- a/Model/Player/Backends/AVPlayerBackend.swift +++ b/Model/Player/Backends/AVPlayerBackend.swift @@ -40,7 +40,6 @@ final class AVPlayerBackend: PlayerBackend { private var composition = AVMutableComposition() private var loadedCompositionAssets = [AVMediaType]() - private var currentArtwork: MPMediaItemArtwork? private var frequentTimeObserver: Any? private var infrequentTimeObserver: Any? private var playerTimeControlStatusObserver: Any? @@ -73,7 +72,7 @@ final class AVPlayerBackend: PlayerBackend { _ stream: Stream, of video: Video, preservingTime: Bool, - upgrading: Bool + upgrading _: Bool ) { if let url = stream.singleAssetURL { model.logger.info("playing stream with one asset\(stream.kind == .hls ? " (HLS)" : ""): \(url)") @@ -85,10 +84,6 @@ final class AVPlayerBackend: PlayerBackend { loadComposition(stream, of: video, preservingTime: preservingTime) } - - if !upgrading { - updateCurrentArtwork() - } } func play() { @@ -501,7 +496,7 @@ final class AVPlayerBackend: PlayerBackend { self.controls.currentTime = self.currentTime ?? .zero #if !os(tvOS) - self.updateNowPlayingInfo() + self.model.updateNowPlayingInfo() #endif if let currentTime = self.currentTime { @@ -566,48 +561,4 @@ final class AVPlayerBackend: PlayerBackend { } } } - - private func updateCurrentArtwork() { - guard let thumbnailData = try? Data(contentsOf: model.currentItem.video.thumbnailURL(quality: .medium)!) else { - return - } - - #if os(macOS) - let image = NSImage(data: thumbnailData) - #else - let image = UIImage(data: thumbnailData) - #endif - - if image.isNil { - return - } - - currentArtwork = MPMediaItemArtwork(boundsSize: image!.size) { _ in image! } - } - - fileprivate func updateNowPlayingInfo() { - var nowPlayingInfo: [String: AnyObject] = [ - MPMediaItemPropertyTitle: model.currentItem.video.title as AnyObject, - MPMediaItemPropertyArtist: model.currentItem.video.author as AnyObject, - MPNowPlayingInfoPropertyIsLiveStream: model.currentItem.video.live as AnyObject, - MPNowPlayingInfoPropertyElapsedPlaybackTime: avPlayer.currentTime().seconds as AnyObject, - MPNowPlayingInfoPropertyPlaybackQueueCount: model.queue.count as AnyObject, - MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject - ] - - if !currentArtwork.isNil { - nowPlayingInfo[MPMediaItemPropertyArtwork] = currentArtwork as AnyObject - } - - if !model.currentItem.video.live { - let itemDuration = model.currentItem.videoDuration ?? model.currentItem.duration - let duration = itemDuration.isFinite ? Double(itemDuration) : nil - - if !duration.isNil { - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration as AnyObject - } - } - - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - } } diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index 3eb16b7c..ed6f8ff7 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -224,11 +224,13 @@ final class MPVBackend: PlayerBackend { updateControls() } + model.updateNowPlayingInfo() + if let currentTime = currentTime { model.handleSegments(at: currentTime) } - self.timeObserverThrottle.execute { + timeObserverThrottle.execute { self.model.updateWatch() } } diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index b84b3d7b..3520d0ef 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -97,6 +97,8 @@ final class PlayerModel: ObservableObject { @Default(.closePiPAndOpenPlayerOnEnteringForeground) var closePiPAndOpenPlayerOnEnteringForeground #endif + private var currentArtwork: MPMediaItemArtwork? + init(accounts: AccountsModel? = nil, comments: CommentsModel? = nil, controls: PlayerControlsModel? = nil) { self.accounts = accounts ?? AccountsModel() self.comments = comments ?? CommentsModel() @@ -232,6 +234,10 @@ final class PlayerModel: ObservableObject { preservingTime: preservingTime, upgrading: upgrading ) + + if !upgrading { + updateCurrentArtwork() + } } func saveTime(completionHandler: @escaping () -> Void = {}) { @@ -418,4 +424,52 @@ final class PlayerModel: ObservableObject { backend.exitFullScreen() } #endif + + func updateNowPlayingInfo() { + let currentTime = (backend.currentTime?.seconds.isFinite ?? false) ? backend.currentTime!.seconds : 0 + var nowPlayingInfo: [String: AnyObject] = [ + MPMediaItemPropertyTitle: currentItem.video.title as AnyObject, + MPMediaItemPropertyArtist: currentItem.video.author as AnyObject, + MPNowPlayingInfoPropertyIsLiveStream: currentItem.video.live as AnyObject, + MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime as AnyObject, + MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject, + MPNowPlayingInfoPropertyPlaybackQueueIndex: 1 as AnyObject, + MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject + ] + + if !currentArtwork.isNil { + nowPlayingInfo[MPMediaItemPropertyArtwork] = currentArtwork as AnyObject + } + + if !currentItem.video.live { + let itemDuration = (backend.playerItemDuration ?? .zero).seconds + let duration = itemDuration.isFinite ? Double(itemDuration) : nil + + if !duration.isNil { + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration as AnyObject + } + } + + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + + func updateCurrentArtwork() { + guard let video = currentVideo, + let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: .medium)!) + else { + return + } + + #if os(macOS) + let image = NSImage(data: thumbnailData) + #else + let image = UIImage(data: thumbnailData) + #endif + + if image.isNil { + return + } + + currentArtwork = MPMediaItemArtwork(boundsSize: image!.size) { _ in image! } + } } diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 5386f6c6..872adafc 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -1,5 +1,6 @@ import AVFAudio import Defaults +import MediaPlayer import SDWebImage import SDWebImagePINPlugin import SDWebImageWebPCoder @@ -107,7 +108,7 @@ struct ContentView: View { SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared) SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app") #if !os(macOS) - try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) + setupNowPlayingInfoCenter() #endif #if os(iOS) @@ -164,6 +165,54 @@ struct ContentView: View { playlists.load() } + func setupNowPlayingInfoCenter() { + try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) + + UIApplication.shared.beginReceivingRemoteControlEvents() + + MPRemoteCommandCenter.shared().playCommand.addTarget { _ in + player.play() + return .success + } + + MPRemoteCommandCenter.shared().pauseCommand.addTarget { _ in + player.pause() + return .success + } + + MPRemoteCommandCenter.shared().previousTrackCommand.isEnabled = false + MPRemoteCommandCenter.shared().nextTrackCommand.isEnabled = false + + MPRemoteCommandCenter.shared().changePlaybackPositionCommand.addTarget { remoteEvent in + guard let event = remoteEvent as? MPChangePlaybackPositionCommandEvent + else { + return .commandFailed + } + + player.backend.seek(to: event.positionTime) + + return .success + } + + let skipForwardCommand = MPRemoteCommandCenter.shared().skipForwardCommand + skipForwardCommand.isEnabled = true + skipForwardCommand.preferredIntervals = [10] + + skipForwardCommand.addTarget { _ in + player.backend.seek(relative: .secondsInDefaultTimescale(10)) + return .success + } + + let skipBackwardCommand = MPRemoteCommandCenter.shared().skipBackwardCommand + skipBackwardCommand.isEnabled = true + skipBackwardCommand.preferredIntervals = [10] + + skipBackwardCommand.addTarget { _ in + player.backend.seek(relative: .secondsInDefaultTimescale(-10)) + return .success + } + } + func openWelcomeScreenIfAccountEmpty() { guard Defaults[.instances].isEmpty else { return