Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d4060eb68 | |||
| 15a8bc4abd | |||
| da5b7702f7 | |||
| 0066a4121c | |||
| 1bf9d695d8 | |||
| 8edc3b0e75 | |||
| 22c780adba | |||
| c8805d55fd | |||
| a6e74efa37 | |||
| 7d525e3129 | |||
| 39d8c55743 | |||
| d56d4c699e | |||
| 04ae9e7986 | |||
| 30503f0ffe | |||
| ba82a46f8b | |||
| 3eef6bf59d | |||
| ea9f632eb6 | |||
| 53aa56348e | |||
| 26c1d2875e | |||
| 5ede0f8364 |
+4
@@ -16,6 +16,7 @@
|
||||
077557572066867F0002C6A1 /* QueueManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077557562066867F0002C6A1 /* QueueManager.swift */; };
|
||||
0775575D2066A7DB0002C6A1 /* SimpleAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0775575C2066A7DB0002C6A1 /* SimpleAudioPlayer.swift */; };
|
||||
077557612066ABAD0002C6A1 /* QueuedAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077557602066ABAD0002C6A1 /* QueuedAudioPlayer.swift */; };
|
||||
078C908C210CD8B300555E80 /* AVPlayerItemObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078C908B210CD8B300555E80 /* AVPlayerItemObserver.swift */; };
|
||||
07F41B1A205FC0B100E25749 /* AudioSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F41B19205FC0B100E25749 /* AudioSessionController.swift */; };
|
||||
07F41B2220614BDC00E25749 /* RemoteCommandController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F41B2120614BDC00E25749 /* RemoteCommandController.swift */; };
|
||||
0A2CA8B0DD7E300ABFCF1956E6B58248 /* Nimble.h in Headers */ = {isa = PBXBuildFile; fileRef = 2727DBDF3F41A52F002F6B1992165881 /* Nimble.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -181,6 +182,7 @@
|
||||
077557562066867F0002C6A1 /* QueueManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = QueueManager.swift; path = SwiftAudio/Classes/QueueManager.swift; sourceTree = "<group>"; };
|
||||
0775575C2066A7DB0002C6A1 /* SimpleAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SimpleAudioPlayer.swift; path = SwiftAudio/Classes/SimpleAudioPlayer.swift; sourceTree = "<group>"; };
|
||||
077557602066ABAD0002C6A1 /* QueuedAudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QueuedAudioPlayer.swift; path = SwiftAudio/Classes/QueuedAudioPlayer.swift; sourceTree = "<group>"; };
|
||||
078C908B210CD8B300555E80 /* AVPlayerItemObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemObserver.swift; sourceTree = "<group>"; };
|
||||
07F41B19205FC0B100E25749 /* AudioSessionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AudioSessionController.swift; path = SwiftAudio/Classes/AudioSessionController.swift; sourceTree = "<group>"; };
|
||||
07F41B2120614BDC00E25749 /* RemoteCommandController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RemoteCommandController.swift; path = SwiftAudio/Classes/RemoteCommandController.swift; sourceTree = "<group>"; };
|
||||
0A9D7EA20C39A55A1EF0C23094895A75 /* Quick-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Quick-dummy.m"; sourceTree = "<group>"; };
|
||||
@@ -387,6 +389,7 @@
|
||||
07732657205ED6C400C4D1CD /* AVPlayerObserver.swift */,
|
||||
07732658205ED6C400C4D1CD /* AVPlayerItemNotificationObserver.swift */,
|
||||
07732659205ED6C400C4D1CD /* AVPlayerTimeObserver.swift */,
|
||||
078C908B210CD8B300555E80 /* AVPlayerItemObserver.swift */,
|
||||
);
|
||||
name = Observer;
|
||||
path = SwiftAudio/Classes/Observer;
|
||||
@@ -994,6 +997,7 @@
|
||||
07F41B1A205FC0B100E25749 /* AudioSessionController.swift in Sources */,
|
||||
A1A245A1D2A54FF00AACE521F153D281 /* AudioPlayer.swift in Sources */,
|
||||
0775575D2066A7DB0002C6A1 /* SimpleAudioPlayer.swift in Sources */,
|
||||
078C908C210CD8B300555E80 /* AVPlayerItemObserver.swift in Sources */,
|
||||
077557572066867F0002C6A1 /* QueueManager.swift in Sources */,
|
||||
C525C159B05D6FEEE0FC3D16910C934B /* AVPlayerWrapper.swift in Sources */,
|
||||
C226CFBDCE851BDA9CD1DA26894BF272 /* AVPlayerWrapperState.swift in Sources */,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
|
||||
07732655205ECE1C00C4D1CD /* nasa_throttle_up.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */; };
|
||||
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0775575820668B020002C6A1 /* QueueManagerTests.swift */; };
|
||||
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */; };
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
||||
@@ -52,6 +53,7 @@
|
||||
07732650205EACA300C4D1CD /* WAV-MP3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "WAV-MP3.wav"; sourceTree = "<group>"; };
|
||||
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = nasa_throttle_up.mp3; sourceTree = "<group>"; };
|
||||
0775575820668B020002C6A1 /* QueueManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueManagerTests.swift; sourceTree = "<group>"; };
|
||||
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemObserverTests.swift; sourceTree = "<group>"; };
|
||||
521F3AEC1228A2FA2637355F /* Pods-SwiftAudio_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudio_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudio_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -149,6 +151,7 @@
|
||||
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */,
|
||||
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */,
|
||||
0775575820668B020002C6A1 /* QueueManagerTests.swift */,
|
||||
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
|
||||
07732650205EACA300C4D1CD /* WAV-MP3.wav */,
|
||||
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */,
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
||||
@@ -437,6 +440,7 @@
|
||||
files = (
|
||||
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
|
||||
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
|
||||
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
|
||||
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */,
|
||||
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */,
|
||||
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
@@ -70,7 +70,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -27,8 +27,8 @@ class AudioController {
|
||||
player.remoteCommands = [
|
||||
.stop,
|
||||
.togglePlayPause,
|
||||
.skipForward(preferredIntervals: [30]),
|
||||
.skipBackward(preferredIntervals: [30]),
|
||||
.next,
|
||||
.previous,
|
||||
.changePlaybackPosition
|
||||
]
|
||||
try? audioSessionController.set(category: .playback)
|
||||
|
||||
@@ -60,7 +60,6 @@ class ViewController: UIViewController {
|
||||
extension ViewController: AudioPlayerDelegate {
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AVPlayerWrapperState) {
|
||||
|
||||
playButton.setTitle(state == .playing ? "Pause" : "Play", for: .normal)
|
||||
|
||||
switch state {
|
||||
@@ -77,6 +76,9 @@ extension ViewController: AudioPlayerDelegate {
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
|
||||
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
|
||||
case .loading, .playing, .paused, .idle:
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
@@ -104,4 +106,12 @@ extension ViewController: AudioPlayerDelegate {
|
||||
isScrubbing = false
|
||||
}
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double) {
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
|
||||
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
let source = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
|
||||
|
||||
class AVPlayerItemObserverTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("An AVPlayerItemObserver") {
|
||||
var observer: AVPlayerItemObserver!
|
||||
beforeEach {
|
||||
observer = AVPlayerItemObserver()
|
||||
}
|
||||
describe("observed item", {
|
||||
context("when observing", {
|
||||
var item: AVPlayerItem!
|
||||
beforeEach {
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: source))
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
|
||||
it("should exist", closure: {
|
||||
expect(observer.observingItem).toEventuallyNot(beNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("observing status", {
|
||||
it("should not be observing", closure: {
|
||||
expect(observer.isObserving).toEventuallyNot(beTrue())
|
||||
})
|
||||
context("when observing", {
|
||||
var item: AVPlayerItem!
|
||||
beforeEach {
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: source))
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
it("should be observing", closure: {
|
||||
expect(observer.isObserving).toEventually(beTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AVPlayerItemObserverDelegateHolder: AVPlayerItemObserverDelegate {
|
||||
|
||||
var updateDuration: ((_ duration: Double) -> Void)?
|
||||
|
||||
func item(didUpdateDuration duration: Double) {
|
||||
updateDuration?(duration)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,16 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
|
||||
expect(self.timeControlStatus).toEventuallyNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("when observing again", {
|
||||
beforeEach {
|
||||
observer.startObserving()
|
||||
}
|
||||
|
||||
it("should be observing", closure: {
|
||||
expect(observer.isObserving).toEventually(beTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -22,23 +22,33 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
wrapper.volume = 0.0
|
||||
}
|
||||
|
||||
describe("its state", {
|
||||
|
||||
context("when doing nothing", {
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
})
|
||||
describe("state", {
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
})
|
||||
|
||||
context("when loading a source", {
|
||||
beforeEach {
|
||||
try? wrapper.load(fromFilePath: source, playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should be loading", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.loading))
|
||||
})
|
||||
|
||||
it("should eventually be ready", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.ready))
|
||||
})
|
||||
})
|
||||
|
||||
context("when playing with no source", {
|
||||
beforeEach {
|
||||
try? wrapper.play()
|
||||
}
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
})
|
||||
})
|
||||
|
||||
context("when playing a source", {
|
||||
beforeEach {
|
||||
@@ -68,7 +78,22 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
it("should eventually be paused", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
context("when toggling the source from play", {
|
||||
let holder = AudioPlayerDelegateHolder()
|
||||
beforeEach {
|
||||
wrapper.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
if state == .playing {
|
||||
try? wrapper.togglePlaying()
|
||||
}
|
||||
}
|
||||
try? wrapper.load(fromFilePath: source, playWhenReady: true)
|
||||
}
|
||||
it("should eventually be playing", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
|
||||
})
|
||||
})
|
||||
|
||||
context("when stopping the source", {
|
||||
@@ -95,7 +120,30 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
context("when seeking before loading", {
|
||||
beforeEach {
|
||||
try? wrapper.seek(to: 10)
|
||||
}
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its duration", {
|
||||
it("should be 0", closure: {
|
||||
expect(wrapper.duration).to(equal(0))
|
||||
})
|
||||
|
||||
context("when loading source", {
|
||||
beforeEach {
|
||||
try? wrapper.load(fromFilePath: source, playWhenReady: false)
|
||||
}
|
||||
it("should eventually not be 0", closure: {
|
||||
expect(wrapper.duration).toEventuallyNot(equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,6 +157,7 @@ class AudioPlayerDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
var state: AVPlayerWrapperState? {
|
||||
didSet {
|
||||
print(state)
|
||||
if let state = state {
|
||||
self.stateUpdate?(state)
|
||||
}
|
||||
@@ -138,4 +187,8 @@ class AudioPlayerDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,6 +78,11 @@ class QueueManagerTests: QuickSpec {
|
||||
expect(manager.current).to(equal(self.dummyItems.first))
|
||||
})
|
||||
|
||||
it("should have next items", closure: {
|
||||
expect(manager.nextItems).toNot(beNil())
|
||||
expect(manager.nextItems?.count).to(equal(self.dummyItems.count - 1))
|
||||
})
|
||||
|
||||
context("then calling next", {
|
||||
var nextItem: Int?
|
||||
beforeEach {
|
||||
@@ -93,6 +98,10 @@ class QueueManagerTests: QuickSpec {
|
||||
expect(manager.current).to(equal(self.dummyItems[1]))
|
||||
})
|
||||
|
||||
it("should have previous items", closure: {
|
||||
expect(manager.previousItems.count).to(equal(1))
|
||||
})
|
||||
|
||||
context("then calling previous", {
|
||||
var previousItem: Int?
|
||||
beforeEach {
|
||||
@@ -171,6 +180,27 @@ class QueueManagerTests: QuickSpec {
|
||||
|
||||
// MARK: - Jumping
|
||||
|
||||
context("then jumping to the current item", {
|
||||
var error: Error?
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
do {
|
||||
item = try manager.jump(to: manager.currentIndex)
|
||||
}
|
||||
catch let err {
|
||||
error = err
|
||||
}
|
||||
}
|
||||
|
||||
it("should not return an item", closure: {
|
||||
expect(item).to(beNil())
|
||||
})
|
||||
|
||||
it("should throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("then jumping to the second item", {
|
||||
var jumped: Int?
|
||||
beforeEach {
|
||||
@@ -233,6 +263,76 @@ class QueueManagerTests: QuickSpec {
|
||||
|
||||
// MARK: - Moving
|
||||
|
||||
context("moving from current index", {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try manager.moveItem(fromIndex: manager.currentIndex, toIndex: manager.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("moving from a negative index", {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try manager.moveItem(fromIndex: -1, toIndex: manager.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("moving from a too large index", {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try manager.moveItem(fromIndex: manager.items.count, toIndex: manager.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("moving to a negative index", {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try manager.moveItem(fromIndex: manager.currentIndex + 1, toIndex: -1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("moving to a too large index", {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try manager.moveItem(fromIndex: manager.currentIndex + 1, toIndex: manager.items.count)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error", closure: {
|
||||
expect(error).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("then moving 2nd to 4th", closure: {
|
||||
let afterMoving: [Int] = [0, 2, 3, 1, 4, 5, 6]
|
||||
beforeEach {
|
||||
@@ -244,11 +344,6 @@ class QueueManagerTests: QuickSpec {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudio'
|
||||
s.version = '0.3.0'
|
||||
s.version = '0.3.2'
|
||||
s.summary = 'Easy audio streaming for iOS'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
@@ -18,6 +18,7 @@ protocol AVPlayerWrapperDelegate: class {
|
||||
func AVWrapper(secondsElapsed seconds: Double)
|
||||
func AVWrapper(failedWithError error: Error?)
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
|
||||
func AVWrapper(didUpdateDuration duration: Double)
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ class AVPlayerWrapper {
|
||||
let playerObserver: AVPlayerObserver
|
||||
let playerTimeObserver: AVPlayerTimeObserver
|
||||
let playerItemNotificationObserver: AVPlayerItemNotificationObserver
|
||||
let playerItemObserver: AVPlayerItemObserver
|
||||
|
||||
/**
|
||||
True if the last call to load(from:playWhenReady) had playWhenReady=true.
|
||||
@@ -49,7 +51,9 @@ class AVPlayerWrapper {
|
||||
|
||||
fileprivate var _state: AVPlayerWrapperState = AVPlayerWrapperState.idle {
|
||||
didSet {
|
||||
self.delegate?.AVWrapper(didChangeState: _state)
|
||||
if oldValue != _state {
|
||||
self.delegate?.AVWrapper(didChangeState: _state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +149,7 @@ class AVPlayerWrapper {
|
||||
self.playerObserver = AVPlayerObserver(player: avPlayer)
|
||||
self.playerTimeObserver = AVPlayerTimeObserver(player: avPlayer, periodicObserverTimeInterval: timeEventFrequency.getTime())
|
||||
self.playerItemNotificationObserver = AVPlayerItemNotificationObserver()
|
||||
self.playerItemObserver = AVPlayerItemObserver()
|
||||
|
||||
self.bufferDuration = 0
|
||||
self.timeEventFrequency = timeEventFrequency
|
||||
@@ -152,6 +157,7 @@ class AVPlayerWrapper {
|
||||
self.playerObserver.delegate = self
|
||||
self.playerTimeObserver.delegate = self
|
||||
self.playerItemNotificationObserver.delegate = self
|
||||
self.playerItemObserver.delegate = self
|
||||
|
||||
playerTimeObserver.registerForPeriodicTimeEvents()
|
||||
}
|
||||
@@ -255,6 +261,7 @@ class AVPlayerWrapper {
|
||||
|
||||
reset(soft: true)
|
||||
_playWhenReady = playWhenReady
|
||||
_state = .loading
|
||||
|
||||
// Set item
|
||||
let currentAsset = AVURLAsset(url: url)
|
||||
@@ -266,6 +273,7 @@ class AVPlayerWrapper {
|
||||
playerTimeObserver.registerForBoundaryTimeEvents()
|
||||
playerObserver.startObserving()
|
||||
playerItemNotificationObserver.startObserving(item: currentItem)
|
||||
playerItemObserver.startObserving(item: currentItem)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,3 +353,13 @@ extension AVPlayerWrapper: AVPlayerItemNotificationObserverDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AVPlayerWrapper: AVPlayerItemObserverDelegate {
|
||||
|
||||
// MARK: - AVPlayerItemObserverDelegate
|
||||
|
||||
func item(didUpdateDuration duration: Double) {
|
||||
self.delegate?.AVWrapper(didUpdateDuration: duration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ public protocol AudioPlayerDelegate: class {
|
||||
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool)
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,12 +126,11 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
// MARK: - Init
|
||||
|
||||
/**
|
||||
Create a new AudioManager.
|
||||
Create a new AudioPlayer.
|
||||
|
||||
- parameter audioPlayer: The underlying AudioPlayer instance for the Manager. If you need to configure the behaviour of the player, create an instance, configure it and pass it in here.
|
||||
- parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`.
|
||||
*/
|
||||
public init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
self.wrapper = AVPlayerWrapper()
|
||||
self.nowPlayingInfoController = NowPlayingInfoController(infoCenter: infoCenter)
|
||||
self.remoteCommandController = RemoteCommandController()
|
||||
@@ -273,7 +274,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
// MARK: - AVPlayerWrapperDelegate
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState) {
|
||||
updatePlaybackValues()
|
||||
switch state {
|
||||
case .playing, .paused: updatePlaybackValues()
|
||||
default: break
|
||||
}
|
||||
self.delegate?.audioPlayer(playerDidChangeState: state)
|
||||
}
|
||||
|
||||
@@ -294,4 +298,8 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
self.delegate?.audioPlayer(seekTo: seconds, didFinish: didFinish)
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
self.delegate?.audioPlayer(didUpdateDuration: duration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// AVPlayerItemObserver.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 28/07/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
protocol AVPlayerItemObserverDelegate: class {
|
||||
|
||||
/**
|
||||
Called when the observed item updates the duration.
|
||||
*/
|
||||
func item(didUpdateDuration duration: Double)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Observing an AVPlayers status changes.
|
||||
*/
|
||||
class AVPlayerItemObserver: NSObject {
|
||||
|
||||
private static var context = 0
|
||||
private let main: DispatchQueue = .main
|
||||
|
||||
private struct AVPlayerItemKeyPath {
|
||||
static let duration = #keyPath(AVPlayerItem.duration)
|
||||
}
|
||||
|
||||
var isObserving: Bool = false
|
||||
|
||||
weak var observingItem: AVPlayerItem?
|
||||
weak var delegate: AVPlayerItemObserverDelegate?
|
||||
|
||||
deinit {
|
||||
if self.isObserving {
|
||||
stopObservingCurrentItem()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Start observing an item. Will remove self as observer from old item.
|
||||
|
||||
- parameter item: The player item to observe.
|
||||
*/
|
||||
func startObserving(item: AVPlayerItem) {
|
||||
main.async {
|
||||
if self.isObserving {
|
||||
self.stopObservingCurrentItem()
|
||||
}
|
||||
self.isObserving = true
|
||||
self.observingItem = item
|
||||
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, options: [.new], context: &AVPlayerItemObserver.context)
|
||||
}
|
||||
}
|
||||
|
||||
private func stopObservingCurrentItem() {
|
||||
observingItem?.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, context: &AVPlayerItemObserver.context)
|
||||
self.isObserving = false
|
||||
self.observingItem = nil
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
guard context == &AVPlayerItemObserver.context, let observedKeyPath = keyPath else {
|
||||
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
return
|
||||
}
|
||||
|
||||
switch observedKeyPath {
|
||||
case AVPlayerItemKeyPath.duration:
|
||||
if let duration = change?[.newKey] as? CMTime {
|
||||
self.delegate?.item(didUpdateDuration: duration.seconds)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,13 +75,11 @@ class QueueManager<T> {
|
||||
@discardableResult
|
||||
public func next() throws -> T {
|
||||
let nextIndex = _currentIndex + 1
|
||||
if _items.count > nextIndex {
|
||||
_currentIndex = nextIndex
|
||||
return _items[nextIndex]
|
||||
}
|
||||
else {
|
||||
guard _items.count > nextIndex else {
|
||||
throw APError.QueueError.noNextItem
|
||||
}
|
||||
_currentIndex = nextIndex
|
||||
return _items[nextIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,13 +92,11 @@ class QueueManager<T> {
|
||||
@discardableResult
|
||||
public func previous() throws -> T {
|
||||
let previousIndex = _currentIndex - 1
|
||||
if previousIndex >= 0 {
|
||||
_currentIndex = previousIndex
|
||||
return _items[previousIndex]
|
||||
}
|
||||
else {
|
||||
guard previousIndex >= 0 else {
|
||||
throw APError.QueueError.noPreviousItem
|
||||
}
|
||||
_currentIndex = previousIndex
|
||||
return _items[previousIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import MediaPlayer
|
||||
|
||||
/**
|
||||
An audio player that can keep track of a queue of AudioItems.
|
||||
@@ -21,18 +21,35 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
*/
|
||||
public var automaticallyPlayNextSong: Bool = true
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
}
|
||||
|
||||
public override var currentItem: AudioItem? {
|
||||
return queueManager.current
|
||||
}
|
||||
|
||||
/**
|
||||
The previous items held by the queue.
|
||||
*/
|
||||
public var previousItems: [AudioItem]? {
|
||||
return queueManager.previousItems
|
||||
}
|
||||
|
||||
/**
|
||||
The upcoming items in the queue.
|
||||
*/
|
||||
public var nextItems: [AudioItem]? {
|
||||
return queueManager.nextItems
|
||||
}
|
||||
|
||||
/**
|
||||
Add a single item to the queue.
|
||||
|
||||
- parameter item: The item to add.
|
||||
- parameter playWhenReady: If the AudioPlayer has no item loaded, it will load the `item`. If this is `true` it will automatically start playback. Default is `true`.
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func add(item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItem(item)
|
||||
@@ -43,6 +60,13 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Add items to the queue.
|
||||
|
||||
- parameter items: The items to add to the queue.
|
||||
- parameter playWhenReady: If the AudioPlayer has no item loaded, it will load the first item in the list. If this is `true` it will automatically start playback. Default is `true`.
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func add(items: [AudioItem], playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItems(items)
|
||||
@@ -53,25 +77,53 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Step to the next item in the queue.
|
||||
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func next() throws {
|
||||
let nextItem = try queueManager.next()
|
||||
try self.loadItem(nextItem, playWhenReady: true)
|
||||
}
|
||||
|
||||
/**
|
||||
Step to the previous item in the queue.
|
||||
*/
|
||||
public func previous() throws {
|
||||
let previousItem = try queueManager.previous()
|
||||
try self.loadItem(previousItem, playWhenReady: true)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove an item from the queue.
|
||||
|
||||
- parameter index: The index of the item to remove.
|
||||
- throws: `APError.QueueError`
|
||||
*/
|
||||
public func removeItem(atIndex index: Int) throws {
|
||||
try queueManager.remove(atIndex: index)
|
||||
}
|
||||
|
||||
/**
|
||||
Jump to a certain item in the queue.
|
||||
|
||||
- parameter index: The index of the item to jump to.
|
||||
- parameter playWhenReady: Wether the item should start playing when ready. Default is `true`.
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
|
||||
let item = try queueManager.jump(to: index)
|
||||
try self.loadItem(item, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
/**
|
||||
Move an item in the queue from one position to another.
|
||||
|
||||
- parameter fromIndex: The index of the item to move.
|
||||
- parameter toIndex: The index to move the item to.
|
||||
- throws: `APError.QueueError`
|
||||
*/
|
||||
func moveItem(fromIndex: Int, toIndex: Int) throws {
|
||||
try queueManager.moveItem(fromIndex: fromIndex, toIndex: toIndex)
|
||||
}
|
||||
|
||||
@@ -14,23 +14,30 @@ public typealias RemoteCommandHandler = (MPRemoteCommandEvent) -> MPRemoteComman
|
||||
public protocol RemoteCommandProtocol {
|
||||
associatedtype Command: MPRemoteCommand
|
||||
|
||||
var id: String { get }
|
||||
var commandKeyPath: KeyPath<MPRemoteCommandCenter, Command> { get }
|
||||
var handlerKeyPath: KeyPath<RemoteCommandController, RemoteCommandHandler> { get }
|
||||
}
|
||||
|
||||
public struct BaseRemoteCommand: RemoteCommandProtocol {
|
||||
public struct PlayBackCommand: RemoteCommandProtocol {
|
||||
|
||||
public static let play = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.playCommand, handlerKeyPath: \RemoteCommandController.handlePlayCommand)
|
||||
public static let play = PlayBackCommand(id: "Play", commandKeyPath: \MPRemoteCommandCenter.playCommand, handlerKeyPath: \RemoteCommandController.handlePlayCommand)
|
||||
|
||||
public static let pause = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.pauseCommand, handlerKeyPath: \RemoteCommandController.handlePauseCommand)
|
||||
public static let pause = PlayBackCommand(id: "Pause", commandKeyPath: \MPRemoteCommandCenter.pauseCommand, handlerKeyPath: \RemoteCommandController.handlePauseCommand)
|
||||
|
||||
public static let stop = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.stopCommand, handlerKeyPath: \RemoteCommandController.handleStopCommand)
|
||||
public static let stop = PlayBackCommand(id: "Stop", commandKeyPath: \MPRemoteCommandCenter.stopCommand, handlerKeyPath: \RemoteCommandController.handleStopCommand)
|
||||
|
||||
public static let togglePlayPause = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.togglePlayPauseCommand, handlerKeyPath: \RemoteCommandController.handleTogglePlayPauseCommand)
|
||||
public static let togglePlayPause = PlayBackCommand(id: "TogglePlayPause", commandKeyPath: \MPRemoteCommandCenter.togglePlayPauseCommand, handlerKeyPath: \RemoteCommandController.handleTogglePlayPauseCommand)
|
||||
|
||||
public static let nextTrack = PlayBackCommand(id: "NextTrackCommand", commandKeyPath: \MPRemoteCommandCenter.nextTrackCommand, handlerKeyPath: \RemoteCommandController.handleNextTrackCommand)
|
||||
|
||||
public static let previousTrack = PlayBackCommand(id: "PreviousTrack", commandKeyPath: \MPRemoteCommandCenter.previousTrackCommand, handlerKeyPath: \RemoteCommandController.handlePreviousTrackCommand)
|
||||
|
||||
|
||||
public typealias Command = MPRemoteCommand
|
||||
|
||||
public let id: String
|
||||
|
||||
public var commandKeyPath: KeyPath<MPRemoteCommandCenter, MPRemoteCommand>
|
||||
|
||||
public var handlerKeyPath: KeyPath<RemoteCommandController, RemoteCommandHandler>
|
||||
@@ -39,10 +46,12 @@ public struct BaseRemoteCommand: RemoteCommandProtocol {
|
||||
|
||||
public struct ChangePlaybackPositionCommand: RemoteCommandProtocol {
|
||||
|
||||
public static let changePlaybackPosition = ChangePlaybackPositionCommand(commandKeyPath: \MPRemoteCommandCenter.changePlaybackPositionCommand, handlerKeyPath: \RemoteCommandController.handleChangePlaybackPositionCommand)
|
||||
public static let changePlaybackPosition = ChangePlaybackPositionCommand(id: "ChangePlaybackPosition", commandKeyPath: \MPRemoteCommandCenter.changePlaybackPositionCommand, handlerKeyPath: \RemoteCommandController.handleChangePlaybackPositionCommand)
|
||||
|
||||
public typealias Command = MPChangePlaybackPositionCommand
|
||||
|
||||
public let id: String
|
||||
|
||||
public var commandKeyPath: KeyPath<MPRemoteCommandCenter, MPChangePlaybackPositionCommand>
|
||||
|
||||
public var handlerKeyPath: KeyPath<RemoteCommandController, RemoteCommandHandler>
|
||||
@@ -51,12 +60,14 @@ public struct ChangePlaybackPositionCommand: RemoteCommandProtocol {
|
||||
|
||||
public struct SkipIntervalCommand: RemoteCommandProtocol {
|
||||
|
||||
public static let skipForward = SkipIntervalCommand(commandKeyPath: \MPRemoteCommandCenter.skipForwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipForwardCommand)
|
||||
public static let skipForward = SkipIntervalCommand(id: "SkipForward", commandKeyPath: \MPRemoteCommandCenter.skipForwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipForwardCommand)
|
||||
|
||||
public static let skipBackward = SkipIntervalCommand(commandKeyPath: \MPRemoteCommandCenter.skipBackwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipBackwardCommand)
|
||||
public static let skipBackward = SkipIntervalCommand(id: "SkipBackward", commandKeyPath: \MPRemoteCommandCenter.skipBackwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipBackwardCommand)
|
||||
|
||||
public typealias Command = MPSkipIntervalCommand
|
||||
|
||||
public let id: String
|
||||
|
||||
public var commandKeyPath: KeyPath<MPRemoteCommandCenter, MPSkipIntervalCommand>
|
||||
|
||||
public var handlerKeyPath: KeyPath<RemoteCommandController, RemoteCommandHandler>
|
||||
@@ -78,6 +89,10 @@ public enum RemoteCommand {
|
||||
|
||||
case togglePlayPause
|
||||
|
||||
case next
|
||||
|
||||
case previous
|
||||
|
||||
case changePlaybackPosition
|
||||
|
||||
case skipForward(preferredIntervals: [NSNumber])
|
||||
@@ -94,6 +109,8 @@ public enum RemoteCommand {
|
||||
.pause,
|
||||
.stop,
|
||||
.togglePlayPause,
|
||||
.next,
|
||||
.previous,
|
||||
.changePlaybackPosition,
|
||||
.skipForward(preferredIntervals: []),
|
||||
.skipBackward(preferredIntervals: []),
|
||||
|
||||
@@ -18,6 +18,8 @@ public class RemoteCommandController {
|
||||
|
||||
weak var audioPlayer: AudioPlayer?
|
||||
|
||||
var commandTargetPointers: [String: Any] = [:]
|
||||
|
||||
init() {}
|
||||
|
||||
/**
|
||||
@@ -40,37 +42,38 @@ public class RemoteCommandController {
|
||||
|
||||
private func enableCommand<Command: RemoteCommandProtocol>(_ command: Command) {
|
||||
center[keyPath: command.commandKeyPath].isEnabled = true
|
||||
center[keyPath: command.commandKeyPath].addTarget(handler: self[keyPath: command.handlerKeyPath])
|
||||
commandTargetPointers[command.id] = center[keyPath: command.commandKeyPath].addTarget(handler: self[keyPath: command.handlerKeyPath])
|
||||
}
|
||||
|
||||
private func disableCommand<Command: RemoteCommandProtocol>(_ command: Command) {
|
||||
center[keyPath: command.commandKeyPath].isEnabled = false
|
||||
center[keyPath: command.commandKeyPath].removeTarget(self[keyPath: command.handlerKeyPath])
|
||||
center[keyPath: command.commandKeyPath].removeTarget(commandTargetPointers[command.id])
|
||||
commandTargetPointers.removeValue(forKey: command.id)
|
||||
}
|
||||
|
||||
private func enable(command: RemoteCommand) {
|
||||
switch command {
|
||||
case .play: self.enableCommand(BaseRemoteCommand.play)
|
||||
case .pause: self.enableCommand(BaseRemoteCommand.pause)
|
||||
case .stop: self.enableCommand(BaseRemoteCommand.stop)
|
||||
case .togglePlayPause: self.enableCommand(BaseRemoteCommand.togglePlayPause)
|
||||
case .play: self.enableCommand(PlayBackCommand.play)
|
||||
case .pause: self.enableCommand(PlayBackCommand.pause)
|
||||
case .stop: self.enableCommand(PlayBackCommand.stop)
|
||||
case .togglePlayPause: self.enableCommand(PlayBackCommand.togglePlayPause)
|
||||
case .next: self.enableCommand(PlayBackCommand.nextTrack)
|
||||
case .previous: self.enableCommand(PlayBackCommand.previousTrack)
|
||||
case .changePlaybackPosition: self.enableCommand(ChangePlaybackPositionCommand.changePlaybackPosition)
|
||||
|
||||
case .skipForward(let preferredIntervals):
|
||||
self.enableCommand(SkipIntervalCommand.skipForward.set(preferredIntervals: preferredIntervals))
|
||||
|
||||
case .skipBackward(let preferredIntervals):
|
||||
self.enableCommand(SkipIntervalCommand.skipBackward.set(preferredIntervals: preferredIntervals))
|
||||
case .skipForward(let preferredIntervals): self.enableCommand(SkipIntervalCommand.skipForward.set(preferredIntervals: preferredIntervals))
|
||||
case .skipBackward(let preferredIntervals): self.enableCommand(SkipIntervalCommand.skipBackward.set(preferredIntervals: preferredIntervals))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func disable(command: RemoteCommand) {
|
||||
switch command {
|
||||
case .play: self.disableCommand(BaseRemoteCommand.play)
|
||||
case .pause: self.disableCommand(BaseRemoteCommand.pause)
|
||||
case .stop: self.disableCommand(BaseRemoteCommand.stop)
|
||||
case .togglePlayPause: self.disableCommand(BaseRemoteCommand.togglePlayPause)
|
||||
case .play: self.disableCommand(PlayBackCommand.play)
|
||||
case .pause: self.disableCommand(PlayBackCommand.pause)
|
||||
case .stop: self.disableCommand(PlayBackCommand.stop)
|
||||
case .togglePlayPause: self.disableCommand(PlayBackCommand.togglePlayPause)
|
||||
case .next: self.disableCommand(PlayBackCommand.nextTrack)
|
||||
case .previous: self.disableCommand(PlayBackCommand.previousTrack)
|
||||
case .changePlaybackPosition: self.disableCommand(ChangePlaybackPositionCommand.changePlaybackPosition)
|
||||
case .skipForward(_): self.disableCommand(SkipIntervalCommand.skipForward)
|
||||
case .skipBackward(_): self.disableCommand(SkipIntervalCommand.skipBackward)
|
||||
@@ -80,49 +83,64 @@ public class RemoteCommandController {
|
||||
// MARK: - Handlers
|
||||
|
||||
lazy var handlePlayCommand: RemoteCommandHandler = { (event) in
|
||||
do {
|
||||
try self.audioPlayer?.play()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.play()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePauseCommand: RemoteCommandHandler = { (event) in
|
||||
do {
|
||||
try self.audioPlayer?.pause()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.pause()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleStopCommand: RemoteCommandHandler = { (event) in
|
||||
self.audioPlayer?.stop()
|
||||
return .success
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
audioPlayer.stop()
|
||||
return .success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { (event) in
|
||||
do {
|
||||
try self.audioPlayer?.togglePlaying()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.togglePlaying()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleSkipForwardCommand: RemoteCommandHandler = { (event) in
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
try? audioPlayer.seek(to: audioPlayer.currentTime + Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
do {
|
||||
try audioPlayer.seek(to: audioPlayer.currentTime + Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
@@ -130,17 +148,48 @@ public class RemoteCommandController {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
try? audioPlayer.seek(to: audioPlayer.currentTime - Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
do {
|
||||
try audioPlayer.seek(to: audioPlayer.currentTime - Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { (event) in
|
||||
if let event = event as? MPChangePlaybackPositionCommandEvent {
|
||||
if let event = event as? MPChangePlaybackPositionCommandEvent,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try self.audioPlayer?.seek(to: event.positionTime)
|
||||
try audioPlayer.seek(to: event.positionTime)
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleNextTrackCommand: RemoteCommandHandler = { (event) in
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.next()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePreviousTrackCommand: RemoteCommandHandler = { (event) in
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.previous()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
@@ -157,8 +206,19 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.noActionableNowPlayingItem
|
||||
}
|
||||
}
|
||||
else if let error = error as? APError.LoadError {
|
||||
switch error {
|
||||
case .invalidSourceUrl(_):
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
}
|
||||
else if let error = error as? APError.QueueError {
|
||||
switch error {
|
||||
case .noNextItem, .noPreviousItem, .invalidIndex(_, _):
|
||||
return MPRemoteCommandHandlerStatus.noSuchContent
|
||||
}
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
/**
|
||||
A simple audio player that keeps on item at a time.
|
||||
*/
|
||||
public class SimpleAudioPlayer: AudioPlayer {
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
}
|
||||
|
||||
/**
|
||||
Load an AudioItem into the manager.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user