Refactor AudioPlayer to use an AudioPlayerRateManager protocol for managing rate/defaultRate state management. Implements a LegacyAudioPlayerRateManager implementation with existing (pre-iOS 16) behavior.

This commit is contained in:
Marke Hallowell 2024-04-14 11:45:03 -07:00
parent bcb239efd1
commit edb25f5bcd
5 changed files with 121 additions and 36 deletions

View file

@ -29,9 +29,7 @@ class AudioPlayer: NSObject {
internal let logger = AppLogger(category: "AudioPlayer")
private var status: PlayerStatus
internal var rate: Float
private var tmpRate: Float = 1.0
internal var rateManager: AudioPlayerRateManager
private var playerContext = 0
private var playerItemContext = 0
@ -64,11 +62,13 @@ class AudioPlayer: NSObject {
self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
self.sessionId = sessionId
self.status = .uninitialized
self.rate = 0.0
self.tmpRate = playbackRate
self.rateManager = LegacyAudioPlayerRateManager(audioPlayer: self.audioPlayer, defaultRate: playbackRate)
super.init()
self.rateManager.rateChangedCompletion = self.updateNowPlaying
self.rateManager.defaultRateChangedCompletion = self.setupTimeObservers
initAudioSession()
setupRemoteTransportControls()
@ -81,7 +81,6 @@ class AudioPlayer: NSObject {
// Listen to player events
self.setupAudioSessionNotifications()
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
for track in playbackSession.audioTracks {
@ -150,7 +149,6 @@ class AudioPlayer: NSObject {
}
// Remove observers
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &playerContext)
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
self.removeTimeObservers()
@ -332,7 +330,7 @@ class AudioPlayer: NSObject {
self.markAudioSessionAs(active: true)
DispatchQueue.runOnMainQueue {
self.audioPlayer.play()
self.audioPlayer.rate = self.tmpRate
self.rateManager.handlePlayEvent()
}
self.status = .playing
@ -424,25 +422,8 @@ class AudioPlayer: NSObject {
}
}
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
if self.audioPlayer.rate != rate {
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
DispatchQueue.runOnMainQueue {
self.audioPlayer.rate = rate
}
}
self.rate = rate
self.updateNowPlaying()
if playbackSpeedChanged {
self.tmpRate = rate
// Setup the time observer again at the new rate
self.setupTimeObservers()
}
public func setPlaybackRate(_ rate: Float) {
self.rateManager.setPlaybackRate(rate)
}
public func setChapterTrack() {
@ -706,24 +687,21 @@ class AudioPlayer: NSObject {
NowPlayingInfo.shared.update(
duration: currentChapter.getRelativeChapterEndTime(),
currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime),
rate: rate,
defaultRate: tmpRate,
rate: self.rateManager.rate,
defaultRate: self.rateManager.defaultRate,
chapterName: currentChapter.title,
chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1,
chapterCount: session.chapters.count
)
} else if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate, defaultRate: tmpRate)
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: self.rateManager.rate, defaultRate: self.rateManager.defaultRate)
}
}
// MARK: - Observer
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &playerContext {
if keyPath == #keyPath(AVPlayer.rate) {
logger.log("playerContext observer player rate")
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
} else if keyPath == #keyPath(AVPlayer.currentItem) {
if keyPath == #keyPath(AVPlayer.currentItem) {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
logger.log("WARNING: Item ended")
}

View file

@ -0,0 +1,23 @@
//
// AudioPlayerRateManager.swift
// Audiobookshelf
//
// Created by Marke Hallowell on 4/14/24.
//
import Foundation
import AVFoundation
protocol AudioPlayerRateManager {
var rate: Float { get }
var defaultRate: Float { get }
var rateChangedCompletion: () -> Void { get set }
var defaultRateChangedCompletion: () -> Void { get set }
init(audioPlayer: AVPlayer, defaultRate: Float)
func setPlaybackRate(_ rate: Float)
// Callback for play events (e.g. LegacyAudioPlayerRateManager uses this set rate immediately after playback resumes)
func handlePlayEvent() -> Void
}

View file

@ -22,7 +22,7 @@ extension AudioPlayer {
// Return the player time until sleep
var sleepTimeRemaining: Double? = nil
if let chapterStopAt = self.sleepTimeChapterStopAt {
sleepTimeRemaining = (chapterStopAt - currentTime) / Double(self.rate > 0 ? self.rate : 1.0)
sleepTimeRemaining = (chapterStopAt - currentTime) / Double(self.rateManager.rate > 0 ? self.rateManager.rate : 1.0)
} else if self.isCountdownSleepTimerSet() {
sleepTimeRemaining = self.sleepTimeRemaining
}

View file

@ -0,0 +1,84 @@
//
// LegacyAudioPlayerRateManager.swift
// Audiobookshelf
//
// Created by Marke Hallowell on 4/14/24.
//
import Foundation
import AVFoundation
class LegacyAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
internal let logger = AppLogger(category: "AudioPlayer")
internal var audioPlayer: AVPlayer
internal var managerContext = 0
// MARK: - AudioPlayerRateManager
public private(set) var defaultRate: Float
public private(set) var rate: Float
public var rateChangedCompletion: () -> Void
public var defaultRateChangedCompletion: () -> Void
required init(audioPlayer: AVPlayer, defaultRate: Float) {
self.rate = 0.0
self.defaultRate = defaultRate
self.audioPlayer = audioPlayer
self.rateChangedCompletion = {}
self.defaultRateChangedCompletion = {}
super.init()
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &managerContext)
}
public func setPlaybackRate(_ rate: Float) {
self.handlePlaybackRateChange(rate, observed: false)
}
public func handlePlayEvent() {
DispatchQueue.runOnMainQueue {
self.audioPlayer.rate = self.defaultRate
}
}
// MARK: - Destructor
public func destroy() {
// Remove Observer
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &managerContext)
}
// MARK: - Internal
internal func handlePlaybackRateChange(_ rate: Float, observed: Bool = false) {
let playbackSpeedChanged = rate > 0.0 && rate != self.defaultRate && !(observed && rate == 1)
if self.audioPlayer.rate != rate {
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
DispatchQueue.runOnMainQueue {
self.audioPlayer.rate = rate
}
}
self.rate = rate
self.rateChangedCompletion()
if playbackSpeedChanged {
self.defaultRate = rate
self.defaultRateChangedCompletion()
}
}
// MARK: - Observer
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &managerContext {
if keyPath == #keyPath(AVPlayer.rate) {
logger.log("playerContext observer player rate")
self.handlePlaybackRateChange(change?[.newKey] as? Float ?? 1.0, observed: true)
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
}
}

View file

@ -38,7 +38,7 @@ class PlayerHandler {
public static var paused: Bool {
get {
guard let player = player else { return true }
return player.rate == 0.0
return player.rateManager.rate == 0.0
}
set(paused) {
if paused {