Compare commits

...

11 Commits

Author SHA1 Message Date
Jørgen Henrichsen d1bbc94bdd Update Readme. Update podspec.
Bump version to 0.9.1.
2019-06-11 13:48:16 +02:00
Jørgen Henrichsen fb5a8dde6c Merge pull request #61 from minhtc/fix-exclusive-access
Fix crash on modification requires exclusive access
2019-06-11 13:47:03 +02:00
minhtcx 9b71040f43 fix crash on modification requires exclusive access 2019-06-10 12:02:46 +07:00
Jørgen Henrichsen 5e50ea48d6 Merge pull request #60 from jorgenhenrichsen/recover-from-AVPlayer-failed
Recover from AVPlayer failed
2019-05-11 19:41:12 +02:00
Jørgen Henrichsen 92a804e9e4 Update Readme. Update podspec.
Bump version to v0.9.0.
2019-05-11 19:30:59 +02:00
Jørgen Henrichsen af4635d047 Remove AVPlayer parameter from init in both AudioPlayer and AVPlayerWrapper. 2019-05-11 19:29:36 +02:00
Jørgen Henrichsen addebef7f9 Add more detailed description on the fail event. 2019-05-11 18:14:54 +02:00
Jørgen Henrichsen 46022ef0d6 Add event when AVPlayer is recreated.
The AudioSession category will need to be set again when this event is emitted.
2019-05-11 17:56:43 +02:00
Jørgen Henrichsen 386dc5202c Recreate the AVPlayer when loading an item, if the previous item failed. 2019-05-11 17:46:55 +02:00
Jørgen Henrichsen 17166093c2 Correctly set isObserving when stopping the observation. 2019-05-11 17:46:27 +02:00
Jørgen Henrichsen c06b8ce64c Made the player observer not retain the AVPlayer.
They will also stop observing the current AVPlayer if a new one is going to be set.
2019-05-11 17:34:24 +02:00
13 changed files with 117 additions and 50 deletions
+5
View File
@@ -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)
}
}
+17 -1
View File
@@ -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())
})
})
})
}
@@ -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", {
+7 -4
View File
@@ -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() {
}
+2 -2
View File
@@ -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.1'
```
### 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.1
```
Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudio'
s.version = '0.8.0'
s.version = '0.9.1'
s.summary = 'Easy audio streaming for iOS'
# This description is used to generate tags and improve search results.
@@ -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 {
@@ -16,5 +16,6 @@ protocol AVPlayerWrapperDelegate: class {
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
func AVWrapper(didUpdateDuration duration: Double)
func AVWrapperItemDidPlayToEndTime()
func AVWrapperDidRecreateAVPlayer()
}
@@ -9,7 +9,7 @@ import Foundation
import AVFoundation
protocol AVPlayerWrapperProtocol {
protocol AVPlayerWrapperProtocol: class {
var state: AVPlayerWrapperState { get }
+6 -3
View File
@@ -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: ())
}
}
+10 -1
View File
@@ -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<SecondElapseEventData> = 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<FailEventData> = AudioPlayer.Event()
@@ -54,6 +56,13 @@ extension AudioPlayer {
*/
public let updateDuration: AudioPlayer.Event<UpdateDurationEventData> = 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> = (EventData) -> Void
@@ -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?) {
@@ -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
}
}