mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-16 17:04:54 +02:00
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:
parent
bcb239efd1
commit
edb25f5bcd
5 changed files with 121 additions and 36 deletions
|
@ -29,9 +29,7 @@ class AudioPlayer: NSObject {
|
||||||
internal let logger = AppLogger(category: "AudioPlayer")
|
internal let logger = AppLogger(category: "AudioPlayer")
|
||||||
|
|
||||||
private var status: PlayerStatus
|
private var status: PlayerStatus
|
||||||
internal var rate: Float
|
internal var rateManager: AudioPlayerRateManager
|
||||||
|
|
||||||
private var tmpRate: Float = 1.0
|
|
||||||
|
|
||||||
private var playerContext = 0
|
private var playerContext = 0
|
||||||
private var playerItemContext = 0
|
private var playerItemContext = 0
|
||||||
|
@ -64,11 +62,13 @@ class AudioPlayer: NSObject {
|
||||||
self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.status = .uninitialized
|
self.status = .uninitialized
|
||||||
self.rate = 0.0
|
self.rateManager = LegacyAudioPlayerRateManager(audioPlayer: self.audioPlayer, defaultRate: playbackRate)
|
||||||
self.tmpRate = playbackRate
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.rateManager.rateChangedCompletion = self.updateNowPlaying
|
||||||
|
self.rateManager.defaultRateChangedCompletion = self.setupTimeObservers
|
||||||
|
|
||||||
initAudioSession()
|
initAudioSession()
|
||||||
setupRemoteTransportControls()
|
setupRemoteTransportControls()
|
||||||
|
|
||||||
|
@ -81,7 +81,6 @@ class AudioPlayer: NSObject {
|
||||||
|
|
||||||
// Listen to player events
|
// Listen to player events
|
||||||
self.setupAudioSessionNotifications()
|
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)
|
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
|
||||||
|
|
||||||
for track in playbackSession.audioTracks {
|
for track in playbackSession.audioTracks {
|
||||||
|
@ -150,7 +149,6 @@ class AudioPlayer: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove observers
|
// Remove observers
|
||||||
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &playerContext)
|
|
||||||
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
|
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
|
||||||
self.removeTimeObservers()
|
self.removeTimeObservers()
|
||||||
|
|
||||||
|
@ -332,7 +330,7 @@ class AudioPlayer: NSObject {
|
||||||
self.markAudioSessionAs(active: true)
|
self.markAudioSessionAs(active: true)
|
||||||
DispatchQueue.runOnMainQueue {
|
DispatchQueue.runOnMainQueue {
|
||||||
self.audioPlayer.play()
|
self.audioPlayer.play()
|
||||||
self.audioPlayer.rate = self.tmpRate
|
self.rateManager.handlePlayEvent()
|
||||||
}
|
}
|
||||||
self.status = .playing
|
self.status = .playing
|
||||||
|
|
||||||
|
@ -424,25 +422,8 @@ class AudioPlayer: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
|
public func setPlaybackRate(_ rate: Float) {
|
||||||
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
|
self.rateManager.setPlaybackRate(rate)
|
||||||
|
|
||||||
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 setChapterTrack() {
|
public func setChapterTrack() {
|
||||||
|
@ -706,24 +687,21 @@ class AudioPlayer: NSObject {
|
||||||
NowPlayingInfo.shared.update(
|
NowPlayingInfo.shared.update(
|
||||||
duration: currentChapter.getRelativeChapterEndTime(),
|
duration: currentChapter.getRelativeChapterEndTime(),
|
||||||
currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime),
|
currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime),
|
||||||
rate: rate,
|
rate: self.rateManager.rate,
|
||||||
defaultRate: tmpRate,
|
defaultRate: self.rateManager.defaultRate,
|
||||||
chapterName: currentChapter.title,
|
chapterName: currentChapter.title,
|
||||||
chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1,
|
chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1,
|
||||||
chapterCount: session.chapters.count
|
chapterCount: session.chapters.count
|
||||||
)
|
)
|
||||||
} else if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
|
} 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
|
// MARK: - Observer
|
||||||
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
if context == &playerContext {
|
if context == &playerContext {
|
||||||
if keyPath == #keyPath(AVPlayer.rate) {
|
if keyPath == #keyPath(AVPlayer.currentItem) {
|
||||||
logger.log("playerContext observer player rate")
|
|
||||||
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
|
|
||||||
} else if keyPath == #keyPath(AVPlayer.currentItem) {
|
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
logger.log("WARNING: Item ended")
|
logger.log("WARNING: Item ended")
|
||||||
}
|
}
|
||||||
|
|
23
ios/App/Shared/player/AudioPlayerRateManager.swift
Normal file
23
ios/App/Shared/player/AudioPlayerRateManager.swift
Normal 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
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ extension AudioPlayer {
|
||||||
// Return the player time until sleep
|
// Return the player time until sleep
|
||||||
var sleepTimeRemaining: Double? = nil
|
var sleepTimeRemaining: Double? = nil
|
||||||
if let chapterStopAt = self.sleepTimeChapterStopAt {
|
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() {
|
} else if self.isCountdownSleepTimerSet() {
|
||||||
sleepTimeRemaining = self.sleepTimeRemaining
|
sleepTimeRemaining = self.sleepTimeRemaining
|
||||||
}
|
}
|
||||||
|
|
84
ios/App/Shared/player/LegacyAudioPlayerRateManager.swift
Normal file
84
ios/App/Shared/player/LegacyAudioPlayerRateManager.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ class PlayerHandler {
|
||||||
public static var paused: Bool {
|
public static var paused: Bool {
|
||||||
get {
|
get {
|
||||||
guard let player = player else { return true }
|
guard let player = player else { return true }
|
||||||
return player.rate == 0.0
|
return player.rateManager.rate == 0.0
|
||||||
}
|
}
|
||||||
set(paused) {
|
set(paused) {
|
||||||
if paused {
|
if paused {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue