Compare commits

..

36 Commits

Author SHA1 Message Date
jorgenhenrichsen 454e036449 Merge pull request #19 from jorgenhenrichsen/dev
Dev
2018-09-27 15:49:55 +02:00
Jørgen Henrichsen 8fb3b8424d Update podspec
Bumped to version 0.3.3
2018-09-27 15:39:40 +02:00
jorgenhenrichsen 731863a900 Merge pull request #18 from jorgenhenrichsen/interruptions
Interruptions
2018-09-27 15:34:03 +02:00
jorgenhenrichsen 45582b3f43 Update README
More info about interruptions
2018-09-27 15:25:15 +02:00
Jørgen Henrichsen 534b62da9e Added tests for interruption notifications 2018-09-27 14:50:33 +02:00
Jørgen Henrichsen e3cb3c4a51 Added possiblity to turn of interruption notifications 2018-09-27 14:50:10 +02:00
Jørgen Henrichsen a0e05a885a Update README
Added part about interruptions.
2018-09-27 09:53:09 +02:00
Jørgen Henrichsen 1a13887a65 Added interruption handler and delegate protocol. 2018-09-27 09:52:52 +02:00
jorgenhenrichsen da6e83f64a Merge pull request #17 from jorgenhenrichsen/testing
Testing
2018-09-25 20:10:14 +02:00
Jørgen Henrichsen 0f2bfb3e79 Use long source when testing SimpleAudioPlayer 2018-09-25 19:52:38 +02:00
Jørgen Henrichsen eabeca93a0 Use shorter buffer duration for audio tests. 2018-09-21 17:35:20 +02:00
Jørgen Henrichsen 6a93840463 More tests
Added for SimpleAudioPlayer and QueuedAudioPlayer.
Made next and previous items in the queue non-optional, instead they will be empty when no items are in the queue.
2018-09-06 23:39:03 +02:00
Jørgen Henrichsen 1261f88803 Removed play to end tests. 2018-08-18 09:29:44 +02:00
Jørgen Henrichsen 6239d6d5e6 Shorter test sound 2018-08-18 09:23:19 +02:00
Jørgen Henrichsen a63771a040 More tests for AVPlayerWrapper and AudioPlayer 2018-08-18 09:03:06 +02:00
Jørgen Henrichsen 7d27ef286f Add the TestSound to the project 2018-08-05 15:28:27 +02:00
Jørgen Henrichsen bf0d3a79dd Use a smaller, silent test sound. 2018-08-05 14:59:31 +02:00
Jørgen Henrichsen 22dec9d6b1 Use low buffer duration when testing audio 2018-08-05 13:48:39 +02:00
jorgenhenrichsen b7be6338c6 Add codecov badge to README 2018-08-05 13:36:38 +02:00
jorgenhenrichsen 97b70d056a Added codecov yml
Ignore Example directory
2018-08-05 13:25:57 +02:00
jorgenhenrichsen c9bce64bc1 Only upload coverage for SwiftAudio 2018-08-05 13:14:57 +02:00
Jørgen Henrichsen dafcbdfe3e Set automaticallyWaitsToMinimizeStalling to false in test case 2018-08-05 13:04:46 +02:00
jorgenhenrichsen 24666c3ab5 Merge pull request #15 from jorgenhenrichsen/master
Master -> Dev
2018-08-05 12:57:18 +02:00
jorgenhenrichsen 8de5d678b8 Use iOS 11.4 on simulator 2018-08-05 12:51:39 +02:00
jorgenhenrichsen 70a55f22e0 Update travis.yml
*Use Xcode 9.4
*Use Swift lang
2018-08-05 12:47:03 +02:00
jorgenhenrichsen e180fc7a72 Add codecov coverage uploading 2018-08-05 12:38:39 +02:00
Jørgen Henrichsen 9008c6f9a0 Renamed the delegate holder 2018-08-05 12:23:44 +02:00
Jørgen Henrichsen d49c522032 Added test case in AudioPlayerSessionControllerTests
Added case for deactivating the audio session.
2018-08-05 12:23:10 +02:00
Jørgen Henrichsen 7681d5d983 Added AudioPlayerTests 2018-08-05 12:22:39 +02:00
Jørgen Henrichsen 8b1e57b3c0 Added a Source file for test audio.
Contains the test audio file path, used in all tests.
2018-08-05 10:32:30 +02:00
Jørgen Henrichsen 1c4f5ec738 AudioSessionController tests.
Also activate audio session on play instead of launch in the example.
2018-08-05 10:02:53 +02:00
Jørgen Henrichsen d2ed064295 Test seeking function of AVPlayerWrapper 2018-08-05 09:07:34 +02:00
jorgenhenrichsen 0d4060eb68 Merge pull request #14 from jorgenhenrichsen/remote-commands
Remote commands
2018-08-04 19:59:41 +02:00
Jørgen Henrichsen 15a8bc4abd Bumped pod version 2018-08-04 19:53:59 +02:00
Jørgen Henrichsen da5b7702f7 Use next/prev commands in the example. 2018-08-04 08:55:44 +02:00
Jørgen Henrichsen 0066a4121c Added next and previous remote commands.
Also improved error handling in the RemoteCommandController.
2018-08-04 08:55:04 +02:00
28 changed files with 759 additions and 91 deletions
+2
View File
@@ -0,0 +1,2 @@
ignore:
- "Example/.*"
+6 -3
View File
@@ -2,13 +2,16 @@
# * http://www.objc.io/issue-6/travis-ci.html
# * https://github.com/supermarin/xcpretty#usage
osx_image: xcode9.2
language: objective-c
osx_image: xcode9.4
language: swift
cache: cocoapods
podfile: Example/Podfile
before_install:
- gem install cocoapods # Since Travis is not always on latest version
- pod install --project-directory=Example
script:
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/SwiftAudio.xcworkspace -scheme SwiftAudio-Example -sdk iphonesimulator11.2 -destination "OS=11.2,name=iPhone X" | xcpretty
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/SwiftAudio.xcworkspace -scheme SwiftAudio-Example -sdk iphonesimulator11.4 -destination "OS=11.4,name=iPhone X" | xcpretty
- pod lib lint
after_success:
- bash <(curl -s https://codecov.io/bash) -J 'SwiftAudio'
+44 -2
View File
@@ -13,6 +13,14 @@
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130A2067F2E000F789B3 /* QueueViewController.swift */; };
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */; };
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */; };
0708ED6C2116DA4C00EB29BD /* AudioSessionControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */; };
0708ED702116E89900EB29BD /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6F2116E89900EB29BD /* Source.swift */; };
0708ED722116E91D00EB29BD /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6F2116E89900EB29BD /* Source.swift */; };
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */; };
0708ED79211732F500EB29BD /* TestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0708ED78211732F500EB29BD /* TestSound.m4a */; };
0708ED7A211732F500EB29BD /* TestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0708ED78211732F500EB29BD /* TestSound.m4a */; };
07194D212127F6DB002EA8C8 /* ShortTestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */; };
07194D222127F6E9002EA8C8 /* ShortTestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */; };
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */; };
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */; };
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */; };
@@ -22,6 +30,8 @@
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 */; };
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */; };
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.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 */; };
@@ -47,6 +57,11 @@
0707130A2067F2E000F789B3 /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTableViewCell.swift; sourceTree = "<group>"; };
0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueueTableViewCell.xib; sourceTree = "<group>"; };
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionControllerTests.swift; sourceTree = "<group>"; };
0708ED6F2116E89900EB29BD /* Source.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = "<group>"; };
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerTests.swift; sourceTree = "<group>"; };
0708ED78211732F500EB29BD /* TestSound.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestSound.m4a; sourceTree = "<group>"; };
07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = ShortTestSound.m4a; sourceTree = "<group>"; };
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerTimeObserverTests.swift; sourceTree = "<group>"; };
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemNotificationObserverTests.swift; sourceTree = "<group>"; };
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperTests.swift; sourceTree = "<group>"; };
@@ -54,6 +69,8 @@
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>"; };
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleAudioPlayerTests.swift; sourceTree = "<group>"; };
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuedAudioPlayerTests.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>"; };
@@ -95,6 +112,18 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0708ED712116E91300EB29BD /* Source */ = {
isa = PBXGroup;
children = (
07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */,
0708ED6F2116E89900EB29BD /* Source.swift */,
07732650205EACA300C4D1CD /* WAV-MP3.wav */,
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */,
0708ED78211732F500EB29BD /* TestSound.m4a */,
);
path = Source;
sourceTree = "<group>";
};
607FACC71AFB9204008FA782 = {
isa = PBXGroup;
children = (
@@ -146,14 +175,17 @@
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */,
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */,
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */,
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */,
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */,
0775575820668B020002C6A1 /* QueueManagerTests.swift */,
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
07732650205EACA300C4D1CD /* WAV-MP3.wav */,
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */,
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */,
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */,
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */,
0708ED712116E91300EB29BD /* Source */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
@@ -296,6 +328,8 @@
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
07732655205ECE1C00C4D1CD /* nasa_throttle_up.mp3 in Resources */,
07194D222127F6E9002EA8C8 /* ShortTestSound.m4a in Resources */,
0708ED79211732F500EB29BD /* TestSound.m4a in Resources */,
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */,
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */,
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
@@ -306,6 +340,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
07194D212127F6DB002EA8C8 /* ShortTestSound.m4a in Resources */,
0708ED7A211732F500EB29BD /* TestSound.m4a in Resources */,
07732653205EB1B500C4D1CD /* nasa_throttle_up.mp3 in Resources */,
07732651205EACA300C4D1CD /* WAV-MP3.wav in Resources */,
);
@@ -428,6 +464,7 @@
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */,
070713072067EB4F00F789B3 /* Double + Extensions.swift in Sources */,
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
0708ED722116E91D00EB29BD /* Source.swift in Sources */,
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */,
070713092067EFFB00F789B3 /* AudioController.swift in Sources */,
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
@@ -438,9 +475,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0708ED702116E89900EB29BD /* Source.swift in Sources */,
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */,
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */,
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
0708ED6C2116DA4C00EB29BD /* AudioSessionControllerTests.swift in Sources */,
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */,
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */,
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */,
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */,
+2 -3
View File
@@ -27,12 +27,11 @@ class AudioController {
player.remoteCommands = [
.stop,
.togglePlayPause,
.skipForward(preferredIntervals: [30]),
.skipBackward(preferredIntervals: [30]),
.next,
.previous,
.changePlaybackPosition
]
try? audioSessionController.set(category: .playback)
try? audioSessionController.activateSession()
try? player.add(items: sources, playWhenReady: false)
}
+2 -2
View File
@@ -46,7 +46,7 @@ extension QueueViewController: UITableViewDataSource, UITableViewDelegate {
case 0:
return 1
case 1:
return controller.player.nextItems?.count ?? 0
return controller.player.nextItems.count ?? 0
default:
return 0
}
@@ -60,7 +60,7 @@ extension QueueViewController: UITableViewDataSource, UITableViewDelegate {
case 0:
item = controller.player.currentItem
case 1:
item = controller.player.nextItems?[indexPath.row]
item = controller.player.nextItems[indexPath.row]
default:
item = nil
}
+3
View File
@@ -31,6 +31,9 @@ class ViewController: UIViewController {
}
@IBAction func togglePlay(_ sender: Any) {
if (!controller.audioSessionController.audioSessionIsActive) {
try? controller.audioSessionController.activateSession()
}
try? controller.player.togglePlaying()
}
@@ -15,7 +15,7 @@ class AVPlayerItemNotificationObserverTests: QuickSpec {
var observer: AVPlayerItemNotificationObserver!
beforeEach {
item = AVPlayerItem(asset: AVURLAsset(url: URL(string: "https://p.scdn.co/mp3-preview/4839b070015ab7d6de9fec1756e1f3096d908fba")!))
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
observer = AVPlayerItemNotificationObserver()
}
@@ -4,8 +4,6 @@ import AVFoundation
@testable import SwiftAudio
let source = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
class AVPlayerItemObserverTests: QuickSpec {
override func spec() {
@@ -19,7 +17,7 @@ class AVPlayerItemObserverTests: QuickSpec {
context("when observing", {
var item: AVPlayerItem!
beforeEach {
item = AVPlayerItem(url: URL(fileURLWithPath: source))
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
observer.startObserving(item: item)
}
@@ -36,7 +34,7 @@ class AVPlayerItemObserverTests: QuickSpec {
context("when observing", {
var item: AVPlayerItem!
beforeEach {
item = AVPlayerItem(url: URL(fileURLWithPath: source))
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
observer.startObserving(item: item)
}
it("should be observing", closure: {
+1 -1
View File
@@ -34,7 +34,7 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
context("when player has started", {
beforeEach {
player.replaceCurrentItem(with: AVPlayerItem(asset: AVURLAsset(url: URL(string: "https://p.scdn.co/mp3-preview/4839b070015ab7d6de9fec1756e1f3096d908fba")!)))
player.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: Source.path)))
player.play()
}
+43 -18
View File
@@ -6,12 +6,8 @@ import Nimble
class AVPlayerWrapperTests: QuickSpec {
override func spec() {
let source = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
let shortSource = Bundle.main.path(forResource: "nasa_throttle_up", ofType: "mp3")!
describe("An AVPlayerWrapper") {
var wrapper: AVPlayerWrapper!
@@ -19,17 +15,18 @@ class AVPlayerWrapperTests: QuickSpec {
beforeEach {
wrapper = AVPlayerWrapper()
wrapper.automaticallyWaitsToMinimizeStalling = false
wrapper.bufferDuration = 0.0001
wrapper.volume = 0.0
}
describe("state", {
describe("its 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)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
}
it("should be loading", closure: {
@@ -52,7 +49,7 @@ class AVPlayerWrapperTests: QuickSpec {
context("when playing a source", {
beforeEach {
try? wrapper.load(fromFilePath: source, playWhenReady: true)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
}
it("should eventually be playing", closure: {
@@ -63,7 +60,7 @@ class AVPlayerWrapperTests: QuickSpec {
context("when pausing the source", {
let holder = AudioPlayerDelegateHolder()
let holder = AVPlayerWrapperDelegateHolder()
beforeEach {
wrapper.delegate = holder
@@ -72,7 +69,7 @@ class AVPlayerWrapperTests: QuickSpec {
try? wrapper.pause()
}
}
try? wrapper.load(fromFilePath: source, playWhenReady: true)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
}
it("should eventually be paused", closure: {
@@ -81,7 +78,7 @@ class AVPlayerWrapperTests: QuickSpec {
})
context("when toggling the source from play", {
let holder = AudioPlayerDelegateHolder()
let holder = AVPlayerWrapperDelegateHolder()
beforeEach {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
@@ -89,20 +86,20 @@ class AVPlayerWrapperTests: QuickSpec {
try? wrapper.togglePlaying()
}
}
try? wrapper.load(fromFilePath: source, playWhenReady: true)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
}
it("should eventually be playing", closure: {
it("should eventually be paused", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
})
})
context("when stopping the source", {
var holder: AudioPlayerDelegateHolder!
var holder: AVPlayerWrapperDelegateHolder!
var receivedIdleUpdate: Bool = false
beforeEach {
holder = AudioPlayerDelegateHolder()
holder = AVPlayerWrapperDelegateHolder()
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
@@ -112,7 +109,7 @@ class AVPlayerWrapperTests: QuickSpec {
receivedIdleUpdate = true
}
}
try? wrapper.load(fromFilePath: source, playWhenReady: true)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
}
it("should eventually be 'idle'", closure: {
@@ -138,20 +135,45 @@ class AVPlayerWrapperTests: QuickSpec {
context("when loading source", {
beforeEach {
try? wrapper.load(fromFilePath: source, playWhenReady: false)
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
}
it("should eventually not be 0", closure: {
expect(wrapper.duration).toEventuallyNot(equal(0))
})
})
})
describe("its current time", {
it("should be 0", closure: {
expect(wrapper.currentTime).to(equal(0))
})
context("when seeking to a time", {
let holder = AVPlayerWrapperDelegateHolder()
let seekTime: TimeInterval = 0.5
beforeEach {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .ready && wrapper.duration != 0 {
try? wrapper.seek(to: seekTime)
}
}
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
}
it("should eventually be equal to the seeked time", closure: {
expect(wrapper.currentTime).toEventually(equal(seekTime))
})
})
})
}
}
}
class AudioPlayerDelegateHolder: AVPlayerWrapperDelegate {
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
@@ -188,7 +210,10 @@ class AudioPlayerDelegateHolder: AVPlayerWrapperDelegate {
}
func AVWrapper(didUpdateDuration duration: Double) {
if let state = self.state {
self.stateUpdate?(state)
}
}
}
+185
View File
@@ -0,0 +1,185 @@
import Quick
import Nimble
@testable import SwiftAudio
class AudioPlayerTests: QuickSpec {
override func spec() {
describe("An AudioPlayer") {
var audioPlayer: AudioPlayer!
beforeEach {
audioPlayer = AudioPlayer()
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.bufferDuration = 0.0001
audioPlayer.volume = 0
}
describe("its state", {
it("should be idle", closure: {
expect(audioPlayer.playerState).to(equal(AudioPlayerState.idle))
})
context("when audio item is loaded", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
}
it("it should eventually be ready", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
})
})
context("when an item is loaded (playWhenReady=true)", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
}
it("it should eventually be playing", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
context("when playing an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { state in
print(state.rawValue)
if state == .ready {
try? audioPlayer.play()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be playing", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
context("when pausing an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
try? audioPlayer.pause()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be paused", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.paused))
})
})
context("when stopping an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
audioPlayer.stop()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be idle", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.idle))
})
})
})
describe("its current time", {
it("should be 0", closure: {
expect(audioPlayer.currentTime).to(equal(0))
})
context("when seeking to a time", {
let holder = AudioPlayerDelegateHolder()
let seekTime: TimeInterval = 0.5
beforeEach {
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .ready && audioPlayer.duration != 0 {
try? audioPlayer.seek(to: seekTime)
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be equal to the seeked time", closure: {
expect(audioPlayer.currentTime).toEventually(equal(seekTime))
})
})
})
describe("its rate", {
it("should be 0", closure: {
expect(audioPlayer.rate).to(equal(0))
})
context("when playing an item", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be 1.0", closure: {
expect(audioPlayer.rate).toEventually(equal(1.0))
})
})
})
}
}
}
class AudioPlayerDelegateHolder: AudioPlayerDelegate {
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
var state: AudioPlayerState? {
didSet {
if let state = state {
stateUpdate?(state)
}
}
}
func audioPlayer(playerDidChangeState state: AudioPlayerState) {
self.state = state
}
func audioPlayerItemDidComplete() {
}
func audioPlayer(secondsElapsed seconds: Double) {
}
func audioPlayer(failedWithError error: Error?) {
}
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
}
func audioPlayer(didUpdateDuration duration: Double) {
if let state = self.state {
self.stateUpdate?(state)
}
}
}
@@ -0,0 +1,86 @@
import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
class AudioSessionControllerTests: QuickSpec {
override func spec() {
describe("An AudioSessionController") {
let audioSessionController: AudioSessionController = AudioSessionController.shared
it("should be inactive", closure: {
expect(audioSessionController.audioSessionIsActive).to(beFalse())
})
context("when session is activated", {
beforeEach {
try? audioSessionController.activateSession()
}
it("should be active", closure: {
expect(audioSessionController.audioSessionIsActive).to(beTrue())
})
context("when deactivating session", {
beforeEach {
try? audioSessionController.deactivateSession()
}
it("should be inactive", closure: {
expect(audioSessionController.audioSessionIsActive).to(beFalse())
})
})
})
describe("its isObservingForInterruptions", {
it("should be true", closure: {
expect(audioSessionController.isObservingForInterruptions).to(beTrue())
})
context("when isObservingForInterruptions is set to false", {
beforeEach {
audioSessionController.isObservingForInterruptions = false
}
it("should be false", closure: {
expect(audioSessionController.isObservingForInterruptions).to(beFalse())
})
})
})
describe("its delegate", {
context("when a interruption arrives", {
var delegate: AudioSessionControllerDelegateImplementation!
beforeEach {
let notification = Notification(name: .AVAudioSessionInterruption, object: nil, userInfo: [
AVAudioSessionInterruptionTypeKey: UInt(0)
])
delegate = AudioSessionControllerDelegateImplementation()
audioSessionController.delegate = delegate
audioSessionController.handleInterruption(notification: notification)
}
it("should eventually be updated with the interruption type", closure: {
expect(delegate.interruptionType).toEventuallyNot(beNil())
})
})
})
}
}
}
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
var interruptionType: AVAudioSessionInterruptionType? = nil
func handleInterruption(type: AVAudioSessionInterruptionType) {
self.interruptionType = type
}
}
+2 -2
View File
@@ -80,7 +80,7 @@ class QueueManagerTests: QuickSpec {
it("should have next items", closure: {
expect(manager.nextItems).toNot(beNil())
expect(manager.nextItems?.count).to(equal(self.dummyItems.count - 1))
expect(manager.nextItems.count).to(equal(self.dummyItems.count - 1))
})
context("then calling next", {
@@ -99,7 +99,7 @@ class QueueManagerTests: QuickSpec {
})
it("should have previous items", closure: {
expect(manager.previousItems.count).to(equal(1))
expect(manager.previousItems).toNot(beNil())
})
context("then calling previous", {
+119
View File
@@ -0,0 +1,119 @@
import Quick
import Nimble
@testable import SwiftAudio
class QueuedAudioPlayerTests: QuickSpec {
override func spec() {
describe("A QueuedAudioPlayer") {
var audioPlayer: QueuedAudioPlayer!
beforeEach {
audioPlayer = QueuedAudioPlayer()
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.bufferDuration = 0.0001
audioPlayer.volume = 0
}
describe("its current item", {
it("should be nil", closure: {
expect(audioPlayer.currentItem).to(beNil())
})
context("when adding one item", {
beforeEach {
try? audioPlayer.add(item: ShortSource.getAudioItem(), playWhenReady: false)
}
it("should not be nil", closure: {
expect(audioPlayer.currentItem).toNot(beNil())
})
})
context("when adding multiple items", {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: false)
}
it("should not be nil", closure: {
expect(audioPlayer.currentItem).toNot(beNil())
})
})
})
describe("its next items", {
it("should be empty", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
context("when adding 2 items", {
beforeEach {
try? audioPlayer.add(items: [Source.getAudioItem(), Source.getAudioItem()])
}
it("should contain 1 item", closure: {
expect(audioPlayer.nextItems.count).to(equal(1))
})
context("then calling next()", {
beforeEach {
try? audioPlayer.next()
}
it("should contain 0 items", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
context("then calling previous()", {
beforeEach {
try? audioPlayer.previous()
}
it("should contain 1 item", closure: {
expect(audioPlayer.nextItems.count).to(equal(1))
})
})
})
context("then removing one item", {
beforeEach {
try? audioPlayer.removeItem(atIndex: 1)
}
it("should be empty", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
})
context("then jumping to the last item", {
beforeEach {
try? audioPlayer.jumpToItem(atIndex: 1)
}
it("should be empty", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
})
})
})
describe("its previous items", {
it("should be empty", closure: {
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", closure: {
expect(audioPlayer.previousItems.count).to(equal(0))
})
context("then calling next()", {
beforeEach {
try? audioPlayer.next()
}
it("should contain one item", closure: {
expect(audioPlayer.previousItems.count).to(equal(1))
})
})
})
})
}
}
}
@@ -0,0 +1,43 @@
import Quick
import Nimble
@testable import SwiftAudio
class SimpleAudioPlayerTests: QuickSpec {
override func spec() {
describe("A SimpleAudioPlayer") {
var player: SimpleAudioPlayer!
beforeEach {
player = SimpleAudioPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.bufferDuration = 0.0001
player.volume = 0
}
describe("its state", {
it("should be idle", closure: {
expect(player.playerState).to(equal(AudioPlayerState.idle))
})
context("when loading an item with playeWhenReady: false", {
beforeEach {
try? player.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be ready", closure: {
expect(player.playerState).toEventually(equal(AudioPlayerState.ready))
})
})
context("when loading an item with playWhenReady: true", {
beforeEach {
try? player.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be playing", closure: {
expect(player.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
})
}
}
}
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
//
// Sources.swift
// SwiftAudio_Tests
//
// Created by Jørgen Henrichsen on 05/08/2018.
// Copyright © 2018 CocoaPods. All rights reserved.
//
import Foundation
import SwiftAudio
struct Source {
static let path: String = Bundle.main.path(forResource: "TestSound", ofType: "m4a")!
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file)
}
}
struct ShortSource {
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file)
}
}
Binary file not shown.
+7
View File
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/jorgenhenrichsen/SwiftAudio.svg?branch=master)](https://travis-ci.org/jorgenhenrichsen/SwiftAudio)
[![Version](https://img.shields.io/cocoapods/v/SwiftAudio.svg?style=flat)](http://cocoapods.org/pods/SwiftAudio)
[![codecov](https://codecov.io/gh/jorgenhenrichsen/SwiftAudio/branch/master/graph/badge.svg)](https://codecov.io/gh/jorgenhenrichsen/SwiftAudio)
[![License](https://img.shields.io/cocoapods/l/SwiftAudio.svg?style=flat)](http://cocoapods.org/pods/SwiftAudio)
[![Platform](https://img.shields.io/cocoapods/p/SwiftAudio.svg?style=flat)](http://cocoapods.org/pods/SwiftAudio)
@@ -76,6 +77,12 @@ try? AudioSessionController.activateSession()
If you want audio to continue playing when the app is inactive, remember to activate background audio:
App Settings -> Capabilities -> Background Modes -> Check 'Audio, AirPlay, and Picture in Picture'.
#### Interruptions
If you are using the AudioSessionController for setting up the audio session, you can use it to handle interruptions too.
Implement `AudioSessionControllerDelegate` and you will be notified by `handleInterruption(type: AVAudioSessionInterruptionType)`.
If you are storing progress for playback time on items when the app quits, it can be a good idea to do it on interruptions as well.
To disable interruption notifcations set `isObservingForInterruptions` to `false`.
### Now Playing Info
The `AudioPlayer` will automatically update the `MPNowPlayingInfoCenter` with artist, title, album, artwork, time if the passed in `AudioItem` supports this.
If you need to set additional properties for some items use `AudioPlayer.add(property:)`. Available properties can be found in `NowPlayingInfoProperty`.
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudio'
s.version = '0.3.1'
s.version = '0.3.3'
s.summary = 'Easy audio streaming for iOS'
# This description is used to generate tags and improve search results.
-1
View File
@@ -210,7 +210,6 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
func enableRemoteCommands(forItem item: AudioItem) {
if let item = item as? RemoteCommandable {
print("Enabling remote commands for item")
self.enableRemoteCommands(item.getCommands())
}
else {
@@ -52,6 +52,10 @@ public enum AudioSessionCategory {
}
public protocol AudioSessionControllerDelegate: class {
func handleInterruption(type: AVAudioSessionInterruptionType)
}
/**
Simple controller for the `AVAudioSession`. If you need more advanced options, just use the `AVAudioSession` directly.
- warning: Do not combine usage of this and `AVAudioSession` directly, chose one.
@@ -61,6 +65,8 @@ public class AudioSessionController {
public static let shared = AudioSessionController()
private let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
private let notificationCenter: NotificationCenter = NotificationCenter.default
private var _isObservingForInterruptions: Bool = false
/**
True if another app is currently playing audio.
@@ -76,7 +82,34 @@ public class AudioSessionController {
*/
public var audioSessionIsActive: Bool = false
private init() {}
/**
Wheter notifications for interruptions are being observed or not.
This is enabled by default.
Set this to false to disable the behaviour.
*/
public var isObservingForInterruptions: Bool {
get {
return _isObservingForInterruptions
}
set {
if newValue == _isObservingForInterruptions {
return
}
if newValue {
registerForInterruptionNotification()
}
else {
unregisterForInterruptionNotification()
}
}
}
public weak var delegate: AudioSessionControllerDelegate?
private init() {
registerForInterruptionNotification()
}
public func activateSession() throws {
do {
@@ -101,4 +134,29 @@ public class AudioSessionController {
try audioSession.setCategory(category.getValue())
}
// MARK: - Interruptions
private func registerForInterruptionNotification() {
notificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: .AVAudioSessionInterruption,
object: nil)
_isObservingForInterruptions = true
}
private func unregisterForInterruptionNotification() {
notificationCenter.removeObserver(self, name: .AVAudioSessionInterruption, object: nil)
_isObservingForInterruptions = false
}
@objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
self.delegate?.handleInterruption(type: type)
}
}
+7 -1
View File
@@ -19,11 +19,17 @@ class QueueManager<T> {
return _items
}
public var nextItems: [T]? {
public var nextItems: [T] {
guard _currentIndex < _items.count else {
return []
}
return Array(_items[_currentIndex + 1..<items.count])
}
public var previousItems: [T] {
if (_currentIndex == 0) {
return []
}
return Array(_items[0..<_currentIndex])
}
+2 -2
View File
@@ -32,14 +32,14 @@ public class QueuedAudioPlayer: AudioPlayer {
/**
The previous items held by the queue.
*/
public var previousItems: [AudioItem]? {
public var previousItems: [AudioItem] {
return queueManager.previousItems
}
/**
The upcoming items in the queue.
*/
public var nextItems: [AudioItem]? {
public var nextItems: [AudioItem] {
return queueManager.nextItems
}
+15 -5
View File
@@ -19,15 +19,19 @@ public protocol RemoteCommandProtocol {
var handlerKeyPath: KeyPath<RemoteCommandController, RemoteCommandHandler> { get }
}
public struct BaseRemoteCommand: RemoteCommandProtocol {
public struct PlayBackCommand: RemoteCommandProtocol {
public static let play = BaseRemoteCommand(id: "Play", commandKeyPath: \MPRemoteCommandCenter.playCommand, handlerKeyPath: \RemoteCommandController.handlePlayCommand)
public static let play = PlayBackCommand(id: "Play", commandKeyPath: \MPRemoteCommandCenter.playCommand, handlerKeyPath: \RemoteCommandController.handlePlayCommand)
public static let pause = BaseRemoteCommand(id: "Pause", commandKeyPath: \MPRemoteCommandCenter.pauseCommand, handlerKeyPath: \RemoteCommandController.handlePauseCommand)
public static let pause = PlayBackCommand(id: "Pause", commandKeyPath: \MPRemoteCommandCenter.pauseCommand, handlerKeyPath: \RemoteCommandController.handlePauseCommand)
public static let stop = BaseRemoteCommand(id: "Stop", commandKeyPath: \MPRemoteCommandCenter.stopCommand, handlerKeyPath: \RemoteCommandController.handleStopCommand)
public static let stop = PlayBackCommand(id: "Stop", commandKeyPath: \MPRemoteCommandCenter.stopCommand, handlerKeyPath: \RemoteCommandController.handleStopCommand)
public static let togglePlayPause = BaseRemoteCommand(id: "TogglePlayPause", 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
@@ -85,6 +89,10 @@ public enum RemoteCommand {
case togglePlayPause
case next
case previous
case changePlaybackPosition
case skipForward(preferredIntervals: [NSNumber])
@@ -101,6 +109,8 @@ public enum RemoteCommand {
.pause,
.stop,
.togglePlayPause,
.next,
.previous,
.changePlaybackPosition,
.skipForward(preferredIntervals: []),
.skipBackward(preferredIntervals: []),
+101 -44
View File
@@ -53,27 +53,27 @@ public class RemoteCommandController {
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)
@@ -83,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
}
@@ -133,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 {
@@ -160,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
}
}