Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8276f38b1b | |||
| fcd5790e1e | |||
| ead7c0962e | |||
| 7ff34271e8 | |||
| 4f7a5b02a6 | |||
| af803339dc | |||
| a5bf6eb1dd | |||
| 5e0c27b990 | |||
| 6079234942 | |||
| e74b5ffe4d |
@@ -3,6 +3,21 @@ import Nimble
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
extension QueuedAudioPlayer {
|
||||
class SeekEventListener {
|
||||
var eventResult: (Int, Bool) = (-1, false)
|
||||
func handleEvent(seconds: Int, didFinish: Bool) { eventResult = (seconds, didFinish) }
|
||||
}
|
||||
|
||||
func seekWithExpectation(to time: Double) {
|
||||
let eventListener = SeekEventListener()
|
||||
event.seek.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
seek(to: time)
|
||||
expect(eventListener.eventResult).toEventually(equal((0, true)))
|
||||
}
|
||||
}
|
||||
|
||||
class QueuedAudioPlayerTests: QuickSpec {
|
||||
override func spec() {
|
||||
describe("A QueuedAudioPlayer") {
|
||||
@@ -170,7 +185,7 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
describe("onNext") {
|
||||
context("player was playing") {
|
||||
beforeEach {
|
||||
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: true)
|
||||
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
@@ -197,7 +212,7 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
try? audioPlayer.next()
|
||||
}
|
||||
|
||||
it("should go to next item and play") {
|
||||
it("should go to next item and not play") {
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
|
||||
@@ -218,8 +233,9 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
try? audioPlayer.previous()
|
||||
}
|
||||
|
||||
it("should go to next item and play") {
|
||||
it("should go to previous item and play") {
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(1))
|
||||
expect(audioPlayer.previousItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
}
|
||||
@@ -238,8 +254,9 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
try? audioPlayer.previous()
|
||||
}
|
||||
|
||||
it("should go to next item and play") {
|
||||
it("should go to previous item and not play") {
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(1))
|
||||
expect(audioPlayer.previousItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
|
||||
}
|
||||
@@ -247,6 +264,11 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
}
|
||||
}
|
||||
|
||||
class TestEventListener {
|
||||
var eventResult: (Int?, Int?) = (-1, -1)
|
||||
func handleEvent(previousIndex: Int?, nextIndex: Int?) { eventResult = (previousIndex, nextIndex) }
|
||||
}
|
||||
|
||||
describe("its repeat mode") {
|
||||
context("when adding 2 items") {
|
||||
beforeEach {
|
||||
@@ -258,43 +280,53 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
audioPlayer.repeatMode = .off
|
||||
}
|
||||
|
||||
context("allow playback to end") {
|
||||
context("allow playback to end normally") {
|
||||
beforeEach {
|
||||
audioPlayer.seek(to: 0.0682)
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should move to next item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 1)))
|
||||
}
|
||||
|
||||
context("allow playback to end again") {
|
||||
beforeEach {
|
||||
audioPlayer.seek(to: 0.0682)
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should stop playback normally") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.paused))
|
||||
expect(eventListener.eventResult).toEventually(equal((1, nil)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
beforeEach {
|
||||
try? audioPlayer.next()
|
||||
}
|
||||
|
||||
it("should move to next item") {
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
expect(audioPlayer.currentIndex).to(equal(1))
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
try? audioPlayer.next()
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 1)))
|
||||
}
|
||||
|
||||
context("then calling next() again") {
|
||||
it("should fail") {
|
||||
try? audioPlayer.next()
|
||||
expect(try audioPlayer.next()).to(throwError())
|
||||
}
|
||||
}
|
||||
@@ -308,25 +340,30 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
|
||||
context("allow playback to end") {
|
||||
beforeEach {
|
||||
audioPlayer.seek(to: 0.0682)
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should restart current item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.currentTime).toEventually(equal(0))
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(1))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
beforeEach {
|
||||
try? audioPlayer.next()
|
||||
}
|
||||
|
||||
it("should move to next item and should play") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
try? audioPlayer.next()
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,37 +375,46 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
|
||||
context("allow playback to end") {
|
||||
beforeEach {
|
||||
audioPlayer.seek(to: 0.0682)
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should move to next item and should play") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 1)))
|
||||
}
|
||||
|
||||
context("allow playback to end again") {
|
||||
beforeEach {
|
||||
audioPlayer.seek(to: 0.0682)
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should move to first track and should play") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(1))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((1, 0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
beforeEach {
|
||||
try? audioPlayer.next()
|
||||
}
|
||||
|
||||
it("should move to next item and should play") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
try? audioPlayer.next()
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
expect(audioPlayer.currentIndex).to(equal(1))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 1)))
|
||||
}
|
||||
|
||||
context("then calling next() again") {
|
||||
@@ -377,14 +423,124 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
}
|
||||
|
||||
it("should move to first track and should play") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
try? audioPlayer.next()
|
||||
expect(audioPlayer.nextItems.count).to(equal(1))
|
||||
expect(audioPlayer.currentIndex).to(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((1, 0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("when adding 1 items") {
|
||||
beforeEach {
|
||||
try? audioPlayer.add(item: ShortSource.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
|
||||
context("then setting repeat mode off") {
|
||||
beforeEach {
|
||||
audioPlayer.repeatMode = .off
|
||||
}
|
||||
|
||||
context("allow playback to end normally") {
|
||||
beforeEach {
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should stop playback normally") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.nextItems.count).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.paused))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, nil)))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
it("should fail") {
|
||||
try? audioPlayer.next()
|
||||
expect(try audioPlayer.next()).to(throwError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then setting repeat mode track") {
|
||||
beforeEach {
|
||||
audioPlayer.repeatMode = .track
|
||||
}
|
||||
|
||||
context("allow playback to end") {
|
||||
beforeEach {
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should restart current item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.currentTime).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
it("should restart current item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.currentTime).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then setting repeat mode queue") {
|
||||
beforeEach {
|
||||
audioPlayer.repeatMode = .queue
|
||||
}
|
||||
|
||||
context("allow playback to end") {
|
||||
beforeEach {
|
||||
audioPlayer.seekWithExpectation(to: 0.0682)
|
||||
}
|
||||
|
||||
it("should restart current item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
expect(audioPlayer.currentTime).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next()") {
|
||||
it("should restart current item") {
|
||||
let eventListener = TestEventListener()
|
||||
audioPlayer.event.queueIndex.addListener(eventListener, eventListener.handleEvent)
|
||||
|
||||
// workaround: seek not to beggining, for 0 expecations to correctly fail if necessary.
|
||||
audioPlayer.seekWithExpectation(to: 0.05)
|
||||
try? audioPlayer.next()
|
||||
expect(audioPlayer.currentTime).toEventually(equal(0))
|
||||
expect(audioPlayer.currentIndex).toEventually(equal(0))
|
||||
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
expect(eventListener.eventResult).toEventually(equal((0, 0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioEx'
|
||||
s.version = '0.15.0'
|
||||
s.version = '0.15.2'
|
||||
s.summary = 'Easy audio streaming for iOS'
|
||||
s.description = <<-DESC
|
||||
SwiftAudioEx is an audio player written in Swift, making it simpler to work with audio playback from streams and files.
|
||||
|
||||
@@ -26,72 +26,51 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var avPlayer: AVPlayer
|
||||
let playerObserver: AVPlayerObserver
|
||||
let playerTimeObserver: AVPlayerTimeObserver
|
||||
let playerItemNotificationObserver: AVPlayerItemNotificationObserver
|
||||
let playerItemObserver: AVPlayerItemObserver
|
||||
|
||||
/**
|
||||
True if the last call to load(from:playWhenReady) had playWhenReady=true.
|
||||
*/
|
||||
fileprivate var _playWhenReady: Bool = true
|
||||
fileprivate var _initialTime: TimeInterval?
|
||||
fileprivate var avPlayer = AVPlayer()
|
||||
private let playerObserver = AVPlayerObserver()
|
||||
internal let playerTimeObserver: AVPlayerTimeObserver
|
||||
private let playerItemNotificationObserver = AVPlayerItemNotificationObserver()
|
||||
private let playerItemObserver = AVPlayerItemObserver()
|
||||
|
||||
fileprivate var initialTime: TimeInterval?
|
||||
fileprivate var pendingAsset: AVAsset? = nil
|
||||
|
||||
/// True when the track was paused for the purpose of switching tracks
|
||||
fileprivate var _pausedForLoad: Bool = false
|
||||
|
||||
fileprivate var _state: AVPlayerWrapperState = AVPlayerWrapperState.idle {
|
||||
didSet {
|
||||
if oldValue != _state {
|
||||
self.delegate?.AVWrapper(didChangeState: _state)
|
||||
}
|
||||
}
|
||||
}
|
||||
fileprivate var pausedForLoad: Bool = false
|
||||
|
||||
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()
|
||||
|
||||
self.playerObserver.delegate = self
|
||||
self.playerTimeObserver.delegate = self
|
||||
self.playerItemNotificationObserver.delegate = self
|
||||
self.playerItemObserver.delegate = self
|
||||
playerTimeObserver = AVPlayerTimeObserver(periodicObserverTimeInterval: timeEventFrequency.getTime())
|
||||
playerTimeObserver.player = avPlayer
|
||||
|
||||
playerObserver.player = avPlayer
|
||||
playerObserver.delegate = self
|
||||
playerTimeObserver.delegate = self
|
||||
playerItemNotificationObserver.delegate = self
|
||||
playerItemObserver.delegate = self
|
||||
|
||||
// disabled since we're not making use of video playback
|
||||
self.avPlayer.allowsExternalPlayback = false;
|
||||
avPlayer.allowsExternalPlayback = false;
|
||||
|
||||
playerTimeObserver.registerForPeriodicTimeEvents()
|
||||
}
|
||||
|
||||
// MARK: - AVPlayerWrapperProtocol
|
||||
|
||||
var state: AVPlayerWrapperState {
|
||||
return _state
|
||||
}
|
||||
|
||||
var reasonForWaitingToPlay: AVPlayer.WaitingReason? {
|
||||
return avPlayer.reasonForWaitingToPlay
|
||||
}
|
||||
|
||||
var currentItem: AVPlayerItem? {
|
||||
return avPlayer.currentItem
|
||||
}
|
||||
|
||||
var _pendingAsset: AVAsset? = nil
|
||||
|
||||
var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return avPlayer.automaticallyWaitsToMinimizeStalling }
|
||||
set { avPlayer.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
|
||||
fileprivate(set) var state: AVPlayerWrapperState = AVPlayerWrapperState.idle {
|
||||
didSet {
|
||||
if oldValue != state {
|
||||
delegate?.AVWrapper(didChangeState: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var willPlayWhenReady: Bool {
|
||||
return _playWhenReady
|
||||
/**
|
||||
True if the last call to load(from:playWhenReady) had playWhenReady=true.
|
||||
*/
|
||||
fileprivate(set) var playWhenReady: Bool = true
|
||||
|
||||
var currentItem: AVPlayerItem? {
|
||||
avPlayer.currentItem
|
||||
}
|
||||
|
||||
var currentTime: TimeInterval {
|
||||
@@ -106,7 +85,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
else if let seconds = currentItem?.duration.seconds, !seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
else if let seconds = currentItem?.loadedTimeRanges.first?.timeRangeValue.duration.seconds,
|
||||
else if let seconds = currentItem?.seekableTimeRanges.last?.timeRangeValue.duration.seconds,
|
||||
!seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
@@ -114,41 +93,50 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
}
|
||||
|
||||
var bufferedPosition: TimeInterval {
|
||||
return currentItem?.loadedTimeRanges.last?.timeRangeValue.end.seconds ?? 0
|
||||
currentItem?.loadedTimeRanges.last?.timeRangeValue.end.seconds ?? 0
|
||||
}
|
||||
|
||||
|
||||
var reasonForWaitingToPlay: AVPlayer.WaitingReason? {
|
||||
avPlayer.reasonForWaitingToPlay
|
||||
}
|
||||
|
||||
var rate: Float {
|
||||
get { avPlayer.rate }
|
||||
set { avPlayer.rate = newValue }
|
||||
}
|
||||
|
||||
weak var delegate: AVPlayerWrapperDelegate? = nil
|
||||
|
||||
var bufferDuration: TimeInterval = 0
|
||||
|
||||
|
||||
var timeEventFrequency: TimeEventFrequency = .everySecond {
|
||||
didSet {
|
||||
playerTimeObserver.periodicObserverTimeInterval = timeEventFrequency.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
var rate: Float {
|
||||
get { return avPlayer.rate }
|
||||
set { avPlayer.rate = newValue }
|
||||
}
|
||||
|
||||
var volume: Float {
|
||||
get { return avPlayer.volume }
|
||||
get { avPlayer.volume }
|
||||
set { avPlayer.volume = newValue }
|
||||
}
|
||||
|
||||
var isMuted: Bool {
|
||||
get { return avPlayer.isMuted }
|
||||
get { avPlayer.isMuted }
|
||||
set { avPlayer.isMuted = newValue }
|
||||
}
|
||||
|
||||
var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { avPlayer.automaticallyWaitsToMinimizeStalling }
|
||||
set { avPlayer.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
func play() {
|
||||
_playWhenReady = true
|
||||
playWhenReady = true
|
||||
avPlayer.play()
|
||||
}
|
||||
|
||||
func pause() {
|
||||
_playWhenReady = false
|
||||
playWhenReady = false
|
||||
avPlayer.pause()
|
||||
}
|
||||
|
||||
@@ -170,13 +158,13 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
|
||||
func seek(to seconds: TimeInterval) {
|
||||
// if the player is loading then we need to defer seeking until it's ready.
|
||||
if (self._state == AVPlayerWrapperState.loading) {
|
||||
self._initialTime = seconds
|
||||
if (state == AVPlayerWrapperState.loading) {
|
||||
initialTime = seconds
|
||||
} else {
|
||||
avPlayer.seek(to: CMTimeMakeWithSeconds(seconds, preferredTimescale: 1000)) { (finished) in
|
||||
if let _ = self._initialTime {
|
||||
self._initialTime = nil
|
||||
if self._playWhenReady {
|
||||
if let _ = self.initialTime {
|
||||
self.initialTime = nil
|
||||
if self.playWhenReady {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
@@ -189,27 +177,24 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool, options: [String: Any]? = nil) {
|
||||
reset(soft: true)
|
||||
_playWhenReady = playWhenReady
|
||||
self.playWhenReady = playWhenReady
|
||||
|
||||
if currentItem?.status == .failed {
|
||||
recreateAVPlayer()
|
||||
}
|
||||
|
||||
pendingAsset = AVURLAsset(url: url, options: options)
|
||||
|
||||
self._pendingAsset = AVURLAsset(url: url, options: options)
|
||||
|
||||
if let pendingAsset = _pendingAsset {
|
||||
self._state = .loading
|
||||
if let pendingAsset = pendingAsset {
|
||||
state = .loading
|
||||
pendingAsset.loadValuesAsynchronously(forKeys: [Constants.assetPlayableKey], completionHandler: { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
guard let self = self else { return }
|
||||
|
||||
var error: NSError? = nil
|
||||
let status = pendingAsset.statusOfValue(forKey: Constants.assetPlayableKey, error: &error)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let isPendingAsset = (self._pendingAsset != nil && pendingAsset.isEqual(self._pendingAsset))
|
||||
let isPendingAsset = (self.pendingAsset != nil && pendingAsset.isEqual(self.pendingAsset))
|
||||
switch status {
|
||||
case .loaded:
|
||||
if isPendingAsset {
|
||||
@@ -241,7 +226,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
case .failed:
|
||||
if isPendingAsset {
|
||||
self.delegate?.AVWrapper(failedWithError: error)
|
||||
self._pendingAsset = nil
|
||||
self.pendingAsset = nil
|
||||
}
|
||||
break
|
||||
|
||||
@@ -257,10 +242,10 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
}
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval? = nil, options: [String : Any]? = nil) {
|
||||
_initialTime = initialTime
|
||||
self.initialTime = initialTime
|
||||
|
||||
_pausedForLoad = true
|
||||
self.pause()
|
||||
pausedForLoad = true
|
||||
pause()
|
||||
|
||||
self.load(from: url, playWhenReady: playWhenReady, options: options)
|
||||
}
|
||||
@@ -271,9 +256,9 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
playerItemObserver.stopObservingCurrentItem()
|
||||
playerTimeObserver.unregisterForBoundaryTimeEvents()
|
||||
playerItemNotificationObserver.stopObservingCurrentItem()
|
||||
|
||||
self._pendingAsset?.cancelLoading()
|
||||
self._pendingAsset = nil
|
||||
|
||||
pendingAsset?.cancelLoading()
|
||||
pendingAsset = nil
|
||||
|
||||
if !soft {
|
||||
avPlayer.replaceCurrentItem(with: nil)
|
||||
@@ -300,16 +285,15 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
|
||||
switch status {
|
||||
case .paused:
|
||||
if currentItem == nil {
|
||||
_state = .idle
|
||||
state = .idle
|
||||
}
|
||||
else if _pausedForLoad == true {}
|
||||
else {
|
||||
self._state = .paused
|
||||
else if pausedForLoad != true {
|
||||
state = .paused
|
||||
}
|
||||
case .waitingToPlayAtSpecifiedRate:
|
||||
self._state = .buffering
|
||||
state = .buffering
|
||||
case .playing:
|
||||
self._state = .playing
|
||||
state = .playing
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
@@ -318,18 +302,18 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
|
||||
func player(statusDidChange status: AVPlayer.Status) {
|
||||
switch status {
|
||||
case .readyToPlay:
|
||||
self._state = .ready
|
||||
self._pausedForLoad = false
|
||||
if _playWhenReady && (_initialTime ?? 0) == 0 {
|
||||
self.play()
|
||||
state = .ready
|
||||
pausedForLoad = false
|
||||
if playWhenReady && (initialTime ?? 0) == 0 {
|
||||
play()
|
||||
}
|
||||
else if let initialTime = _initialTime {
|
||||
self.seek(to: initialTime)
|
||||
else if let initialTime = initialTime {
|
||||
seek(to: initialTime)
|
||||
}
|
||||
break
|
||||
|
||||
case .failed:
|
||||
self.delegate?.AVWrapper(failedWithError: avPlayer.error)
|
||||
delegate?.AVWrapper(failedWithError: avPlayer.error)
|
||||
break
|
||||
|
||||
case .unknown:
|
||||
@@ -338,7 +322,6 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AVPlayerWrapper: AVPlayerTimeObserverDelegate {
|
||||
@@ -346,11 +329,11 @@ extension AVPlayerWrapper: AVPlayerTimeObserverDelegate {
|
||||
// MARK: - AVPlayerTimeObserverDelegate
|
||||
|
||||
func audioDidStart() {
|
||||
self._state = .playing
|
||||
state = .playing
|
||||
}
|
||||
|
||||
func timeEvent(time: CMTime) {
|
||||
self.delegate?.AVWrapper(secondsElapsed: time.seconds)
|
||||
delegate?.AVWrapper(secondsElapsed: time.seconds)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -370,11 +353,11 @@ extension AVPlayerWrapper: AVPlayerItemObserverDelegate {
|
||||
// MARK: - AVPlayerItemObserverDelegate
|
||||
|
||||
func item(didUpdateDuration duration: Double) {
|
||||
self.delegate?.AVWrapper(didUpdateDuration: duration)
|
||||
delegate?.AVWrapper(didUpdateDuration: duration)
|
||||
}
|
||||
|
||||
func item(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
self.delegate?.AVWrapper(didReceiveMetadata: metadata)
|
||||
delegate?.AVWrapper(didReceiveMetadata: metadata)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import AVFoundation
|
||||
|
||||
|
||||
protocol AVPlayerWrapperProtocol: AnyObject {
|
||||
|
||||
|
||||
var state: AVPlayerWrapperState { get }
|
||||
|
||||
var willPlayWhenReady: Bool { get }
|
||||
var playWhenReady: Bool { get }
|
||||
|
||||
var currentItem: AVPlayerItem? { get }
|
||||
|
||||
@@ -54,5 +54,4 @@ protocol AVPlayerWrapperProtocol: AnyObject {
|
||||
func load(from url: URL, playWhenReady: Bool, options: [String: Any]?)
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval?, options: [String: Any]?)
|
||||
|
||||
}
|
||||
|
||||
@@ -66,23 +66,23 @@ public class DefaultAudioItem: AudioItem {
|
||||
}
|
||||
|
||||
public func getSourceUrl() -> String {
|
||||
return audioUrl
|
||||
audioUrl
|
||||
}
|
||||
|
||||
public func getArtist() -> String? {
|
||||
return artist
|
||||
artist
|
||||
}
|
||||
|
||||
public func getTitle() -> String? {
|
||||
return title
|
||||
title
|
||||
}
|
||||
|
||||
public func getAlbumTitle() -> String? {
|
||||
return albumTitle
|
||||
albumTitle
|
||||
}
|
||||
|
||||
public func getSourceType() -> SourceType {
|
||||
return sourceType
|
||||
sourceType
|
||||
}
|
||||
|
||||
public func getArtwork(_ handler: @escaping (UIImage?) -> Void) {
|
||||
@@ -97,17 +97,17 @@ public class DefaultAudioItemTimePitching: DefaultAudioItem, TimePitching {
|
||||
public var pitchAlgorithmType: AVAudioTimePitchAlgorithm
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
self.pitchAlgorithmType = AVAudioTimePitchAlgorithm.lowQualityZeroLatency
|
||||
pitchAlgorithmType = AVAudioTimePitchAlgorithm.timeDomain
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm) {
|
||||
self.pitchAlgorithmType = audioTimePitchAlgorithm
|
||||
pitchAlgorithmType = audioTimePitchAlgorithm
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm {
|
||||
return pitchAlgorithmType
|
||||
pitchAlgorithmType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public class DefaultAudioItemInitialTime: DefaultAudioItem, InitialTiming {
|
||||
public var initialTime: TimeInterval
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
self.initialTime = 0.0
|
||||
initialTime = 0.0
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class DefaultAudioItemInitialTime: DefaultAudioItem, InitialTiming {
|
||||
}
|
||||
|
||||
public func getInitialTime() -> TimeInterval {
|
||||
return initialTime
|
||||
initialTime
|
||||
}
|
||||
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class DefaultAudioItemAssetOptionsProviding: DefaultAudioItem, AssetOptio
|
||||
public var options: [String: Any]
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
self.options = [:]
|
||||
options = [:]
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
@@ -148,7 +148,6 @@ public class DefaultAudioItemAssetOptionsProviding: DefaultAudioItem, AssetOptio
|
||||
}
|
||||
|
||||
public func getAssetOptions() -> [String: Any] {
|
||||
return options
|
||||
options
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,22 +11,15 @@ import MediaPlayer
|
||||
public typealias AudioPlayerState = AVPlayerWrapperState
|
||||
|
||||
public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
private var _wrapper: AVPlayerWrapperProtocol
|
||||
|
||||
|
||||
/// The wrapper around the underlying AVPlayer
|
||||
var wrapper: AVPlayerWrapperProtocol {
|
||||
return _wrapper
|
||||
}
|
||||
let wrapper: AVPlayerWrapperProtocol = AVPlayerWrapper()
|
||||
|
||||
public let nowPlayingInfoController: NowPlayingInfoControllerProtocol
|
||||
public let remoteCommandController: RemoteCommandController
|
||||
public let event = EventHolder()
|
||||
|
||||
var _currentItem: AudioItem?
|
||||
public var currentItem: AudioItem? {
|
||||
return _currentItem
|
||||
}
|
||||
private(set) var currentItem: AudioItem?
|
||||
|
||||
/**
|
||||
Set this to false to disable automatic updating of now playing info for control center and lock screen.
|
||||
@@ -37,7 +30,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
Controls the time pitch algorithm applied to each item loaded into the player.
|
||||
If the loaded `AudioItem` conforms to `TimePitcher`-protocol this will be overriden.
|
||||
*/
|
||||
public var audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.lowQualityZeroLatency
|
||||
public var audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.timeDomain
|
||||
|
||||
/**
|
||||
Default remote commands to use for each playing item
|
||||
@@ -54,35 +47,35 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
// MARK: - Getters from AVPlayerWrapper
|
||||
|
||||
internal var willPlayWhenReady: Bool {
|
||||
return wrapper.willPlayWhenReady
|
||||
wrapper.playWhenReady
|
||||
}
|
||||
|
||||
/**
|
||||
The elapsed playback time of the current item.
|
||||
*/
|
||||
public var currentTime: Double {
|
||||
return wrapper.currentTime
|
||||
wrapper.currentTime
|
||||
}
|
||||
|
||||
/**
|
||||
The duration of the current AudioItem.
|
||||
*/
|
||||
public var duration: Double {
|
||||
return wrapper.duration
|
||||
wrapper.duration
|
||||
}
|
||||
|
||||
/**
|
||||
The bufferedPosition of the current AudioItem.
|
||||
*/
|
||||
public var bufferedPosition: Double {
|
||||
return wrapper.bufferedPosition
|
||||
wrapper.bufferedPosition
|
||||
}
|
||||
|
||||
/**
|
||||
The current state of the underlying `AudioPlayer`.
|
||||
*/
|
||||
public var playerState: AudioPlayerState {
|
||||
return wrapper.state
|
||||
wrapper.state
|
||||
}
|
||||
|
||||
// MARK: - Setters for AVPlayerWrapper
|
||||
@@ -95,45 +88,45 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
- Important: This setting will have no effect if `automaticallyWaitsToMinimizeStalling` is set to `true` in the AVPlayer
|
||||
*/
|
||||
public var bufferDuration: TimeInterval {
|
||||
get { return wrapper.bufferDuration }
|
||||
set { _wrapper.bufferDuration = newValue }
|
||||
get { wrapper.bufferDuration }
|
||||
set { wrapper.bufferDuration = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
Set this to decide how often the player should call the delegate with time progress events.
|
||||
*/
|
||||
public var timeEventFrequency: TimeEventFrequency {
|
||||
get { return wrapper.timeEventFrequency }
|
||||
set { _wrapper.timeEventFrequency = newValue }
|
||||
get { wrapper.timeEventFrequency }
|
||||
set { wrapper.timeEventFrequency = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates whether the player should automatically delay playback in order to minimize stalling
|
||||
*/
|
||||
public var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return wrapper.automaticallyWaitsToMinimizeStalling }
|
||||
set { _wrapper.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
get { wrapper.automaticallyWaitsToMinimizeStalling }
|
||||
set { wrapper.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
public var volume: Float {
|
||||
get { return wrapper.volume }
|
||||
set { _wrapper.volume = newValue }
|
||||
get { wrapper.volume }
|
||||
set { wrapper.volume = newValue }
|
||||
}
|
||||
|
||||
public var isMuted: Bool {
|
||||
get { return wrapper.isMuted }
|
||||
set { _wrapper.isMuted = newValue }
|
||||
get { wrapper.isMuted }
|
||||
set { wrapper.isMuted = newValue }
|
||||
}
|
||||
|
||||
private var _rate: Float = 1.0
|
||||
public var rate: Float {
|
||||
get { return _rate }
|
||||
get { _rate }
|
||||
set {
|
||||
_rate = newValue
|
||||
|
||||
// Only set the rate on the wrapper if it is already playing.
|
||||
if _wrapper.rate > 0 {
|
||||
_wrapper.rate = newValue
|
||||
if wrapper.rate > 0 {
|
||||
wrapper.rate = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,11 +140,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
*/
|
||||
public init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
|
||||
remoteCommandController: RemoteCommandController = RemoteCommandController()) {
|
||||
self._wrapper = AVPlayerWrapper()
|
||||
self.nowPlayingInfoController = nowPlayingInfoController
|
||||
self.remoteCommandController = remoteCommandController
|
||||
|
||||
self._wrapper.delegate = self
|
||||
wrapper.delegate = self
|
||||
self.remoteCommandController.audioPlayer = self
|
||||
}
|
||||
|
||||
@@ -182,10 +174,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
initialTime: (item as? InitialTiming)?.getInitialTime(),
|
||||
options:(item as? AssetOptionsProviding)?.getAssetOptions())
|
||||
|
||||
self._currentItem = item
|
||||
currentItem = item
|
||||
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
self.loadNowPlayingMetaValues()
|
||||
loadNowPlayingMetaValues()
|
||||
}
|
||||
enableRemoteCommands(forItem: item)
|
||||
}
|
||||
@@ -194,30 +186,30 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
Toggle playback status.
|
||||
*/
|
||||
public func togglePlaying() {
|
||||
self.wrapper.togglePlaying()
|
||||
wrapper.togglePlaying()
|
||||
}
|
||||
|
||||
/**
|
||||
Start playback
|
||||
*/
|
||||
public func play() {
|
||||
self.wrapper.play()
|
||||
wrapper.play()
|
||||
}
|
||||
|
||||
/**
|
||||
Pause playback
|
||||
*/
|
||||
public func pause() {
|
||||
self.wrapper.pause()
|
||||
wrapper.pause()
|
||||
}
|
||||
|
||||
/**
|
||||
Stop playback, resetting the player.
|
||||
*/
|
||||
public func stop() {
|
||||
self.reset()
|
||||
self.wrapper.stop()
|
||||
self.event.playbackEnd.emit(data: .playerStopped)
|
||||
reset()
|
||||
wrapper.stop()
|
||||
event.playbackEnd.emit(data: .playerStopped)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,15 +217,15 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
*/
|
||||
public func seek(to seconds: TimeInterval) {
|
||||
if automaticallyUpdateNowPlayingInfo {
|
||||
self.updateNowPlayingCurrentTime(seconds)
|
||||
updateNowPlayingCurrentTime(seconds)
|
||||
}
|
||||
self.wrapper.seek(to: seconds)
|
||||
wrapper.seek(to: seconds)
|
||||
}
|
||||
|
||||
// MARK: - Remote Command Center
|
||||
|
||||
func enableRemoteCommands(_ commands: [RemoteCommand]) {
|
||||
self.remoteCommandController.enable(commands: commands)
|
||||
remoteCommandController.enable(commands: commands)
|
||||
}
|
||||
|
||||
func enableRemoteCommands(forItem item: AudioItem) {
|
||||
@@ -306,10 +298,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
private func loadArtwork(forItem item: AudioItem) {
|
||||
item.getArtwork { (image) in
|
||||
if let image = image {
|
||||
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (size) -> UIImage in
|
||||
return image
|
||||
})
|
||||
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { _ in image })
|
||||
self.nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(artwork))
|
||||
} else {
|
||||
self.nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +309,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
// MARK: - Private
|
||||
|
||||
func reset() {
|
||||
self._currentItem = nil
|
||||
currentItem = nil
|
||||
}
|
||||
|
||||
private func setTimePitchingAlgorithmForCurrentItem() {
|
||||
@@ -340,7 +332,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
setTimePitchingAlgorithmForCurrentItem()
|
||||
case .playing:
|
||||
// When a track starts playing, reset the rate to the stored rate
|
||||
self.rate = _rate;
|
||||
rate = _rate;
|
||||
fallthrough
|
||||
case .paused:
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
@@ -348,38 +340,37 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
}
|
||||
default: break
|
||||
}
|
||||
self.event.stateChange.emit(data: state)
|
||||
event.stateChange.emit(data: state)
|
||||
}
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
self.event.secondElapse.emit(data: seconds)
|
||||
event.secondElapse.emit(data: seconds)
|
||||
}
|
||||
|
||||
func AVWrapper(failedWithError error: Error?) {
|
||||
self.event.fail.emit(data: error)
|
||||
event.fail.emit(data: error)
|
||||
}
|
||||
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
|
||||
if !didFinish && automaticallyUpdateNowPlayingInfo {
|
||||
updateNowPlayingCurrentTime(currentTime)
|
||||
}
|
||||
self.event.seek.emit(data: (seconds, didFinish))
|
||||
event.seek.emit(data: (seconds, didFinish))
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
self.event.updateDuration.emit(data: duration)
|
||||
event.updateDuration.emit(data: duration)
|
||||
}
|
||||
|
||||
func AVWrapper(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
self.event.receiveMetadata.emit(data: metadata)
|
||||
event.receiveMetadata.emit(data: metadata)
|
||||
}
|
||||
|
||||
func AVWrapperItemDidPlayToEndTime() {
|
||||
self.event.playbackEnd.emit(data: .playedUntilEnd)
|
||||
event.playbackEnd.emit(data: .playedUntilEnd)
|
||||
}
|
||||
|
||||
func AVWrapperDidRecreateAVPlayer() {
|
||||
self.event.didRecreateAVPlayer.emit(data: ())
|
||||
event.didRecreateAVPlayer.emit(data: ())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class AudioSessionController {
|
||||
True if another app is currently playing audio.
|
||||
*/
|
||||
public var isOtherAudioPlaying: Bool {
|
||||
return audioSession.isOtherAudioPlaying
|
||||
audioSession.isOtherAudioPlaying
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,7 @@ public class AudioSessionController {
|
||||
Set this to false to disable the behaviour.
|
||||
*/
|
||||
public var isObservingForInterruptions: Bool {
|
||||
get {
|
||||
return _isObservingForInterruptions
|
||||
}
|
||||
get { _isObservingForInterruptions }
|
||||
set {
|
||||
if newValue == _isObservingForInterruptions {
|
||||
return
|
||||
@@ -117,15 +115,15 @@ public class AudioSessionController {
|
||||
|
||||
switch type {
|
||||
case .began:
|
||||
self.delegate?.handleInterruption(type: .began)
|
||||
delegate?.handleInterruption(type: .began)
|
||||
case .ended:
|
||||
guard let typeValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
|
||||
self.delegate?.handleInterruption(type: .ended(shouldResume: false))
|
||||
delegate?.handleInterruption(type: .ended(shouldResume: false))
|
||||
return
|
||||
}
|
||||
|
||||
let options = AVAudioSession.InterruptionOptions(rawValue: typeValue)
|
||||
self.delegate?.handleInterruption(type: .ended(shouldResume: options.contains(.shouldResume)))
|
||||
delegate?.handleInterruption(type: .ended(shouldResume: options.contains(.shouldResume)))
|
||||
@unknown default: return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import MediaPlayer
|
||||
|
||||
extension AudioPlayer {
|
||||
|
||||
public typealias StateChangeEventData = (AudioPlayerState)
|
||||
public typealias PlaybackEndEventData = (PlaybackEndedReason)
|
||||
public typealias SecondElapseEventData = (TimeInterval)
|
||||
public typealias FailEventData = (Error?)
|
||||
public typealias StateChangeEventData = AudioPlayerState
|
||||
public typealias PlaybackEndEventData = PlaybackEndedReason
|
||||
public typealias SecondElapseEventData = TimeInterval
|
||||
public typealias FailEventData = Error?
|
||||
public typealias SeekEventData = (seconds: Int, didFinish: Bool)
|
||||
public typealias UpdateDurationEventData = (Double)
|
||||
public typealias MetadataEventData = ([AVTimedMetadataGroup])
|
||||
public typealias UpdateDurationEventData = Double
|
||||
public typealias MetadataEventData = [AVTimedMetadataGroup]
|
||||
public typealias DidRecreateAVPlayerEventData = ()
|
||||
public typealias QueueIndexEventData = (previousIndex: Int?, newIndex: Int?)
|
||||
|
||||
@@ -90,7 +90,7 @@ extension AudioPlayer {
|
||||
|
||||
init<Listener: AnyObject>(listener: Listener, closure: @escaping EventClosure<EventData>) {
|
||||
self.listener = listener
|
||||
self.invoke = { [weak listener] (data: EventData) in
|
||||
invoke = { [weak listener] (data: EventData) in
|
||||
guard let _ = listener else {
|
||||
return false
|
||||
}
|
||||
@@ -133,9 +133,7 @@ extension AudioPlayer {
|
||||
func emit(data: EventData) {
|
||||
eventQueue.async {
|
||||
self.invokersSemaphore.wait()
|
||||
self.invokers = self.invokers.filter({ (invoker) -> Bool in
|
||||
return invoker.invoke(data)
|
||||
})
|
||||
self.invokers = self.invokers.filter { $0.invoke(data) }
|
||||
self.invokersSemaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,31 +11,23 @@ import MediaPlayer
|
||||
public class NowPlayingInfoController: NowPlayingInfoControllerProtocol {
|
||||
private let concurrentInfoQueue: DispatchQueueType
|
||||
|
||||
private var _infoCenter: NowPlayingInfoCenter
|
||||
private var _info: [String: Any] = [:]
|
||||
|
||||
var infoCenter: NowPlayingInfoCenter {
|
||||
return _infoCenter
|
||||
}
|
||||
|
||||
var info: [String: Any] {
|
||||
return _info
|
||||
}
|
||||
private(set) var infoCenter: NowPlayingInfoCenter
|
||||
private(set) var info: [String: Any] = [:]
|
||||
|
||||
public required init() {
|
||||
self.concurrentInfoQueue = DispatchQueue(label: "com.doublesymmetry.nowPlayingInfoQueue", attributes: .concurrent)
|
||||
self._infoCenter = MPNowPlayingInfoCenter.default()
|
||||
concurrentInfoQueue = DispatchQueue(label: "com.doublesymmetry.nowPlayingInfoQueue", attributes: .concurrent)
|
||||
infoCenter = MPNowPlayingInfoCenter.default()
|
||||
}
|
||||
|
||||
/// Used for testing purposes.
|
||||
public required init(dispatchQueue: DispatchQueueType, infoCenter: NowPlayingInfoCenter) {
|
||||
self.concurrentInfoQueue = dispatchQueue
|
||||
self._infoCenter = infoCenter
|
||||
concurrentInfoQueue = dispatchQueue
|
||||
self.infoCenter = infoCenter
|
||||
}
|
||||
|
||||
public required init(infoCenter: NowPlayingInfoCenter) {
|
||||
self.concurrentInfoQueue = DispatchQueue(label: "com.doublesymmetry.nowPlayingInfoQueue", attributes: .concurrent)
|
||||
self._infoCenter = infoCenter
|
||||
concurrentInfoQueue = DispatchQueue(label: "com.doublesymmetry.nowPlayingInfoQueue", attributes: .concurrent)
|
||||
self.infoCenter = infoCenter
|
||||
}
|
||||
|
||||
public func set(keyValues: [NowPlayingInfoKeyValue]) {
|
||||
@@ -43,10 +35,10 @@ public class NowPlayingInfoController: NowPlayingInfoControllerProtocol {
|
||||
guard let self = self else { return }
|
||||
|
||||
keyValues.forEach { (keyValue) in
|
||||
self._info[keyValue.getKey()] = keyValue.getValue()
|
||||
self.info[keyValue.getKey()] = keyValue.getValue()
|
||||
}
|
||||
|
||||
self._infoCenter.nowPlayingInfo = self._info
|
||||
self.infoCenter.nowPlayingInfo = self.info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +46,8 @@ public class NowPlayingInfoController: NowPlayingInfoControllerProtocol {
|
||||
concurrentInfoQueue.async(flags: .barrier) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self._info[keyValue.getKey()] = keyValue.getValue()
|
||||
self._infoCenter.nowPlayingInfo = self._info
|
||||
self.info[keyValue.getKey()] = keyValue.getValue()
|
||||
self.infoCenter.nowPlayingInfo = self.info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +55,8 @@ public class NowPlayingInfoController: NowPlayingInfoControllerProtocol {
|
||||
concurrentInfoQueue.async(flags: .barrier) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self._info = [:]
|
||||
self._infoCenter.nowPlayingInfo = self._info
|
||||
self.info = [:]
|
||||
self.infoCenter.nowPlayingInfo = self.info
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
|
||||
protocol AVPlayerItemNotificationObserverDelegate: class {
|
||||
protocol AVPlayerItemNotificationObserverDelegate: AnyObject {
|
||||
func itemDidPlayToEndTime()
|
||||
}
|
||||
|
||||
@@ -51,9 +50,9 @@ class AVPlayerItemNotificationObserver {
|
||||
guard let observingItem = observingItem, isObserving else {
|
||||
return
|
||||
}
|
||||
self.notificationCenter.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: observingItem)
|
||||
notificationCenter.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: observingItem)
|
||||
self.observingItem = nil
|
||||
self.isObserving = false
|
||||
isObserving = false
|
||||
}
|
||||
|
||||
@objc private func itemDidPlayToEndTime() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
protocol AVPlayerObserverDelegate: class {
|
||||
protocol AVPlayerObserverDelegate: AnyObject {
|
||||
|
||||
/**
|
||||
Called when the AVPlayer.status changes.
|
||||
@@ -20,37 +20,36 @@ protocol AVPlayerObserverDelegate: class {
|
||||
Called when the AVPlayer.timeControlStatus changes.
|
||||
*/
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Observing an AVPlayers status changes.
|
||||
*/
|
||||
class AVPlayerObserver: NSObject {
|
||||
|
||||
|
||||
private static var context = 0
|
||||
private let main: DispatchQueue = .main
|
||||
|
||||
|
||||
private struct AVPlayerKeyPath {
|
||||
static let status = #keyPath(AVPlayer.status)
|
||||
static let timeControlStatus = #keyPath(AVPlayer.timeControlStatus)
|
||||
}
|
||||
|
||||
|
||||
private let statusChangeOptions: NSKeyValueObservingOptions = [.new, .initial]
|
||||
private let timeControlStatusChangeOptions: NSKeyValueObservingOptions = [.new]
|
||||
private(set) var isObserving: Bool = false
|
||||
|
||||
|
||||
weak var delegate: AVPlayerObserverDelegate?
|
||||
weak var player: AVPlayer? {
|
||||
willSet {
|
||||
self.stopObserving()
|
||||
stopObserving()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
self.stopObserving()
|
||||
stopObserving()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Start receiving events from this observer.
|
||||
*/
|
||||
@@ -58,52 +57,49 @@ class AVPlayerObserver: NSObject {
|
||||
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)
|
||||
stopObserving()
|
||||
isObserving = true
|
||||
player.addObserver(self, forKeyPath: AVPlayerKeyPath.status, options: statusChangeOptions, context: &AVPlayerObserver.context)
|
||||
player.addObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, options: timeControlStatusChangeOptions, context: &AVPlayerObserver.context)
|
||||
}
|
||||
|
||||
|
||||
func stopObserving() {
|
||||
guard let player = player, isObserving else {
|
||||
return
|
||||
}
|
||||
player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context)
|
||||
player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context)
|
||||
self.isObserving = false
|
||||
isObserving = false
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||
guard context == &AVPlayerObserver.context, let observedKeyPath = keyPath else {
|
||||
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch observedKeyPath {
|
||||
|
||||
case AVPlayerKeyPath.status:
|
||||
self.handleStatusChange(change)
|
||||
|
||||
handleStatusChange(change)
|
||||
|
||||
case AVPlayerKeyPath.timeControlStatus:
|
||||
self.handleTimeControlStatusChange(change)
|
||||
|
||||
handleTimeControlStatusChange(change)
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handleStatusChange(_ change: [NSKeyValueChangeKey: Any]?) {
|
||||
let status: AVPlayer.Status
|
||||
if let statusNumber = change?[.newKey] as? NSNumber {
|
||||
status = AVPlayer.Status(rawValue: statusNumber.intValue)!
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
status = .unknown
|
||||
}
|
||||
delegate?.player(statusDidChange: status)
|
||||
}
|
||||
|
||||
|
||||
private func handleTimeControlStatusChange(_ change: [NSKeyValueChangeKey: Any]?) {
|
||||
let status: AVPlayer.TimeControlStatus
|
||||
if let statusNumber = change?[.newKey] as? NSNumber {
|
||||
@@ -111,5 +107,4 @@ class AVPlayerObserver: NSObject {
|
||||
delegate?.player(didChangeTimeControlStatus: status)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
protocol AVPlayerTimeObserverDelegate: class {
|
||||
protocol AVPlayerTimeObserverDelegate: AnyObject {
|
||||
func audioDidStart()
|
||||
func timeEvent(time: CMTime)
|
||||
}
|
||||
|
||||
@@ -16,55 +16,47 @@ class QueueManager<T> {
|
||||
|
||||
weak var delegate: QueueManagerDelegate? = nil
|
||||
|
||||
private var _items: [T] = [] {
|
||||
/**
|
||||
All items held by the queue.
|
||||
*/
|
||||
private(set) var items: [T] = [] {
|
||||
didSet {
|
||||
if oldValue.count == 0 && _items.count > 0 && _currentIndex == 0 {
|
||||
if oldValue.count == 0 && items.count > 0 && currentIndex == 0 {
|
||||
delegate?.onReceivedFirstItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
All items held by the queue.
|
||||
*/
|
||||
public var items: [T] {
|
||||
return _items
|
||||
}
|
||||
|
||||
public var nextItems: [T] {
|
||||
guard _currentIndex + 1 < _items.count else {
|
||||
guard currentIndex + 1 < items.count else {
|
||||
return []
|
||||
}
|
||||
return Array(_items[_currentIndex + 1..<_items.count])
|
||||
return Array(items[currentIndex + 1..<items.count])
|
||||
}
|
||||
|
||||
public var previousItems: [T] {
|
||||
if (_currentIndex == 0) {
|
||||
if (currentIndex == 0) {
|
||||
return []
|
||||
}
|
||||
return Array(_items[0..<_currentIndex])
|
||||
return Array(items[0..<currentIndex])
|
||||
}
|
||||
|
||||
private var _currentIndex: Int = 0 {
|
||||
didSet {
|
||||
delegate?.onCurrentIndexChanged(oldIndex: oldValue, newIndex: _currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The index of the current item.
|
||||
Will be populated event though there is no current item (When the queue is empty).
|
||||
*/
|
||||
public var currentIndex: Int {
|
||||
return _currentIndex
|
||||
private(set) var currentIndex: Int = 0 {
|
||||
didSet {
|
||||
delegate?.onCurrentIndexChanged(oldIndex: oldValue, newIndex: currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The current item for the queue.
|
||||
*/
|
||||
public var current: T? {
|
||||
if _items.count > _currentIndex {
|
||||
return _items[_currentIndex]
|
||||
if items.count > currentIndex {
|
||||
return items[currentIndex]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -75,7 +67,7 @@ class QueueManager<T> {
|
||||
- parameter item: The `AudioItem` to be added.
|
||||
*/
|
||||
public func addItem(_ item: T) {
|
||||
_items.append(item)
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +76,7 @@ class QueueManager<T> {
|
||||
- parameter items: The `AudioItem`s to be added.
|
||||
*/
|
||||
public func addItems(_ items: [T]) {
|
||||
_items.append(contentsOf: items)
|
||||
self.items.append(contentsOf: items)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,13 +86,13 @@ class QueueManager<T> {
|
||||
- parameter at: The index to insert the items at.
|
||||
*/
|
||||
public func addItems(_ items: [T], at index: Int) throws {
|
||||
guard index >= 0 && _items.count >= index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index to insert at has to be non-negative and equal to or smaller than the number of items: (\(_items.count))")
|
||||
guard index >= 0 && self.items.count >= index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index to insert at has to be non-negative and equal to or smaller than the number of items: (\(items.count))")
|
||||
}
|
||||
|
||||
_items.insert(contentsOf: items, at: index)
|
||||
self.items.insert(contentsOf: items, at: index)
|
||||
|
||||
if (_currentIndex >= index && _items.count != 1) { _currentIndex = _currentIndex + items.count }
|
||||
if (currentIndex >= index && self.items.count != 1) { currentIndex += items.count }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,12 +104,12 @@ class QueueManager<T> {
|
||||
*/
|
||||
@discardableResult
|
||||
public func next() throws -> T {
|
||||
let nextIndex = _currentIndex + 1
|
||||
guard _items.count > nextIndex else {
|
||||
let nextIndex = currentIndex + 1
|
||||
guard items.count > nextIndex else {
|
||||
throw APError.QueueError.noNextItem
|
||||
}
|
||||
_currentIndex = nextIndex
|
||||
return _items[nextIndex]
|
||||
currentIndex = nextIndex
|
||||
return items[nextIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,12 +121,12 @@ class QueueManager<T> {
|
||||
*/
|
||||
@discardableResult
|
||||
public func previous() throws -> T {
|
||||
let previousIndex = _currentIndex - 1
|
||||
let previousIndex = currentIndex - 1
|
||||
guard previousIndex >= 0 else {
|
||||
throw APError.QueueError.noPreviousItem
|
||||
}
|
||||
_currentIndex = previousIndex
|
||||
return _items[previousIndex]
|
||||
currentIndex = previousIndex
|
||||
return items[previousIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,12 +143,12 @@ class QueueManager<T> {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot jump to the current item")
|
||||
}
|
||||
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(_items.count))")
|
||||
guard index >= 0 && items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(items.count))")
|
||||
}
|
||||
|
||||
_currentIndex = index
|
||||
return _items[index]
|
||||
currentIndex = index
|
||||
return items[index]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,17 +159,16 @@ class QueueManager<T> {
|
||||
- throws: `APError.QueueError`
|
||||
*/
|
||||
func moveItem(fromIndex: Int, toIndex: Int) throws {
|
||||
|
||||
guard fromIndex != _currentIndex else {
|
||||
guard fromIndex != currentIndex else {
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex cannot be equal to the current index.")
|
||||
}
|
||||
|
||||
guard fromIndex >= 0 && fromIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
guard fromIndex >= 0 && fromIndex < items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
}
|
||||
|
||||
guard toIndex >= 0 && toIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
guard toIndex >= 0 && toIndex < items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
}
|
||||
|
||||
let item = try removeItem(at: fromIndex)
|
||||
@@ -193,19 +184,19 @@ class QueueManager<T> {
|
||||
*/
|
||||
@discardableResult
|
||||
public func removeItem(at index: Int) throws -> T {
|
||||
guard index != _currentIndex else {
|
||||
guard index != currentIndex else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot remove the current item!")
|
||||
}
|
||||
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
guard index >= 0 && items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
}
|
||||
|
||||
if index < _currentIndex {
|
||||
_currentIndex = _currentIndex - 1
|
||||
if index < currentIndex {
|
||||
currentIndex -= 1
|
||||
}
|
||||
|
||||
return _items.remove(at: index)
|
||||
return items.remove(at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,10 +206,10 @@ class QueueManager<T> {
|
||||
*/
|
||||
public func replaceCurrentItem(with item: T) {
|
||||
if current == nil {
|
||||
self.addItem(item)
|
||||
addItem(item)
|
||||
}
|
||||
|
||||
self._items[_currentIndex] = item
|
||||
|
||||
items[currentIndex] = item
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,8 +218,8 @@ class QueueManager<T> {
|
||||
*/
|
||||
public func removePreviousItems() {
|
||||
guard currentIndex > 0 else { return }
|
||||
_items.removeSubrange(0..<_currentIndex)
|
||||
_currentIndex = 0
|
||||
items.removeSubrange(0..<currentIndex)
|
||||
currentIndex = 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,17 +227,17 @@ class QueueManager<T> {
|
||||
If no upcoming items exist, no action will be taken.
|
||||
*/
|
||||
public func removeUpcomingItems() {
|
||||
let nextIndex = _currentIndex + 1
|
||||
guard nextIndex < _items.count else { return }
|
||||
_items.removeSubrange(nextIndex..<_items.count)
|
||||
let nextIndex = currentIndex + 1
|
||||
guard nextIndex < items.count else { return }
|
||||
items.removeSubrange(nextIndex..<items.count)
|
||||
}
|
||||
|
||||
/**
|
||||
Removes all items for queue
|
||||
*/
|
||||
public func clearQueue() {
|
||||
_currentIndex = 0
|
||||
_items.removeAll()
|
||||
currentIndex = 0
|
||||
items.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
public var repeatMode: RepeatMode = .off
|
||||
|
||||
public override var currentItem: AudioItem? {
|
||||
return queueManager.current
|
||||
queueManager.current
|
||||
}
|
||||
|
||||
/**
|
||||
The index of the current item.
|
||||
*/
|
||||
public var currentIndex: Int {
|
||||
return queueManager.currentIndex
|
||||
queueManager.currentIndex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
*/
|
||||
public override func stop() {
|
||||
super.stop()
|
||||
self.event.queueIndex.emit(data: (currentIndex, nil))
|
||||
event.queueIndex.emit(data: (currentIndex, nil))
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
@@ -51,21 +51,21 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
All items currently in the queue.
|
||||
*/
|
||||
public var items: [AudioItem] {
|
||||
return queueManager.items
|
||||
queueManager.items
|
||||
}
|
||||
|
||||
/**
|
||||
The previous items held by the queue.
|
||||
*/
|
||||
public var previousItems: [AudioItem] {
|
||||
return queueManager.previousItems
|
||||
queueManager.previousItems
|
||||
}
|
||||
|
||||
/**
|
||||
The upcoming items in the queue.
|
||||
*/
|
||||
public var nextItems: [AudioItem] {
|
||||
return queueManager.nextItems
|
||||
queueManager.nextItems
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +89,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
public func add(item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItem(item)
|
||||
try self.load(item: item, playWhenReady: playWhenReady)
|
||||
try load(item: item, playWhenReady: playWhenReady)
|
||||
}
|
||||
else {
|
||||
queueManager.addItem(item)
|
||||
@@ -106,7 +106,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
public func add(items: [AudioItem], playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItems(items)
|
||||
try self.load(item: currentItem!, playWhenReady: playWhenReady)
|
||||
try load(item: currentItem!, playWhenReady: playWhenReady)
|
||||
}
|
||||
else {
|
||||
queueManager.addItems(items)
|
||||
@@ -128,7 +128,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
do {
|
||||
let nextItem = try queueManager.next()
|
||||
event.playbackEnd.emit(data: .skippedToNext)
|
||||
try self.load(item: nextItem, playWhenReady: shouldPlayWhenReady)
|
||||
try load(item: nextItem, playWhenReady: shouldPlayWhenReady)
|
||||
} catch APError.QueueError.noNextItem {
|
||||
if repeatMode == .queue {
|
||||
event.playbackEnd.emit(data: .skippedToNext)
|
||||
@@ -149,7 +149,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
|
||||
let previousItem = try queueManager.previous()
|
||||
event.playbackEnd.emit(data: .skippedToPrevious)
|
||||
try self.load(item: previousItem, playWhenReady: shouldPlayWhenReady)
|
||||
try load(item: previousItem, playWhenReady: shouldPlayWhenReady)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,9 +170,15 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
|
||||
let item = try queueManager.jump(to: index)
|
||||
event.playbackEnd.emit(data: .jumpedToIndex)
|
||||
try self.load(item: item, playWhenReady: playWhenReady)
|
||||
if (index == currentIndex) {
|
||||
seek(to: 0)
|
||||
playWhenReady ? play() : pause()
|
||||
onCurrentIndexChanged(oldIndex: index, newIndex: index)
|
||||
} else {
|
||||
let item = try queueManager.jump(to: index)
|
||||
event.playbackEnd.emit(data: .jumpedToIndex)
|
||||
try load(item: item, playWhenReady: playWhenReady)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,15 +215,16 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
case .off:
|
||||
do {
|
||||
let nextItem = try queueManager.next()
|
||||
try self.load(item: nextItem, playWhenReady: true)
|
||||
} catch { /* playback finished */ }
|
||||
try load(item: nextItem, playWhenReady: true)
|
||||
} catch {
|
||||
event.queueIndex.emit(data: (currentIndex, nil))
|
||||
}
|
||||
case .track:
|
||||
seek(to: 0)
|
||||
play()
|
||||
try? jumpToItem(atIndex: currentIndex, playWhenReady: true)
|
||||
case .queue:
|
||||
do {
|
||||
let nextItem = try queueManager.next()
|
||||
try self.load(item: nextItem, playWhenReady: true)
|
||||
try load(item: nextItem, playWhenReady: true)
|
||||
} catch {
|
||||
try? jumpToItem(atIndex: 0, playWhenReady: true)
|
||||
}
|
||||
@@ -228,11 +235,11 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
|
||||
func onCurrentIndexChanged(oldIndex: Int, newIndex: Int) {
|
||||
// if _currentItem is nil, then this was triggered by a reset. ignore.
|
||||
if _currentItem == nil { return }
|
||||
self.event.queueIndex.emit(data: (oldIndex, newIndex))
|
||||
if currentItem == nil { return }
|
||||
event.queueIndex.emit(data: (oldIndex, newIndex))
|
||||
}
|
||||
|
||||
func onReceivedFirstItem() {
|
||||
self.event.queueIndex.emit(data: (nil, 0))
|
||||
event.queueIndex.emit(data: (nil, 0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class RemoteCommandController {
|
||||
- parameter remoteCommandCenter: The MPRemoteCommandCenter used. Default is `MPRemoteCommandCenter.shared()`
|
||||
*/
|
||||
public init(remoteCommandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared()) {
|
||||
self.center = remoteCommandCenter
|
||||
center = remoteCommandCenter
|
||||
}
|
||||
|
||||
internal func enable(commands: [RemoteCommand]) {
|
||||
@@ -35,20 +35,13 @@ public class RemoteCommandController {
|
||||
!commands.contains(where: { $0.description == command.description })
|
||||
}
|
||||
|
||||
self.enabledCommands = commands
|
||||
commands.forEach { (command) in
|
||||
self.enable(command: command)
|
||||
}
|
||||
|
||||
commandsToDisable.forEach { (command) in
|
||||
self.disable(command: command)
|
||||
}
|
||||
enabledCommands = commands
|
||||
commands.forEach { self.enable(command: $0) }
|
||||
disable(commands: commandsToDisable)
|
||||
}
|
||||
|
||||
internal func disable(commands: [RemoteCommand]) {
|
||||
commands.forEach { (command) in
|
||||
self.disable(command: command)
|
||||
}
|
||||
commands.forEach { self.disable(command: $0) }
|
||||
}
|
||||
|
||||
private func enableCommand<Command: RemoteCommandProtocol>(_ command: Command) {
|
||||
@@ -102,21 +95,21 @@ public class RemoteCommandController {
|
||||
|
||||
// MARK: - Handlers
|
||||
|
||||
public lazy var handlePlayCommand: RemoteCommandHandler = self.handlePlayCommandDefault
|
||||
public lazy var handlePauseCommand: RemoteCommandHandler = self.handlePauseCommandDefault
|
||||
public lazy var handleStopCommand: RemoteCommandHandler = self.handleStopCommandDefault
|
||||
public lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = self.handleTogglePlayPauseCommandDefault
|
||||
public lazy var handleSkipForwardCommand: RemoteCommandHandler = self.handleSkipForwardCommandDefault
|
||||
public lazy var handleSkipBackwardCommand: RemoteCommandHandler = self.handleSkipBackwardDefault
|
||||
public lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = self.handleChangePlaybackPositionCommandDefault
|
||||
public lazy var handleNextTrackCommand: RemoteCommandHandler = self.handleNextTrackCommandDefault
|
||||
public lazy var handlePreviousTrackCommand: RemoteCommandHandler = self.handlePreviousTrackCommandDefault
|
||||
public lazy var handleLikeCommand: RemoteCommandHandler = self.handleLikeCommandDefault
|
||||
public lazy var handleDislikeCommand: RemoteCommandHandler = self.handleDislikeCommandDefault
|
||||
public lazy var handleBookmarkCommand: RemoteCommandHandler = self.handleBookmarkCommandDefault
|
||||
public lazy var handlePlayCommand: RemoteCommandHandler = handlePlayCommandDefault
|
||||
public lazy var handlePauseCommand: RemoteCommandHandler = handlePauseCommandDefault
|
||||
public lazy var handleStopCommand: RemoteCommandHandler = handleStopCommandDefault
|
||||
public lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = handleTogglePlayPauseCommandDefault
|
||||
public lazy var handleSkipForwardCommand: RemoteCommandHandler = handleSkipForwardCommandDefault
|
||||
public lazy var handleSkipBackwardCommand: RemoteCommandHandler = handleSkipBackwardDefault
|
||||
public lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = handleChangePlaybackPositionCommandDefault
|
||||
public lazy var handleNextTrackCommand: RemoteCommandHandler = handleNextTrackCommandDefault
|
||||
public lazy var handlePreviousTrackCommand: RemoteCommandHandler = handlePreviousTrackCommandDefault
|
||||
public lazy var handleLikeCommand: RemoteCommandHandler = handleLikeCommandDefault
|
||||
public lazy var handleDislikeCommand: RemoteCommandHandler = handleDislikeCommandDefault
|
||||
public lazy var handleBookmarkCommand: RemoteCommandHandler = handleBookmarkCommandDefault
|
||||
|
||||
private func handlePlayCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
if let audioPlayer = audioPlayer {
|
||||
audioPlayer.play()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -124,7 +117,7 @@ public class RemoteCommandController {
|
||||
}
|
||||
|
||||
private func handlePauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
if let audioPlayer = audioPlayer {
|
||||
audioPlayer.pause()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -132,7 +125,7 @@ public class RemoteCommandController {
|
||||
}
|
||||
|
||||
private func handleStopCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
if let audioPlayer = audioPlayer {
|
||||
audioPlayer.stop()
|
||||
return .success
|
||||
}
|
||||
@@ -140,7 +133,7 @@ public class RemoteCommandController {
|
||||
}
|
||||
|
||||
private func handleTogglePlayPauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
if let audioPlayer = audioPlayer {
|
||||
audioPlayer.togglePlaying()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -150,7 +143,7 @@ public class RemoteCommandController {
|
||||
private func handleSkipForwardCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
let audioPlayer = audioPlayer {
|
||||
audioPlayer.seek(to: audioPlayer.currentTime + Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -160,7 +153,7 @@ public class RemoteCommandController {
|
||||
private func handleSkipBackwardDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
let audioPlayer = audioPlayer {
|
||||
audioPlayer.seek(to: audioPlayer.currentTime - Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -169,7 +162,7 @@ public class RemoteCommandController {
|
||||
|
||||
private func handleChangePlaybackPositionCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let event = event as? MPChangePlaybackPositionCommandEvent,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
let audioPlayer = audioPlayer {
|
||||
audioPlayer.seek(to: event.positionTime)
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
@@ -177,41 +170,41 @@ public class RemoteCommandController {
|
||||
}
|
||||
|
||||
private func handleNextTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
if let player = audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.next()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
return getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
private func handlePreviousTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
if let player = audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.previous()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
return getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
private func handleLikeCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
private func handleDislikeCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
private func handleBookmarkCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
private func getRemoteCommandHandlerStatus(forError error: Error) -> MPRemoteCommandHandlerStatus {
|
||||
|
||||
Reference in New Issue
Block a user