Compare commits

...

27 Commits

Author SHA1 Message Date
David Chavez 23fdb9b9db Release 0.15.3 2022-09-14 15:02:30 +02:00
Jonathan Puckey 24c19aa661 Remove unnecessary buffer settings from tests. (#26)
As expected, the tests run successfully without these set too. See https://github.com/doublesymmetry/react-native-track-player/pull/1695
2022-09-06 09:15:36 +02:00
Jonathan Puckey 38429c6ca8 Reset AVPlayerWrapper on failure to load pending asset (#25)
See https://github.com/doublesymmetry/react-native-track-player/issues/1538
2022-09-06 09:13:38 +02:00
Jonathan Puckey 72f9c5d147 Fix order of AVPlayerWrapperState.state (#21) 2022-09-06 09:02:45 +02:00
David Chavez bd93898809 fix(tests): run workflows on PRs (#27) 2022-09-06 08:58:59 +02:00
David Chavez 8276f38b1b Release 0.15.2 2022-05-07 21:14:56 +02:00
David Akpan fcd5790e1e fix/ios-hls-live-duration (#18) 2022-05-07 08:59:17 +02:00
Jacob Spizziri ead7c0962e fix(audioplayer): fix loadArtwork method to unset artwork value if no image is given (#17)
https://github.com/doublesymmetry/react-native-track-player/issues/1511
2022-04-30 00:51:14 +02:00
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
David Chavez 92554a187c Release 0.15.0 2022-04-01 23:54:08 +02:00
David Chavez 473651f357 Support mp3 embedded chapters 2022-04-01 23:47:46 +02:00
David Chavez db2f3e9af7 Remove obsolete code 2022-04-01 23:22:26 +02:00
David Chavez a9f831a258 Fix bug in addItems at index and add tests 2022-04-01 21:18:52 +02:00
David Chavez cc3840d81e Fix next/previous with repeat modes 2022-04-01 20:47:54 +02:00
David Chavez 5307090ea3 Replace deprecated “timedMetadata" KVO 2022-04-01 17:47:57 +02:00
David Chavez bdaee8b18f Extract more information from interruptions 2022-04-01 00:14:47 +02:00
David Chavez 84d359bc4f Update README.md 2022-02-24 09:14:36 +01:00
David Chavez 40ea7ad2f9 Release 0.14.7 2022-02-24 08:49:31 +01:00
David Chavez f2f1c1236c Add tests for new seek improvements 2022-02-24 08:48:54 +01:00
Terkel a75f0d0201 fix: make moveItem public and accessible from outside the class (#9) 2022-02-23 21:40:39 +01:00
Jacob Spizziri 9e4e7f6807 fix(seek): fix an issue causing seek to fail if called immediatly after load (#11) 2022-02-23 21:27:38 +01:00
25 changed files with 844 additions and 525 deletions
+9 -2
View File
@@ -1,5 +1,12 @@
name: validate
on: [push]
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
unit-tests:
runs-on: macos-latest
@@ -17,4 +24,4 @@ jobs:
cd Example
xcodebuild test -scheme SwiftAudio-Example -destination "${destination}" -enableCodeCoverage YES
env:
destination: ${{ matrix.destination }}
destination: ${{ matrix.destination }}
+10 -10
View File
@@ -44,9 +44,9 @@
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */; };
9B05AA312660276400C7A389 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA302660276400C7A389 /* Quick */; };
9B05AA332660276400C7A389 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA322660276400C7A389 /* Nimble */; };
9B1D5E1E27C76F5C004CA883 /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */; };
9B1D5E2027C76F6F004CA883 /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */; };
9B521D0E2662937600EF0C3A /* MockDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */; };
9B77D79426C522D0004BAF2F /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77D79326C522D0004BAF2F /* SwiftAudioEx */; };
9B77D79626C52382004BAF2F /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77D79526C52382004BAF2F /* SwiftAudioEx */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -94,7 +94,7 @@
607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftAudio_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerObserverTests.swift; sourceTree = "<group>"; };
9B05AA38266028D600C7A389 /* SwiftAudio */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SwiftAudio; path = ..; sourceTree = "<group>"; };
9B1D5E1C27C76F49004CA883 /* SwiftAudioEx */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftAudioEx; path = ..; sourceTree = "<group>"; };
9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDispatchQueue.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -103,7 +103,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B77D79426C522D0004BAF2F /* SwiftAudioEx in Frameworks */,
9B1D5E2027C76F6F004CA883 /* SwiftAudioEx in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -111,7 +111,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B77D79626C52382004BAF2F /* SwiftAudioEx in Frameworks */,
9B1D5E1E27C76F5C004CA883 /* SwiftAudioEx in Frameworks */,
9B05AA312660276400C7A389 /* Quick in Frameworks */,
9B05AA332660276400C7A389 /* Nimble in Frameworks */,
);
@@ -222,7 +222,7 @@
9B05AA2F2660276400C7A389 /* Frameworks */ = {
isa = PBXGroup;
children = (
9B05AA38266028D600C7A389 /* SwiftAudio */,
9B1D5E1C27C76F49004CA883 /* SwiftAudioEx */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -244,7 +244,7 @@
);
name = SwiftAudio_Example;
packageProductDependencies = (
9B77D79326C522D0004BAF2F /* SwiftAudioEx */,
9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */,
);
productName = SwiftAudio;
productReference = 607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */;
@@ -267,7 +267,7 @@
packageProductDependencies = (
9B05AA302660276400C7A389 /* Quick */,
9B05AA322660276400C7A389 /* Nimble */,
9B77D79526C52382004BAF2F /* SwiftAudioEx */,
9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */,
);
productName = Tests;
productReference = 607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */;
@@ -674,11 +674,11 @@
package = 9B05AA2C2660274F00C7A389 /* XCRemoteSwiftPackageReference "Nimble" */;
productName = Nimble;
};
9B77D79326C522D0004BAF2F /* SwiftAudioEx */ = {
9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */ = {
isa = XCSwiftPackageProductDependency;
productName = SwiftAudioEx;
};
9B77D79526C52382004BAF2F /* SwiftAudioEx */ = {
9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */ = {
isa = XCSwiftPackageProductDependency;
productName = SwiftAudioEx;
};
@@ -47,9 +47,9 @@ class AVPlayerItemObserverTests: QuickSpec {
}
class AVPlayerItemObserverDelegateHolder: AVPlayerItemObserverDelegate {
var receivedMetadata: ((_ metadata: [AVMetadataItem]) -> Void)?
func item(didReceiveMetadata metadata: [AVMetadataItem]) {
var receivedMetadata: ((_ metadata: [AVTimedMetadataGroup]) -> Void)?
func item(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
receivedMetadata?(metadata)
}
+12 -1
View File
@@ -145,6 +145,17 @@ class AVPlayerWrapperTests: XCTestCase {
wrapper.load(from: Source.url, playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__seeking__should_seek_while_not_yet_loaded() {
let seekTime: TimeInterval = 5.0
let expectation = XCTestExpectation()
holder.didSeekTo = { seconds in
expectation.fulfill()
}
wrapper.load(from: Source.url, playWhenReady: false)
wrapper.seek(to: seekTime)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__loading_source_with_initial_time__should_seek() {
let expectation = XCTestExpectation()
@@ -182,7 +193,7 @@ class AVPlayerWrapperTests: XCTestCase {
}
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
func AVWrapper(didReceiveMetadata metadata: [AVMetadataItem]) {
func AVWrapper(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
}
+29 -31
View File
@@ -6,34 +6,32 @@ import XCTest
@testable import SwiftAudioEx
class AudioPlayerTests: XCTestCase {
var audioPlayer: AudioPlayer!
var listener: AudioPlayerEventListener!
override func setUp() {
super.setUp()
audioPlayer = AudioPlayer()
audioPlayer.volume = 0.0
audioPlayer.bufferDuration = 0.001
audioPlayer.automaticallyWaitsToMinimizeStalling = false
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
}
override func tearDown() {
audioPlayer = nil
listener = nil
super.tearDown()
}
func test_AudioPlayer__state__should_be_idle() {
XCTAssert(audioPlayer.playerState == AudioPlayerState.idle)
}
func test_AudioPlayer__state__load_source__should_be_loading() {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.loading)
}
func test_AudioPlayer__state__load_source__should_be_ready() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
@@ -45,7 +43,7 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__load_source_playWhenReady__should_be_playing() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
@@ -57,7 +55,7 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__play_source__should_be_playing() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
@@ -70,7 +68,7 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__pausing_source__should_be_paused() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
@@ -83,7 +81,7 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__stopping_source__should_be_idle() {
let expectation = XCTestExpectation()
var hasBeenPlaying: Bool = false
@@ -102,13 +100,13 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Current time
func test_AudioPlayer__currentTime__should_be_0() {
XCTAssert(audioPlayer.currentTime == 0.0)
}
// Commented out -- Keeps failing in CI at Bitrise, but succeeds locally, even with Bitrise CLI.
// func test_AudioPlayer__currentTime__playing_source__shold_be_greater_than_0() {
// let expectation = XCTestExpectation()
@@ -121,13 +119,13 @@ class AudioPlayerTests: XCTestCase {
// try? audioPlayer.load(item: LongSource.getAudioItem(), playWhenReady: true)
// wait(for: [expectation], timeout: 20.0)
// }
// MARK: - Rate
func test_AudioPlayer__rate__should_be_1() {
XCTAssert(audioPlayer.rate == 1.0)
}
func test_AudioPlayer__rate__playing_source__should_be_1() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
@@ -143,13 +141,13 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Current item
func test_AudioPlayer__currentItem__should_be_nil() {
XCTAssertNil(audioPlayer.currentItem)
}
func test_AudioPlayer__currentItem__loading_source__should_not_be_nil() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
@@ -165,11 +163,11 @@ class AudioPlayerTests: XCTestCase {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
}
class AudioPlayerEventListener {
var state: AudioPlayerState? {
didSet {
if let state = state {
@@ -177,35 +175,35 @@ class AudioPlayerEventListener {
}
}
}
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
var secondsElapse: ((_ seconds: TimeInterval) -> Void)?
var seekCompletion: (() -> Void)?
weak var audioPlayer: AudioPlayer?
init(audioPlayer: AudioPlayer) {
audioPlayer.event.stateChange.addListener(self, handleDidUpdateState)
audioPlayer.event.seek.addListener(self, handleSeek)
audioPlayer.event.secondElapse.addListener(self, handleSecondsElapse)
}
deinit {
audioPlayer?.event.stateChange.removeListener(self)
audioPlayer?.event.seek.removeListener(self)
audioPlayer?.event.secondElapse.removeListener(self)
}
func handleDidUpdateState(state: AudioPlayerState) {
self.state = state
}
func handleSeek(data: AudioPlayer.SeekEventData) {
seekCompletion?()
}
func handleSecondsElapse(data: AudioPlayer.SecondElapseEventData) {
self.secondsElapse?(data)
}
}
@@ -52,11 +52,12 @@ class AudioSessionControllerTests: QuickSpec {
}
describe("its delegate") {
context("when a interruption arrives") {
context("when a ended interruption arrives") {
var delegate: AudioSessionControllerDelegateImplementation!
beforeEach {
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
AVAudioSessionInterruptionTypeKey: UInt(0)
AVAudioSessionInterruptionTypeKey: UInt(0),
AVAudioSessionInterruptionOptionKey: UInt(1),
])
delegate = AudioSessionControllerDelegateImplementation()
audioSessionController.delegate = delegate
@@ -64,7 +65,23 @@ class AudioSessionControllerTests: QuickSpec {
}
it("should eventually be updated with the interruption type") {
expect(delegate.interruptionType).toEventuallyNot(beNil())
expect(delegate.interruptionType).toEventually(equal(InterruptionType.ended(shouldResume: true)))
}
}
context("when a begin interruption arrives") {
var delegate: AudioSessionControllerDelegateImplementation!
beforeEach {
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
AVAudioSessionInterruptionTypeKey: UInt(1),
])
delegate = AudioSessionControllerDelegateImplementation()
audioSessionController.delegate = delegate
audioSessionController.handleInterruption(notification: notification)
}
it("should eventually be updated with the interruption type") {
expect(delegate.interruptionType).toEventually(equal(InterruptionType.began))
}
}
@@ -91,10 +108,9 @@ class AudioSessionControllerTests: QuickSpec {
}
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
var interruptionType: InterruptionType? = nil
var interruptionType: AVAudioSession.InterruptionType? = nil
func handleInterruption(type: AVAudioSession.InterruptionType) {
func handleInterruption(type: InterruptionType) {
self.interruptionType = type
}
}
+47
View File
@@ -70,6 +70,53 @@ class QueueManagerTests: QuickSpec {
}
}
describe("when adding at index") {
context("adding item at index 0 when queue is empty") {
it("should add element successfully") {
try manager.addItems([3], at: 0)
expect(manager.current).to(equal(3))
}
}
context("adding item at index") {
beforeEach {
manager.addItems([3, 1])
}
context("current [element count]") {
it("should add element successfully") {
try manager.addItems([5], at: manager.items.count)
expect(manager.items.last).to(equal(5))
}
}
context("before the [current index]") {
it("should add element successfully") {
try manager.addItems([5], at: 0)
expect(manager.current).to(equal(3))
expect(manager.currentIndex).to(equal(1))
}
}
context("after the [current index]") {
it("should add element successfully") {
try manager.addItems([5], at: 1)
expect(manager.current).to(equal(3))
expect(manager.currentIndex).to(equal(0))
}
}
context("at [current index]") {
it("should add element successfully") {
try manager.next()
try manager.addItems([5], at: 1)
expect(manager.current).to(equal(1))
expect(manager.currentIndex).to(equal(2))
}
}
}
}
context("when adding one item") {
+282 -48
View File
@@ -3,21 +3,34 @@ 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") {
var audioPlayer: QueuedAudioPlayer!
beforeEach {
audioPlayer = QueuedAudioPlayer()
audioPlayer.bufferDuration = 0.0001
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.volume = 0.0
}
describe("its current item") {
it("should be nil") {
expect(audioPlayer.currentItem).to(beNil())
}
context("when adding one item") {
var item: AudioItem!
beforeEach {
@@ -27,18 +40,18 @@ class QueuedAudioPlayerTests: QuickSpec {
it("should not be nil") {
expect(audioPlayer.currentItem).toNot(beNil())
}
context("then loading a new item") {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should have replaced the item") {
expect(audioPlayer.currentItem?.getSourceUrl()).toNot(equal(item.getSourceUrl()))
}
}
}
context("when adding multiple items") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: false)
@@ -48,12 +61,12 @@ class QueuedAudioPlayerTests: QuickSpec {
}
}
}
describe("its next items") {
it("should be empty") {
expect(audioPlayer.nextItems.count).to(equal(0))
}
context("when adding 2 items") {
beforeEach {
try? audioPlayer.add(items: [Source.getAudioItem(), Source.getAudioItem()])
@@ -61,7 +74,7 @@ class QueuedAudioPlayerTests: QuickSpec {
it("should contain 1 item") {
expect(audioPlayer.nextItems.count).to(equal(1))
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
@@ -69,7 +82,7 @@ class QueuedAudioPlayerTests: QuickSpec {
it("should contain 0 items") {
expect(audioPlayer.nextItems.count).to(equal(0))
}
context("then calling previous()") {
beforeEach {
try? audioPlayer.previous()
@@ -79,17 +92,17 @@ class QueuedAudioPlayerTests: QuickSpec {
}
}
}
context("then removing one item") {
beforeEach {
try? audioPlayer.removeItem(at: 1)
}
it("should be empty") {
expect(audioPlayer.nextItems.count).to(equal(0))
}
}
context("then jumping to the last item") {
beforeEach {
try? audioPlayer.jumpToItem(atIndex: 1)
@@ -98,43 +111,43 @@ class QueuedAudioPlayerTests: QuickSpec {
expect(audioPlayer.nextItems.count).to(equal(0))
}
}
context("then removing upcoming items") {
beforeEach {
audioPlayer.removeUpcomingItems()
}
it("should be empty") {
expect(audioPlayer.nextItems.count).to(equal(0))
}
}
context("then stopping") {
beforeEach {
audioPlayer.stop()
}
it("should be empty") {
expect(audioPlayer.nextItems.count).to(equal(0))
}
}
}
}
describe("its previous items") {
it("should be empty") {
expect(audioPlayer.previousItems.count).to(equal(0))
}
context("when adding 2 items") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
}
it("should be empty") {
expect(audioPlayer.previousItems.count).to(equal(0))
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
@@ -143,34 +156,121 @@ class QueuedAudioPlayerTests: QuickSpec {
expect(audioPlayer.previousItems.count).to(equal(1))
}
}
context("then removing all previous items") {
beforeEach {
audioPlayer.removePreviousItems()
}
it("should be empty") {
expect(audioPlayer.previousItems.count).to(equal(0))
}
}
context("then stopping") {
beforeEach {
audioPlayer.stop()
}
it("should be empty") {
expect(audioPlayer.previousItems.count).to(equal(0))
}
}
}
}
describe("onNext") {
context("player was playing") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
}
it("should go to next item and play") {
expect(audioPlayer.nextItems.count).toEventually(equal(0))
expect(audioPlayer.currentIndex).toEventually(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
}
}
context("player was paused") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
audioPlayer.pause()
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
}
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))
}
}
}
}
describe("onPrevious") {
context("player was playing") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: true)
try? audioPlayer.next()
}
context("then calling previous()") {
beforeEach {
try? audioPlayer.previous()
}
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))
}
}
}
context("player was paused") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
try? audioPlayer.next()
audioPlayer.pause()
}
context("then calling previous()") {
beforeEach {
try? audioPlayer.previous()
}
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))
}
}
}
}
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 {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: true)
}
context("then setting repeat mode off") {
@@ -178,43 +278,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())
}
}
@@ -228,25 +338,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)
it("should move to next item but should not play") {
try? audioPlayer.next()
expect(audioPlayer.nextItems.count).to(equal(0))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
expect(eventListener.eventResult).toEventually(equal((0, 1)))
}
}
}
@@ -258,37 +373,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") {
@@ -297,14 +421,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
@@ -14,7 +14,7 @@ To see the audio player in action, run the example project!
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
iOS 10.0+
iOS 11.0+
## Installation
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioEx'
s.version = '0.14.6'
s.version = '0.15.3'
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,68 +26,75 @@ 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
fileprivate(set) var state: AVPlayerWrapperState = AVPlayerWrapperState.idle {
didSet {
if oldValue != state {
delegate?.AVWrapper(didChangeState: state)
}
}
}
var reasonForWaitingToPlay: AVPlayer.WaitingReason? {
return avPlayer.reasonForWaitingToPlay
fileprivate(set) var lastPlayerTimeControlStatus: AVPlayer.TimeControlStatus = AVPlayer.TimeControlStatus.paused {
didSet {
if oldValue != lastPlayerTimeControlStatus {
switch lastPlayerTimeControlStatus {
case .paused:
if pendingAsset == nil {
state = .idle
}
else if currentItem != nil && pausedForLoad != true {
state = .paused
}
case .waitingToPlayAtSpecifiedRate:
if pendingAsset != nil {
state = .buffering
}
case .playing:
state = .playing
@unknown default:
break
}
}
}
}
/**
True if the last call to load(from:playWhenReady) had playWhenReady=true.
*/
fileprivate(set) var playWhenReady: Bool = true
var currentItem: AVPlayerItem? {
return avPlayer.currentItem
}
var _pendingAsset: AVAsset? = nil
var automaticallyWaitsToMinimizeStalling: Bool {
get { return avPlayer.automaticallyWaitsToMinimizeStalling }
set { avPlayer.automaticallyWaitsToMinimizeStalling = newValue }
avPlayer.currentItem
}
var currentTime: TimeInterval {
@@ -102,7 +109,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
}
@@ -110,41 +117,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()
}
@@ -165,65 +181,75 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
}
func seek(to seconds: TimeInterval) {
avPlayer.seek(to: CMTimeMakeWithSeconds(seconds, preferredTimescale: 1000)) { (finished) in
if let _ = self._initialTime {
self._initialTime = nil
if self._playWhenReady {
self.play()
}
}
self.delegate?.AVWrapper(seekTo: Int(seconds), didFinish: finished)
}
}
// if the player is loading then we need to defer seeking until it's ready.
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 {
self.play()
}
}
self.delegate?.AVWrapper(seekTo: Int(seconds), didFinish: finished)
}
}
}
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))
if (pendingAsset != self.pendingAsset) { return; }
switch status {
case .loaded:
if isPendingAsset {
let currentItem = AVPlayerItem(asset: pendingAsset, automaticallyLoadedAssetKeys: [Constants.assetPlayableKey])
currentItem.preferredForwardBufferDuration = self.bufferDuration
self.avPlayer.replaceCurrentItem(with: currentItem)
// Register for events
self.playerTimeObserver.registerForBoundaryTimeEvents()
self.playerObserver.startObserving()
self.playerItemNotificationObserver.startObserving(item: currentItem)
self.playerItemObserver.startObserving(item: currentItem)
let item = AVPlayerItem(
asset: pendingAsset,
automaticallyLoadedAssetKeys: [Constants.assetPlayableKey]
)
item.preferredForwardBufferDuration = self.bufferDuration
self.avPlayer.replaceCurrentItem(with: item)
// Register for events
self.playerTimeObserver.registerForBoundaryTimeEvents()
self.playerObserver.startObserving()
self.playerItemNotificationObserver.startObserving(item: item)
self.playerItemObserver.startObserving(item: item)
if pendingAsset.availableChapterLocales.count > 0 {
for locale in pendingAsset.availableChapterLocales {
let chapters = pendingAsset.chapterMetadataGroups(withTitleLocale: locale, containingItemsWithCommonKeys: nil)
self.delegate?.AVWrapper(didReceiveMetadata: chapters)
}
} else {
for format in pendingAsset.availableMetadataFormats {
self.delegate?.AVWrapper(didReceiveMetadata: pendingAsset.metadata(forFormat: format))
let timeRange = CMTimeRange(start: CMTime(seconds: 0, preferredTimescale: 1000), end: pendingAsset.duration)
let group = AVTimedMetadataGroup(items: pendingAsset.metadata(forFormat: format), timeRange: timeRange)
self.delegate?.AVWrapper(didReceiveMetadata: [group])
}
}
break
case .failed:
if isPendingAsset {
self.delegate?.AVWrapper(failedWithError: error)
self._pendingAsset = nil
}
self.reset(soft: false)
self.delegate?.AVWrapper(failedWithError: error)
break
case .cancelled:
@@ -238,10 +264,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)
}
@@ -252,9 +278,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)
@@ -278,39 +304,24 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
// MARK: - AVPlayerObserverDelegate
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
switch status {
case .paused:
if currentItem == nil {
_state = .idle
}
else if _pausedForLoad == true {}
else {
self._state = .paused
}
case .waitingToPlayAtSpecifiedRate:
self._state = .buffering
case .playing:
self._state = .playing
@unknown default:
break
}
lastPlayerTimeControlStatus = status;
}
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:
@@ -319,7 +330,6 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
break
}
}
}
extension AVPlayerWrapper: AVPlayerTimeObserverDelegate {
@@ -327,11 +337,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)
}
}
@@ -351,11 +361,11 @@ extension AVPlayerWrapper: AVPlayerItemObserverDelegate {
// MARK: - AVPlayerItemObserverDelegate
func item(didUpdateDuration duration: Double) {
self.delegate?.AVWrapper(didUpdateDuration: duration)
delegate?.AVWrapper(didUpdateDuration: duration)
}
func item(didReceiveMetadata metadata: [AVMetadataItem]) {
self.delegate?.AVWrapper(didReceiveMetadata: metadata)
func item(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
delegate?.AVWrapper(didReceiveMetadata: metadata)
}
}
@@ -9,14 +9,14 @@ import Foundation
import MediaPlayer
protocol AVPlayerWrapperDelegate: class {
protocol AVPlayerWrapperDelegate: AnyObject {
func AVWrapper(didChangeState state: AVPlayerWrapperState)
func AVWrapper(secondsElapsed seconds: Double)
func AVWrapper(failedWithError error: Error?)
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
func AVWrapper(didUpdateDuration duration: Double)
func AVWrapper(didReceiveMetadata metadata: [AVMetadataItem])
func AVWrapper(didReceiveMetadata metadata: [AVTimedMetadataGroup])
func AVWrapperItemDidPlayToEndTime()
func AVWrapperDidRecreateAVPlayer()
@@ -9,9 +9,11 @@ import Foundation
import AVFoundation
protocol AVPlayerWrapperProtocol: class {
protocol AVPlayerWrapperProtocol: AnyObject {
var state: AVPlayerWrapperState { get }
var playWhenReady: Bool { get }
var currentItem: AVPlayerItem? { get }
@@ -52,5 +54,4 @@ protocol AVPlayerWrapperProtocol: class {
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
}
}
+52 -57
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
@@ -52,33 +45,37 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
// MARK: - Getters from AVPlayerWrapper
internal var willPlayWhenReady: Bool {
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
@@ -91,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
}
}
}
@@ -143,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
}
@@ -178,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)
}
@@ -190,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)
}
/**
@@ -221,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) {
@@ -302,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))
}
}
}
@@ -313,7 +309,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
// MARK: - Private
func reset() {
self._currentItem = nil
currentItem = nil
}
private func setTimePitchingAlgorithmForCurrentItem() {
@@ -336,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) {
@@ -344,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: [AVMetadataItem]) {
self.event.receiveMetadata.emit(data: metadata)
func AVWrapper(didReceiveMetadata metadata: [AVTimedMetadataGroup]) {
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: ())
}
}
@@ -8,11 +8,14 @@
import Foundation
import AVFoundation
public protocol AudioSessionControllerDelegate: class {
func handleInterruption(type: AVAudioSession.InterruptionType)
public enum InterruptionType: Equatable {
case began
case ended(shouldResume: Bool)
}
public protocol AudioSessionControllerDelegate: AnyObject {
func handleInterruption(type: InterruptionType)
}
/**
Simple controller for the `AVAudioSession`. If you need more advanced options, just use the `AVAudioSession` directly.
@@ -30,7 +33,7 @@ public class AudioSessionController {
True if another app is currently playing audio.
*/
public var isOtherAudioPlaying: Bool {
return audioSession.isOtherAudioPlaying
audioSession.isOtherAudioPlaying
}
/**
@@ -46,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
@@ -112,7 +113,19 @@ public class AudioSessionController {
return
}
self.delegate?.handleInterruption(type: type)
switch type {
case .began:
delegate?.handleInterruption(type: .began)
case .ended:
guard let typeValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
delegate?.handleInterruption(type: .ended(shouldResume: false))
return
}
let options = AVAudioSession.InterruptionOptions(rawValue: typeValue)
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 = ([AVMetadataItem])
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() {
@@ -8,7 +8,7 @@
import Foundation
import AVFoundation
protocol AVPlayerItemObserverDelegate: class {
protocol AVPlayerItemObserverDelegate: AnyObject {
/**
Called when the observed item updates the duration.
@@ -18,7 +18,7 @@ protocol AVPlayerItemObserverDelegate: class {
/**
Called when the observed item receives metadata
*/
func item(didReceiveMetadata metadata: [AVMetadataItem])
func item(didReceiveMetadata metadata: [AVTimedMetadataGroup])
}
@@ -29,11 +29,11 @@ class AVPlayerItemObserver: NSObject {
private static var context = 0
private let main: DispatchQueue = .main
private let metadataOutput: AVPlayerItemMetadataOutput
private struct AVPlayerItemKeyPath {
static let duration = #keyPath(AVPlayerItem.duration)
static let loadedTimeRanges = #keyPath(AVPlayerItem.loadedTimeRanges)
static let timedMetadata = #keyPath(AVPlayerItem.timedMetadata)
}
private(set) var isObserving: Bool = false
@@ -41,6 +41,13 @@ class AVPlayerItemObserver: NSObject {
private(set) weak var observingItem: AVPlayerItem?
weak var delegate: AVPlayerItemObserverDelegate?
override init() {
metadataOutput = AVPlayerItemMetadataOutput()
super.init()
metadataOutput.setDelegate(self, queue: main)
}
deinit {
stopObservingCurrentItem()
}
@@ -51,12 +58,12 @@ class AVPlayerItemObserver: NSObject {
- parameter item: The player item to observe.
*/
func startObserving(item: AVPlayerItem) {
self.stopObservingCurrentItem()
self.isObserving = true
self.observingItem = item
stopObservingCurrentItem()
isObserving = true
observingItem = item
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, options: [.new], context: &AVPlayerItemObserver.context)
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, options: [.new], context: &AVPlayerItemObserver.context)
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.timedMetadata, options: [.new], context: &AVPlayerItemObserver.context)
item.add(metadataOutput)
}
func stopObservingCurrentItem() {
@@ -65,8 +72,8 @@ class AVPlayerItemObserver: NSObject {
}
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, context: &AVPlayerItemObserver.context)
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, context: &AVPlayerItemObserver.context)
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.timedMetadata, context: &AVPlayerItemObserver.context)
self.isObserving = false
observingItem.remove(metadataOutput)
isObserving = false
self.observingItem = nil
}
@@ -79,21 +86,22 @@ class AVPlayerItemObserver: NSObject {
switch observedKeyPath {
case AVPlayerItemKeyPath.duration:
if let duration = change?[.newKey] as? CMTime {
self.delegate?.item(didUpdateDuration: duration.seconds)
delegate?.item(didUpdateDuration: duration.seconds)
}
case AVPlayerItemKeyPath.loadedTimeRanges:
if let ranges = change?[.newKey] as? [NSValue], let duration = ranges.first?.timeRangeValue.duration {
self.delegate?.item(didUpdateDuration: duration.seconds)
delegate?.item(didUpdateDuration: duration.seconds)
}
case AVPlayerItemKeyPath.timedMetadata:
if let metadata = change?[.newKey] as? [AVMetadataItem] {
self.delegate?.item(didReceiveMetadata: metadata)
}
default: break
}
}
}
extension AVPlayerItemObserver: AVPlayerItemMetadataOutputPushDelegate {
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
delegate?.item(didReceiveMetadata: groups)
}
}
@@ -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)
}
+56 -64
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,12 +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 for addition has to be positive and smaller than the count of current 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)
if (_currentIndex >= index) { _currentIndex = _currentIndex + items.count }
self.items.insert(contentsOf: items, at: index)
if (currentIndex >= index && self.items.count != 1) { currentIndex += items.count }
}
/**
@@ -111,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]
}
/**
@@ -128,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]
}
/**
@@ -150,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]
}
/**
@@ -166,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)
@@ -192,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)
}
/**
@@ -214,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
}
/**
@@ -226,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
}
/**
@@ -235,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()
}
}
+34 -23
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)
@@ -123,14 +123,16 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
- throws: `APError`
*/
public func next() throws {
let shouldPlayWhenReady = (playerState == .loading) ? willPlayWhenReady : [.buffering, .playing].contains(playerState)
do {
let nextItem = try queueManager.next()
event.playbackEnd.emit(data: .skippedToNext)
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
try load(item: nextItem, playWhenReady: shouldPlayWhenReady)
} catch APError.QueueError.noNextItem {
if repeatMode == .queue {
event.playbackEnd.emit(data: .skippedToNext)
try jumpToItem(atIndex: 0, playWhenReady: true)
try jumpToItem(atIndex: 0, playWhenReady: shouldPlayWhenReady)
} else {
throw APError.QueueError.noNextItem
}
@@ -143,9 +145,11 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
Step to the previous item in the queue.
*/
public func previous() throws {
let shouldPlayWhenReady = (playerState == .loading) ? willPlayWhenReady : [.buffering, .playing].contains(playerState)
let previousItem = try queueManager.previous()
event.playbackEnd.emit(data: .skippedToPrevious)
try self.load(item: previousItem, playWhenReady: repeatMode != .track)
try load(item: previousItem, playWhenReady: shouldPlayWhenReady)
}
/**
@@ -166,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)
}
}
/**
@@ -178,7 +188,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
- parameter toIndex: The index to move the item to.
- throws: `APError.QueueError`
*/
func moveItem(fromIndex: Int, toIndex: Int) throws {
public func moveItem(fromIndex: Int, toIndex: Int) throws {
try queueManager.moveItem(fromIndex: fromIndex, toIndex: toIndex)
}
@@ -205,15 +215,16 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
case .off:
do {
let nextItem = try queueManager.next()
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
} 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: repeatMode != .track)
try load(item: nextItem, playWhenReady: true)
} catch {
try? jumpToItem(atIndex: 0, playWhenReady: true)
}
@@ -224,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 {