diff --git a/Example/SwiftAudio/ViewController.swift b/Example/SwiftAudio/ViewController.swift index d301316..794fa11 100644 --- a/Example/SwiftAudio/ViewController.swift +++ b/Example/SwiftAudio/ViewController.swift @@ -31,6 +31,7 @@ class ViewController: UIViewController { controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed) controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek) controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration) + controller.player.event.didRecreateAVPlayer.addListener(self, handleAVPlayerRecreated) } @IBAction func togglePlay(_ sender: Any) { @@ -116,4 +117,8 @@ class ViewController: UIViewController { } } + func handleAVPlayerRecreated() { + try? controller.audioSessionController.set(category: .playback) + } + } diff --git a/Example/Tests/AVPlayerObserverTests.swift b/Example/Tests/AVPlayerObserverTests.swift index 3b1dbf6..7626bb9 100644 --- a/Example/Tests/AVPlayerObserverTests.swift +++ b/Example/Tests/AVPlayerObserverTests.swift @@ -20,10 +20,15 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate { beforeEach { player = AVPlayer() player.volume = 0.0 - observer = AVPlayerObserver(player: player) + observer = AVPlayerObserver() + observer.player = player observer.delegate = self } + it("should not be observing", closure: { + expect(observer.isObserving).to(beFalse()) + }) + context("when observing has started", { beforeEach { observer.startObserving() @@ -54,6 +59,17 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate { expect(observer.isObserving).toEventually(beTrue()) }) }) + + context("when stopping observing", closure: { + + beforeEach { + observer.stopObserving() + } + + it("should not be observing", closure: { + expect(observer.isObserving).to(beFalse()) + }) + }) }) } diff --git a/Example/Tests/AVPlayerTimeObserverTests.swift b/Example/Tests/AVPlayerTimeObserverTests.swift index 4311120..947a1da 100644 --- a/Example/Tests/AVPlayerTimeObserverTests.swift +++ b/Example/Tests/AVPlayerTimeObserverTests.swift @@ -17,7 +17,8 @@ class AVPlayerTimeObserverTests: QuickSpec { player = AVPlayer() player.automaticallyWaitsToMinimizeStalling = false player.volume = 0 - observer = AVPlayerTimeObserver(player: player, periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime()) + observer = AVPlayerTimeObserver(periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime()) + observer.player = player } context("has started boundary time observing", { diff --git a/Example/Tests/AVPlayerWrapperTests.swift b/Example/Tests/AVPlayerWrapperTests.swift index 4b1a90f..aca8c1d 100644 --- a/Example/Tests/AVPlayerWrapperTests.swift +++ b/Example/Tests/AVPlayerWrapperTests.swift @@ -14,10 +14,9 @@ class AVPlayerWrapperTests: QuickSpec { var wrapper: AVPlayerWrapper! beforeEach { - let player = AVPlayer() - player.automaticallyWaitsToMinimizeStalling = false - player.volume = 0.0 - wrapper = AVPlayerWrapper(avPlayer: player) + wrapper = AVPlayerWrapper() + wrapper.automaticallyWaitsToMinimizeStalling = false + wrapper.volume = 0.0 wrapper.bufferDuration = 0.0001 } @@ -235,6 +234,10 @@ class AVPlayerWrapperTests: QuickSpec { } class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate { + func AVWrapperDidRecreateAVPlayer() { + + } + func AVWrapperItemDidPlayToEndTime() { } diff --git a/README.md b/README.md index 3d5b8eb..985c12d 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: ```ruby -pod 'SwiftAudio', '~> 0.8.0' +pod 'SwiftAudio', '~> 0.9.0' ``` ### Carthage SwiftAudio supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile: ```ruby -github "jorgenhenrichsen/SwiftAudio" ~> 0.8.0 +github "jorgenhenrichsen/SwiftAudio" ~> 0.9.0 ``` Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). diff --git a/SwiftAudio.podspec b/SwiftAudio.podspec index 163d24e..fec35d9 100644 --- a/SwiftAudio.podspec +++ b/SwiftAudio.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SwiftAudio' - s.version = '0.8.0' + s.version = '0.9.0' s.summary = 'Easy audio streaming for iOS' # This description is used to generate tags and improve search results. diff --git a/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapper.swift b/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapper.swift index b79d399..915ba9e 100755 --- a/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapper.swift @@ -26,7 +26,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { // MARK: - Properties - let avPlayer: AVPlayer + var avPlayer: AVPlayer let playerObserver: AVPlayerObserver let playerTimeObserver: AVPlayerTimeObserver let playerItemNotificationObserver: AVPlayerItemNotificationObserver @@ -46,10 +46,12 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { } } - public init(avPlayer: AVPlayer = AVPlayer()) { - self.avPlayer = avPlayer - self.playerObserver = AVPlayerObserver(player: avPlayer) - self.playerTimeObserver = AVPlayerTimeObserver(player: avPlayer, periodicObserverTimeInterval: timeEventFrequency.getTime()) + public init() { + self.avPlayer = AVPlayer() + self.playerObserver = AVPlayerObserver() + self.playerObserver.player = avPlayer + self.playerTimeObserver = AVPlayerTimeObserver(periodicObserverTimeInterval: timeEventFrequency.getTime()) + self.playerTimeObserver.player = avPlayer self.playerItemNotificationObserver = AVPlayerItemNotificationObserver() self.playerItemObserver = AVPlayerItemObserver() @@ -167,6 +169,10 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { func load(from url: URL, playWhenReady: Bool) { reset(soft: true) _playWhenReady = playWhenReady + + if currentItem?.status == .failed { + recreateAVPlayer() + } // Set item let currentAsset = AVURLAsset(url: url) @@ -199,6 +205,16 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { } } + /// Will recreate the AVPlayer instance. Used when the current one fails. + private func recreateAVPlayer() { + let player = AVPlayer() + playerObserver.player = player + playerTimeObserver.player = player + playerTimeObserver.registerForPeriodicTimeEvents() + avPlayer = player + delegate?.AVWrapperDidRecreateAVPlayer() + } + } extension AVPlayerWrapper: AVPlayerObserverDelegate { diff --git a/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapperDelegate.swift b/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapperDelegate.swift index ee7bee8..14e82a2 100644 --- a/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapperDelegate.swift +++ b/SwiftAudio/Classes/AVPlayerWrapper/AVPlayerWrapperDelegate.swift @@ -16,5 +16,6 @@ protocol AVPlayerWrapperDelegate: class { func AVWrapper(seekTo seconds: Int, didFinish: Bool) func AVWrapper(didUpdateDuration duration: Double) func AVWrapperItemDidPlayToEndTime() + func AVWrapperDidRecreateAVPlayer() } diff --git a/SwiftAudio/Classes/AudioPlayer.swift b/SwiftAudio/Classes/AudioPlayer.swift index 0c5f18f..bc9c9fd 100755 --- a/SwiftAudio/Classes/AudioPlayer.swift +++ b/SwiftAudio/Classes/AudioPlayer.swift @@ -127,10 +127,9 @@ public class AudioPlayer: AVPlayerWrapperDelegate { - parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`. */ - public init(avPlayer: AVPlayer = AVPlayer(), - nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(), + public init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(), remoteCommandController: RemoteCommandController = RemoteCommandController()) { - self._wrapper = AVPlayerWrapper(avPlayer: avPlayer) + self._wrapper = AVPlayerWrapper() self.nowPlayingInfoController = nowPlayingInfoController self.remoteCommandController = remoteCommandController @@ -341,4 +340,8 @@ public class AudioPlayer: AVPlayerWrapperDelegate { self.event.playbackEnd.emit(data: .playedUntilEnd) } + func AVWrapperDidRecreateAVPlayer() { + self.event.didRecreateAVPlayer.emit(data: ()) + } + } diff --git a/SwiftAudio/Classes/Event.swift b/SwiftAudio/Classes/Event.swift index 99fd66c..4caf52d 100644 --- a/SwiftAudio/Classes/Event.swift +++ b/SwiftAudio/Classes/Event.swift @@ -15,6 +15,7 @@ extension AudioPlayer { public typealias FailEventData = (Error?) public typealias SeekEventData = (seconds: Int, didFinish: Bool) public typealias UpdateDurationEventData = (Double) + public typealias DidRecreateAVPlayerEventData = () public struct EventHolder { @@ -37,7 +38,8 @@ extension AudioPlayer { public let secondElapse: AudioPlayer.Event = AudioPlayer.Event() /** - Emitted when the player encounters an error. + Emitted when the player encounters an error. This will ultimately result in the AVPlayer instance to be recreated. + If this event is emitted, it means you will need to load a new item in some way. Calling play() will not resume playback. - Important: Remember to dispatch to the main queue if any UI is updated in the event handler. */ public let fail: AudioPlayer.Event = AudioPlayer.Event() @@ -54,6 +56,13 @@ extension AudioPlayer { */ public let updateDuration: AudioPlayer.Event = AudioPlayer.Event() + /** + Emitted when the underlying AVPlayer instance is recreated. Recreation happens if the current player fails. + - Important: Remember to dispatch to the main queue if any UI is updated in the event handler. + - Note: It can be necessary to set the AVAudioSession's category again when this event is emitted. + */ + public let didRecreateAVPlayer: AudioPlayer.Event<()> = AudioPlayer.Event() + } public typealias EventClosure = (EventData) -> Void diff --git a/SwiftAudio/Classes/Observer/AVPlayerObserver.swift b/SwiftAudio/Classes/Observer/AVPlayerObserver.swift index 3812a29..0fb33c9 100644 --- a/SwiftAudio/Classes/Observer/AVPlayerObserver.swift +++ b/SwiftAudio/Classes/Observer/AVPlayerObserver.swift @@ -36,39 +36,43 @@ class AVPlayerObserver: NSObject { static let timeControlStatus = #keyPath(AVPlayer.timeControlStatus) } - let player: AVPlayer + private let statusChangeOptions: NSKeyValueObservingOptions = [.new, .initial] private let timeControlStatusChangeOptions: NSKeyValueObservingOptions = [.new] var isObserving: Bool = false weak var delegate: AVPlayerObserverDelegate? - - init(player: AVPlayer) { - self.player = player + weak var player: AVPlayer? { + willSet { + self.stopObserving() + } } deinit { - if self.isObserving { - self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context) - self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context) - } + self.stopObserving() } /** Start receiving events from this observer. - - - Important: If this observer is already receiving events, it will first be removed. Never remove this observer manually. */ func startObserving() { - main.async { - if self.isObserving { - self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context) - self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context) - } - self.isObserving = true - self.player.addObserver(self, forKeyPath: AVPlayerKeyPath.status, options: self.statusChangeOptions, context: &AVPlayerObserver.context) - self.player.addObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, options: self.timeControlStatusChangeOptions, context: &AVPlayerObserver.context) + guard let player = player else { + return } + self.stopObserving() + self.isObserving = true + player.addObserver(self, forKeyPath: AVPlayerKeyPath.status, options: self.statusChangeOptions, context: &AVPlayerObserver.context) + player.addObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, options: self.timeControlStatusChangeOptions, context: &AVPlayerObserver.context) + } + + func stopObserving() { + guard let player = player, self.isObserving else { + return + } + player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context) + player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context) + self.isObserving = false + } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { diff --git a/SwiftAudio/Classes/Observer/AVPlayerTimeObserver.swift b/SwiftAudio/Classes/Observer/AVPlayerTimeObserver.swift index c30a0e0..44819b5 100644 --- a/SwiftAudio/Classes/Observer/AVPlayerTimeObserver.swift +++ b/SwiftAudio/Classes/Observer/AVPlayerTimeObserver.swift @@ -27,7 +27,12 @@ class AVPlayerTimeObserver { var boundaryTimeStartObserverToken: Any? var periodicTimeObserverToken: Any? - private let player: AVPlayer + weak var player: AVPlayer? { + willSet { + unregisterForBoundaryTimeEvents() + unregisterForPeriodicEvents() + } + } /// The frequence to receive periodic time events. /// Setting this to a new value will trigger a re-registering to the periodic events of the player. @@ -41,8 +46,7 @@ class AVPlayerTimeObserver { weak var delegate: AVPlayerTimeObserverDelegate? - init(player: AVPlayer, periodicObserverTimeInterval: CMTime) { - self.player = player + init(periodicObserverTimeInterval: CMTime) { self.periodicObserverTimeInterval = periodicObserverTimeInterval } @@ -50,9 +54,11 @@ class AVPlayerTimeObserver { Will register for the AVPlayer BoundaryTimeEvents, to trigger start and complete events. */ func registerForBoundaryTimeEvents() { - + guard let player = player else { + return + } + unregisterForBoundaryTimeEvents() let startBoundaryTimes: [NSValue] = [AVPlayerTimeObserver.startBoundaryTime].map({NSValue(time: $0)}) - boundaryTimeStartObserverToken = player.addBoundaryTimeObserver(forTimes: startBoundaryTimes, queue: nil, using: { [weak self] in self?.delegate?.audioDidStart() }) @@ -62,10 +68,11 @@ class AVPlayerTimeObserver { Unregister from the boundary events of the player. */ func unregisterForBoundaryTimeEvents() { - if let boundaryTimeStartObserverToken = boundaryTimeStartObserverToken { - player.removeTimeObserver(boundaryTimeStartObserverToken) - self.boundaryTimeStartObserverToken = nil + guard let player = player, let boundaryTimeStartObserverToken = boundaryTimeStartObserverToken else { + return } + player.removeTimeObserver(boundaryTimeStartObserverToken) + self.boundaryTimeStartObserverToken = nil } /** @@ -73,6 +80,9 @@ class AVPlayerTimeObserver { Will trigger unregisterForPeriodicEvents() first to avoid multiple subscriptions. */ func registerForPeriodicTimeEvents() { + guard let player = player else { + return + } unregisterForPeriodicEvents() periodicTimeObserverToken = player.addPeriodicTimeObserver(forInterval: periodicObserverTimeInterval, queue: nil, using: { (time) in self.delegate?.timeEvent(time: time) @@ -83,12 +93,11 @@ class AVPlayerTimeObserver { Unregister for periodic events. */ func unregisterForPeriodicEvents() { - if let periodicTimeObserverToken = periodicTimeObserverToken { - self.player.removeTimeObserver(periodicTimeObserverToken) - self.periodicTimeObserverToken = nil + guard let player = player, let periodicTimeObserverToken = periodicTimeObserverToken else { + return } + player.removeTimeObserver(periodicTimeObserverToken) + self.periodicTimeObserverToken = nil } - - }