From 48e616b301ac854a897ca2fc3b9c9342d8e1176c Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 20 Aug 2022 22:31:03 +0200 Subject: [PATCH] AVPlayer background music mode --- Model/Player/Backends/AVPlayerBackend.swift | 36 ++++++++++++ Model/Player/Backends/MPVBackend.swift | 32 +++++++++++ Model/Player/Backends/PlayerBackend.swift | 5 ++ Model/Player/PlayerControlsModel.swift | 17 ++++-- Model/Player/PlayerModel.swift | 63 ++++++++++----------- Shared/Player/VideoPlayerView.swift | 17 +++++- 6 files changed, 129 insertions(+), 41 deletions(-) diff --git a/Model/Player/Backends/AVPlayerBackend.swift b/Model/Player/Backends/AVPlayerBackend.swift index 6858c1d0..4ae6fb78 100644 --- a/Model/Player/Backends/AVPlayerBackend.swift +++ b/Model/Player/Backends/AVPlayerBackend.swift @@ -588,6 +588,42 @@ final class AVPlayerBackend: PlayerBackend { controlsUpdates = false } + func startMusicMode() { + if model.playingInPictureInPicture { + closePiP() + } + + playerLayer.player = nil + + toggleVisualTracksEnabled(false) + } + + func stopMusicMode() { + playerLayer.player = avPlayer + + toggleVisualTracksEnabled(true) + } + + func toggleVisualTracksEnabled(_ value: Bool) { + if let item = avPlayer.currentItem { + for playerItemTrack in item.tracks { + if let assetTrack = playerItemTrack.assetTrack, + assetTrack.hasMediaCharacteristic(AVMediaCharacteristic.visual) + { + playerItemTrack.isEnabled = value + } + } + } + } + + func didChangeTo() { + if model.musicMode { + startMusicMode() + } else { + stopMusicMode() + } + } + func setNeedsDrawing(_: Bool) {} func setSize(_: Double, _: Double) {} func setNeedsNetworkStateUpdates(_: Bool) {} diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index ab82bde5..02928d24 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -512,4 +512,36 @@ final class MPVBackend: PlayerBackend { networkStateTimer.pause() } } + + func startMusicMode() { + setVideoToNo() + } + + func stopMusicMode() { + addVideoTrackFromStream() + setVideoToAuto() + + controls.resetTimer() + } + + func addVideoTrackFromStream() { + if let videoTrackURL = model.stream?.videoAsset?.url, + tracks < 2 + { + logger.info("adding video track") + addVideoTrack(videoTrackURL) + } + + setVideoToAuto() + } + + func didChangeTo() { + setNeedsDrawing(model.presentingPlayer) + + if model.musicMode { + startMusicMode() + } else { + stopMusicMode() + } + } } diff --git a/Model/Player/Backends/PlayerBackend.swift b/Model/Player/Backends/PlayerBackend.swift index b3d65b0b..ff52a757 100644 --- a/Model/Player/Backends/PlayerBackend.swift +++ b/Model/Player/Backends/PlayerBackend.swift @@ -47,10 +47,15 @@ protocol PlayerBackend { func closePiP() + func startMusicMode() + func stopMusicMode() + func updateControls() func startControlsUpdates() func stopControlsUpdates() + func didChangeTo() + func setNeedsNetworkStateUpdates(_ needsUpdates: Bool) func setNeedsDrawing(_ needsDrawing: Bool) diff --git a/Model/Player/PlayerControlsModel.swift b/Model/Player/PlayerControlsModel.swift index 0d508f63..79a08a42 100644 --- a/Model/Player/PlayerControlsModel.swift +++ b/Model/Player/PlayerControlsModel.swift @@ -38,14 +38,18 @@ final class PlayerControlsModel: ObservableObject { func handlePresentationChange() { DispatchQueue.main.async { [weak self] in - guard let self = self else { return } + guard let self = self, + let player = self.player else { return } if self.presentingControls { - self.player?.backend.startControlsUpdates() + player.backend.startControlsUpdates() self.resetTimer() } else { - self.player?.backend.stopControlsUpdates() - self.timer?.invalidate() - self.timer = nil + if !player.musicMode { + player.backend.stopControlsUpdates() + self.removeTimer() + } else { + self.presentingControls = true + } } } } @@ -132,6 +136,8 @@ final class PlayerControlsModel: ObservableObject { } func startPiP(startImmediately: Bool = true) { + player?.avPlayerBackend.startPictureInPictureOnPlay = true + #if !os(macOS) player.exitFullScreen() #endif @@ -143,7 +149,6 @@ final class PlayerControlsModel: ObservableObject { } DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak player] in - player?.avPlayerBackend.startPictureInPictureOnPlay = true if startImmediately { player?.pipController?.startPictureInPicture() } diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index bea4ac4d..33dbfdd5 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -226,9 +226,11 @@ final class PlayerModel: ObservableObject { navigation.hideKeyboard() - DispatchQueue.main.async { [weak self] in - withAnimation(.linear(duration: 0.25)) { - self?.presentingPlayer = true + if !presentingPlayer { + DispatchQueue.main.async { [weak self] in + withAnimation(.linear(duration: 0.25)) { + self?.presentingPlayer = true + } } } @@ -340,14 +342,30 @@ final class PlayerModel: ObservableObject { func play(_ video: Video, at time: CMTime? = nil, showingPlayer: Bool = true) { pause() + var changeBackendHandler: (() -> Void)? + + if let backend = qualityProfile?.backend ?? QualityProfilesModel.shared.automaticProfile?.backend, + activeBackend != backend, + backend == .appleAVPlayer || !avPlayerBackend.startPictureInPictureOnPlay + { + changeBackendHandler = { [weak self] in + guard let self = self else { return } + self.changeActiveBackend(from: self.activeBackend, to: backend) + } + } + #if os(iOS) if !playingInPictureInPicture, showingPlayer { - onPresentPlayer = { [weak self] in self?.playNow(video, at: time) } + onPresentPlayer = { [weak self] in + changeBackendHandler?() + self?.playNow(video, at: time) + } show() return } #endif + changeBackendHandler?() playNow(video, at: time) guard !playingInPictureInPicture else { @@ -493,16 +511,12 @@ final class PlayerModel: ObservableObject { Defaults[.activeBackend] = to self.activeBackend = to + self.backend.didChangeTo() + guard var stream = stream else { return } - if to == .mpv { - addVideoTrackFromStream() - } else { - musicMode = false - } - let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend let toBackend: PlayerBackend = to == .appleAVPlayer ? avPlayerBackend : mpvBackend @@ -534,7 +548,6 @@ final class PlayerModel: ObservableObject { return } self.upgradeToStream(stream, force: true) - self.setNeedsDrawing(self.presentingPlayer) } } @@ -756,6 +769,7 @@ final class PlayerModel: ObservableObject { #else func handleEnterForeground() { setNeedsDrawing(presentingPlayer) + avPlayerBackend.playerLayer.player = avPlayerBackend.avPlayer guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else { return @@ -766,10 +780,10 @@ final class PlayerModel: ObservableObject { } func handleEnterBackground() { - setNeedsDrawing(false) - if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode { pause() + } else if !playingInPictureInPicture { + avPlayerBackend.playerLayer.player = nil } } #endif @@ -872,34 +886,17 @@ final class PlayerModel: ObservableObject { musicMode.toggle() if musicMode { - if playingInPictureInPicture { - avPlayerBackend.pause() - closePiP() - } - changeActiveBackend(from: .appleAVPlayer, to: .mpv) controls.presentingControls = true controls.removeTimer() - mpvBackend.setVideoToNo() + + backend.startMusicMode() } else { - addVideoTrackFromStream() - mpvBackend.setVideoToAuto() + backend.stopMusicMode() controls.resetTimer() } } - func addVideoTrackFromStream() { - if let videoTrackURL = stream?.videoAsset?.url, - mpvBackend.tracks < 2 - { - logger.info("adding video track") - - mpvBackend.addVideoTrack(videoTrackURL) - } - - mpvBackend.setVideoToAuto() - } - func updateAspectRatio() { #if !os(tvOS) guard aspectRatio != backend.aspectRatio else { return } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index c02a30fc..f6e46c6b 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -90,7 +90,7 @@ struct VideoPlayerView: View { Spacer() #endif } - #if os(macOS) + #if !os(tvOS) .frame(width: player.playerSize.width) #endif @@ -103,6 +103,11 @@ struct VideoPlayerView: View { #endif } } + .onAppear { + if player.musicMode { + player.backend.startControlsUpdates() + } + } } var videoPlayer: some View { @@ -435,10 +440,14 @@ struct VideoPlayerView: View { guard player.presentingPlayer, !playerControls.presentingControlsOverlay else { return } - if playerControls.presentingControls { + if playerControls.presentingControls, !player.musicMode { playerControls.presentingControls = false } + if player.musicMode { + player.backend.stopControlsUpdates() + } + let drag = value.translation.height guard drag > 0 else { return } @@ -479,6 +488,10 @@ struct VideoPlayerView: View { } player.backend.setNeedsDrawing(true) player.show() + + if player.musicMode { + player.backend.startControlsUpdates() + } } }