Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 454e036449 | |||
| 8fb3b8424d | |||
| 731863a900 | |||
| 45582b3f43 | |||
| 534b62da9e | |||
| e3cb3c4a51 | |||
| a0e05a885a | |||
| 1a13887a65 | |||
| da6e83f64a | |||
| 0f2bfb3e79 | |||
| eabeca93a0 | |||
| 6a93840463 | |||
| 1261f88803 | |||
| 6239d6d5e6 | |||
| a63771a040 | |||
| 7d27ef286f | |||
| bf0d3a79dd | |||
| 22dec9d6b1 | |||
| b7be6338c6 | |||
| 97b70d056a | |||
| c9bce64bc1 | |||
| dafcbdfe3e | |||
| 24666c3ab5 | |||
| 8de5d678b8 | |||
| 70a55f22e0 | |||
| e180fc7a72 | |||
| 9008c6f9a0 | |||
| d49c522032 | |||
| 7681d5d983 | |||
| 8b1e57b3c0 | |||
| 1c4f5ec738 | |||
| d2ed064295 | |||
| 0d4060eb68 | |||
| 15a8bc4abd | |||
| da5b7702f7 | |||
| 0066a4121c |
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "Example/.*"
|
||||
+6
-3
@@ -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'
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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", {
|
||||
|
||||
@@ -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.
@@ -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.
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://travis-ci.org/jorgenhenrichsen/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
[](https://codecov.io/gh/jorgenhenrichsen/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
[](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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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: []),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user