Compare commits

...

7 Commits

Author SHA1 Message Date
David Chavez 7ff34271e8 Release 0.15.1 2022-04-22 23:11:59 +02:00
David Chavez 4f7a5b02a6 Fix: Bug - repeat mode and queue index event (#16) 2022-04-22 23:11:01 +02:00
David Chavez af803339dc More syntax updates and simplification 2022-04-03 13:16:43 +02:00
David Chavez a5bf6eb1dd Use timeDomain as default audioTimePitchAlgorithm 2022-04-03 12:35:30 +02:00
David Chavez 5e0c27b990 More syntax improvements 2022-04-03 12:24:18 +02:00
David Chavez 6079234942 More syntax updates 2022-04-03 12:13:39 +02:00
David Chavez e74b5ffe4d Syntax improvements 2022-04-03 11:49:23 +02:00
15 changed files with 497 additions and 398 deletions
+180 -24
View File
@@ -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)))
}
}
}
}
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioEx'
s.version = '0.15.0'
s.version = '0.15.1'
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 {
@@ -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]?)
}
+12 -13
View File
@@ -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
}
}
+45 -56
View File
@@ -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,9 +298,7 @@ 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))
}
}
@@ -317,7 +307,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
// MARK: - Private
func reset() {
self._currentItem = nil
currentItem = nil
}
private func setTimePitchingAlgorithmForCurrentItem() {
@@ -340,7 +330,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 +338,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
}
}
+8 -10
View File
@@ -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)
}
+54 -63
View File
@@ -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()
}
}
+28 -21
View File
@@ -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 {