-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1179 from indiefan/rate-manager
Refactor AudioPlayer to use a Rate Manager protocol.
- Loading branch information
Showing
7 changed files
with
232 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
ios/App/Shared/player/DefaultedAudioPlayerRateManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// | ||
// DefaultedAudioPlayerRateManager.swift | ||
// Audiobookshelf | ||
// | ||
// Created by Marke Hallowell on 4/14/24. | ||
// | ||
|
||
import Foundation | ||
import AVFoundation | ||
|
||
@available(iOS 16.0, *) | ||
class DefaultedAudioPlayerRateManager: NSObject, AudioPlayerRateManager { | ||
internal let logger = AppLogger(category: "DefaultedAudioPlayerRateManager") | ||
|
||
internal var audioPlayer: AVPlayer | ||
|
||
// 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.audioPlayer = audioPlayer | ||
self.rateChangedCompletion = {} | ||
self.defaultRateChangedCompletion = {} | ||
self.rate = self.audioPlayer.rate | ||
self.defaultRate = defaultRate | ||
self.audioPlayer.defaultRate = defaultRate | ||
|
||
super.init() | ||
|
||
NotificationCenter.default.addObserver(self, selector: #selector(handleObservedRateChange), name: AVPlayer.rateDidChangeNotification, object: self.audioPlayer) | ||
} | ||
|
||
public func setPlaybackRate(_ rate: Float) { | ||
self.handlePlaybackRateChange(rate, observed: false) | ||
} | ||
|
||
// No-op (player automatically resumes at last-known defaultRate) | ||
public func handlePlayEvent() { } | ||
|
||
// MARK: - Destructor | ||
public func destroy() { | ||
NotificationCenter.default.removeObserver(self, name: AVPlayer.rateDidChangeNotification, object: self.audioPlayer) | ||
} | ||
|
||
// MARK: - Internal | ||
internal func handlePlaybackRateChange(_ rate: Float, observed: Bool = false) { | ||
let playbackSpeedChanged = rate > 0.0 && rate != self.defaultRate | ||
|
||
if playbackSpeedChanged { | ||
self.defaultRate = rate | ||
self.audioPlayer.defaultRate = rate | ||
self.defaultRateChangedCompletion() | ||
|
||
// Check to see if we also need to make a temporary rate change to player | ||
if self.audioPlayer.rate > 0.0 { | ||
if self.audioPlayer.rate != rate { | ||
self.rate = rate | ||
self.audioPlayer.rate = rate | ||
self.rateChangedCompletion() | ||
} | ||
} | ||
} else { | ||
self.rate = rate | ||
self.rateChangedCompletion() | ||
} | ||
} | ||
|
||
// MARK: - iOS rate change notification handler | ||
@objc internal func handleObservedRateChange(notification: Notification) { | ||
// TODO: Consider handling cases individually (e.g. overall session impact?) | ||
/* | ||
guard let reason = notification.userInfo?[AVPlayer.rateDidChangeReasonKey] as? AVPlayer.RateDidChangeReason else { | ||
return | ||
} | ||
|
||
switch reason { | ||
case .appBackgrounded: | ||
// App transitioned to the background. | ||
case .audioSessionInterrupted: | ||
// The system interrupts the app's audio session. | ||
case .setRateCalled: | ||
// The app set the player's rate. | ||
case .setRateFailed: | ||
// An attempt to change the player's rate failed. | ||
default: | ||
break | ||
} | ||
*/ | ||
self.handlePlaybackRateChange(self.audioPlayer.rate, observed: true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters