Compare commits

...

15 Commits

Author SHA1 Message Date
David Chavez 7ed74b80ec Release 0.14.2 2021-09-28 10:04:01 +02:00
David Chavez 2773e4bfec Trigger skip and jump events only when actually taking action 2021-09-28 09:57:24 +02:00
David Chavez 77dc8f4ff1 Fix flickering elapsed time on a lock screen after pause 2021-09-28 09:41:04 +02:00
David Chavez accdf2c00c Rename exposed SPM package name 2021-09-28 09:31:31 +02:00
David Chavez 542d3a5764 Remove syncRemoteCommandsWithCommandCenter
Removed in favor of a didSet on remoteCommands property
2021-09-28 09:28:14 +02:00
David Chavez 4131e54f3e Create FUNDING.yml 2021-09-28 09:19:46 +02:00
David Chavez 03c4a7310f Release 0.14.2 2021-09-25 00:17:58 +02:00
David Chavez 9d2d2594a1 Replace commands based on diff to avoid iOS 15 issues 2021-09-25 00:17:31 +02:00
David Chavez 4e790876cb Release 0.14.1 2021-09-23 10:51:50 +02:00
David Chavez b19d01bdfc Allow manual resyncing of command center commands 2021-09-23 10:51:21 +02:00
David Chavez 3c8ecb353c Release 0.14.0 2021-09-16 17:50:55 +02:00
David Chavez cafd513468 Raise minimum deployment target to iOS11
Due to breaking change in Swift 5.5 & Xcode 13
2021-09-16 17:50:43 +02:00
David Chavez 7b8a4f318d Add tests for repeat mode 2021-08-19 16:27:41 +02:00
David Chavez acab6473b2 Release 0.13.2 2021-08-12 11:40:50 +02:00
David Chavez 57b6fb08f3 Fix tests 2021-08-12 11:40:21 +02:00
31 changed files with 273 additions and 83 deletions
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: DoubleSymmetry
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+12 -12
View File
@@ -44,9 +44,9 @@
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */; };
9B05AA312660276400C7A389 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA302660276400C7A389 /* Quick */; };
9B05AA332660276400C7A389 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA322660276400C7A389 /* Nimble */; };
9B05AA3A266028E200C7A389 /* SwiftAudio in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA39266028E200C7A389 /* SwiftAudio */; };
9B05AA3C26602C0E00C7A389 /* SwiftAudio in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA3B26602C0E00C7A389 /* SwiftAudio */; };
9B521D0E2662937600EF0C3A /* MockDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */; };
9B77D79426C522D0004BAF2F /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77D79326C522D0004BAF2F /* SwiftAudioEx */; };
9B77D79626C52382004BAF2F /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77D79526C52382004BAF2F /* SwiftAudioEx */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -103,7 +103,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B05AA3A266028E200C7A389 /* SwiftAudio in Frameworks */,
9B77D79426C522D0004BAF2F /* SwiftAudioEx in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -111,7 +111,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B05AA3C26602C0E00C7A389 /* SwiftAudio in Frameworks */,
9B77D79626C52382004BAF2F /* SwiftAudioEx in Frameworks */,
9B05AA312660276400C7A389 /* Quick in Frameworks */,
9B05AA332660276400C7A389 /* Nimble in Frameworks */,
);
@@ -244,7 +244,7 @@
);
name = SwiftAudio_Example;
packageProductDependencies = (
9B05AA39266028E200C7A389 /* SwiftAudio */,
9B77D79326C522D0004BAF2F /* SwiftAudioEx */,
);
productName = SwiftAudio;
productReference = 607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */;
@@ -267,7 +267,7 @@
packageProductDependencies = (
9B05AA302660276400C7A389 /* Quick */,
9B05AA322660276400C7A389 /* Nimble */,
9B05AA3B26602C0E00C7A389 /* SwiftAudio */,
9B77D79526C52382004BAF2F /* SwiftAudioEx */,
);
productName = Tests;
productReference = 607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */;
@@ -532,7 +532,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = HPNZWPB9JK;
INFOPLIST_FILE = SwiftAudio/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -551,7 +551,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = HPNZWPB9JK;
INFOPLIST_FILE = SwiftAudio/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -672,13 +672,13 @@
package = 9B05AA2C2660274F00C7A389 /* XCRemoteSwiftPackageReference "Nimble" */;
productName = Nimble;
};
9B05AA39266028E200C7A389 /* SwiftAudio */ = {
9B77D79326C522D0004BAF2F /* SwiftAudioEx */ = {
isa = XCSwiftPackageProductDependency;
productName = SwiftAudio;
productName = SwiftAudioEx;
};
9B05AA3B26602C0E00C7A389 /* SwiftAudio */ = {
9B77D79526C52382004BAF2F /* SwiftAudioEx */ = {
isa = XCSwiftPackageProductDependency;
productName = SwiftAudio;
productName = SwiftAudioEx;
};
/* End XCSwiftPackageProductDependency section */
};
+1 -1
View File
@@ -7,7 +7,7 @@
//
import Foundation
import SwiftAudio
import SwiftAudioEx
class AudioController {
+1 -1
View File
@@ -7,7 +7,7 @@
//
import UIKit
import SwiftAudio
import SwiftAudioEx
class QueueViewController: UIViewController {
+7 -2
View File
@@ -7,7 +7,7 @@
//
import UIKit
import SwiftAudio
import SwiftAudioEx
import AVFoundation
import MediaPlayer
@@ -31,6 +31,7 @@ class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
controller.player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
controller.player.event.playbackEnd.addListener(self, handleAudioPlayerPlaybackEnd(data:))
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
@@ -106,7 +107,7 @@ class ViewController: UIViewController {
// MARK: - AudioPlayer Event Handlers
func handleAudioPlayerStateChange(data: AudioPlayer.StateChangeEventData) {
print(data)
print("state=\(data)")
DispatchQueue.main.async {
self.setPlayButtonState(forAudioPlayerState: data)
switch data {
@@ -126,6 +127,10 @@ class ViewController: UIViewController {
}
}
}
func handleAudioPlayerPlaybackEnd(data: AudioPlayer.PlaybackEndEventData) {
print("playEndReason=\(data)")
}
func handleAudioPlayerSecondElapsed(data: AudioPlayer.SecondElapseEventData) {
if !isScrubbing {
@@ -2,7 +2,7 @@ import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class AVPlayerItemNotificationObserverTests: QuickSpec {
@@ -2,7 +2,7 @@ import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class AVPlayerItemObserverTests: QuickSpec {
+1 -1
View File
@@ -2,7 +2,7 @@ import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
@@ -2,7 +2,7 @@ import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class AVPlayerTimeObserverTests: QuickSpec {
+1 -1
View File
@@ -1,7 +1,7 @@
import AVFoundation
import XCTest
@testable import SwiftAudio
@testable import SwiftAudioEx
class AVPlayerWrapperTests: XCTestCase {
+1 -1
View File
@@ -2,7 +2,7 @@ import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
@testable import SwiftAudioEx
class AudioPlayerEventTests: QuickSpec {
+3 -3
View File
@@ -3,7 +3,7 @@ import Nimble
import AVFoundation
import XCTest
@testable import SwiftAudio
@testable import SwiftAudioEx
class AudioPlayerTests: XCTestCase {
@@ -124,8 +124,8 @@ class AudioPlayerTests: XCTestCase {
// MARK: - Rate
func test_AudioPlayer__rate__should_be_0() {
XCTAssert(audioPlayer.rate == 0.0)
func test_AudioPlayer__rate__should_be_1() {
XCTAssert(audioPlayer.rate == 1.0)
}
func test_AudioPlayer__rate__playing_source__should_be_1() {
@@ -2,7 +2,7 @@ import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class AudioSessionControllerTests: QuickSpec {
+1 -1
View File
@@ -9,7 +9,7 @@
import Foundation
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class NonFailingAudioSession: AudioSession {
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation
@testable import SwiftAudio
@testable import SwiftAudioEx
final class MockDispatchQueue: DispatchQueueType {
func async(flags: DispatchWorkItemFlags, execute work: @escaping @convention(block) () -> Void) {
@@ -9,7 +9,7 @@
import Foundation
import AVFoundation
@testable import SwiftAudio
@testable import SwiftAudioEx
class NowPlayingInfoCenter_Mock: NowPlayingInfoCenter {
@@ -9,7 +9,7 @@
import Foundation
import MediaPlayer
@testable import SwiftAudio
@testable import SwiftAudioEx
class NowPlayingInfoController_Mock: NowPlayingInfoControllerProtocol {
@@ -2,7 +2,7 @@ import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
@testable import SwiftAudioEx
class NowPlayingInfoControllerTests: QuickSpec {
+1 -1
View File
@@ -2,7 +2,7 @@ import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
@testable import SwiftAudioEx
/// Tests that the AudioPlayer is automatically updating the values it should update in the NowPlayingInfoController.
class NowPlayingInfoTests: QuickSpec {
+1 -1
View File
@@ -1,7 +1,7 @@
import Quick
import Nimble
@testable import SwiftAudio
@testable import SwiftAudioEx
class QueueManagerTests: QuickSpec {
+141 -1
View File
@@ -1,7 +1,7 @@
import Quick
import Nimble
@testable import SwiftAudio
@testable import SwiftAudioEx
class QueuedAudioPlayerTests: QuickSpec {
override func spec() {
@@ -166,6 +166,146 @@ class QueuedAudioPlayerTests: QuickSpec {
}
}
describe("its repeat mode") {
context("when adding 2 items") {
beforeEach {
try? audioPlayer.add(items: [ShortSource.getAudioItem(), ShortSource.getAudioItem()])
}
context("then setting repeat mode off") {
beforeEach {
audioPlayer.repeatMode = .off
}
context("allow playback to end") {
beforeEach {
audioPlayer.seek(to: 0.0682)
}
it("should move to next item") {
expect(audioPlayer.nextItems.count).toEventually(equal(0))
expect(audioPlayer.currentIndex).toEventually(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
context("allow playback to end again") {
beforeEach {
audioPlayer.seek(to: 0.0682)
}
it("should stop playback normally") {
expect(audioPlayer.nextItems.count).toEventually(equal(0))
expect(audioPlayer.currentIndex).toEventually(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.paused))
}
}
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
}
it("should move to next item") {
expect(audioPlayer.nextItems.count).to(equal(0))
expect(audioPlayer.currentIndex).to(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
context("then calling next() again") {
it("should fail") {
expect(try audioPlayer.next()).to(throwError())
}
}
}
}
context("then setting repeat mode track") {
beforeEach {
audioPlayer.repeatMode = .track
}
context("allow playback to end") {
beforeEach {
audioPlayer.seek(to: 0.0682)
}
it("should restart current item") {
expect(audioPlayer.currentTime).toEventually(equal(0))
expect(audioPlayer.nextItems.count).toEventually(equal(1))
expect(audioPlayer.currentIndex).toEventually(equal(0))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
}
it("should move to next item but should not play") {
expect(audioPlayer.nextItems.count).to(equal(0))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
}
}
}
context("then setting repeat mode queue") {
beforeEach {
audioPlayer.repeatMode = .queue
}
context("allow playback to end") {
beforeEach {
audioPlayer.seek(to: 0.0682)
}
it("should move to next item and should play") {
expect(audioPlayer.nextItems.count).toEventually(equal(0))
expect(audioPlayer.currentIndex).toEventually(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
context("allow playback to end again") {
beforeEach {
audioPlayer.seek(to: 0.0682)
}
it("should move to first track and should play") {
expect(audioPlayer.nextItems.count).toEventually(equal(1))
expect(audioPlayer.currentIndex).toEventually(equal(0))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
}
}
context("then calling next()") {
beforeEach {
try? audioPlayer.next()
}
it("should move to next item and should play") {
expect(audioPlayer.nextItems.count).to(equal(0))
expect(audioPlayer.currentIndex).to(equal(1))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
context("then calling next() again") {
beforeEach {
try? audioPlayer.next()
}
it("should move to first track and should play") {
expect(audioPlayer.nextItems.count).to(equal(1))
expect(audioPlayer.currentIndex).to(equal(0))
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
}
}
}
}
}
}
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
//
import Foundation
import SwiftAudio
import SwiftAudioEx
import UIKit
struct Source {
+7 -7
View File
@@ -2,18 +2,18 @@
import PackageDescription
let package = Package(
name: "SwiftAudio",
platforms: [.iOS(.v10)],
name: "SwiftAudioEx",
platforms: [.iOS(.v11)],
products: [
.library(
name: "SwiftAudio",
targets: ["SwiftAudio"]),
name: "SwiftAudioEx",
targets: ["SwiftAudioEx"]),
],
dependencies: [],
targets: [
.target(
name: "SwiftAudio",
name: "SwiftAudioEx",
dependencies: [],
path: "SwiftAudio/Classes")
path: "SwiftAudioEx/Classes")
]
)
)
+2 -2
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioEx'
s.version = '0.13.1'
s.version = '0.14.3'
s.summary = 'Easy audio streaming for iOS'
s.description = <<-DESC
SwiftAudioEx is an audio player written in Swift, making it simpler to work with audio playback from streams and files.
@@ -20,7 +20,7 @@ DESC
'Jørgen Henrichsen' => 'jh.henrichs@gmail.com', }
s.source = { :git => 'https://github.com/DoubleSymmetry/SwiftAudioEx.git', :tag => s.version.to_s }
s.ios.deployment_target = '10.0'
s.ios.deployment_target = '11.0'
s.swift_version = '5.0'
s.source_files = 'SwiftAudioEx/Classes/**/*'
end
+1
View File
@@ -22,6 +22,7 @@ public struct APError {
case noPreviousItem
case noNextItem
case invalidIndex(index: Int, message: String)
case noNextWhenRepeatModeTrack
}
}
+8 -2
View File
@@ -42,7 +42,13 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
/**
Default remote commands to use for each playing item
*/
public var remoteCommands: [RemoteCommand] = []
public var remoteCommands: [RemoteCommand] = [] {
didSet {
if let item = currentItem {
self.enableRemoteCommands(forItem: item)
}
}
}
// MARK: - Getters from AVPlayerWrapper
@@ -267,8 +273,8 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
- Playback rate
*/
public func updateNowPlayingPlaybackValues() {
updateNowPlayingDuration(duration)
updateNowPlayingCurrentTime(currentTime)
updateNowPlayingDuration(duration)
updateNowPlayingRate(rate)
}
@@ -21,10 +21,8 @@ protocol AudioSession {
var availableCategories: [AVAudioSession.Category] { get }
@available(iOS 10.0, *)
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws
@available(iOS 11.0, *)
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws
@@ -31,7 +31,6 @@ public enum NowPlayingInfoProperty: NowPlayingInfoKeyValue {
The URL pointing to the now playing item's underlying asset.
This constant is used by the system UI when video thumbnails or audio waveform visualizations are applicable.
*/
@available(iOS 10.3, *)
case assetUrl(URL?)
/**
@@ -116,7 +115,6 @@ public enum NowPlayingInfoProperty: NowPlayingInfoKeyValue {
The service provider associated with the now-playing item.
Value is a unique NSString that identifies the service provider for the now-playing item. If the now-playing item belongs to a channel or subscription service, this key can be used to coordinate various types of now-playing content from the service provider.
*/
@available(iOS 11.0, *)
case serviceIdentifier(String?)
@@ -130,11 +128,7 @@ public enum NowPlayingInfoProperty: NowPlayingInfoKeyValue {
return MPNowPlayingInfoPropertyAvailableLanguageOptions
case .assetUrl(_):
if #available(iOS 10.3, *) {
return MPNowPlayingInfoPropertyAssetURL
} else {
return ""
}
return MPNowPlayingInfoPropertyAssetURL
case .chapterCount(_):
return MPNowPlayingInfoPropertyChapterCount
@@ -175,11 +169,7 @@ public enum NowPlayingInfoProperty: NowPlayingInfoKeyValue {
return MPNowPlayingInfoPropertyPlaybackRate
case .serviceIdentifier(_):
if #available(iOS 11.0, *) {
return MPNowPlayingInfoPropertyServiceIdentifier
} else {
return ""
}
return MPNowPlayingInfoPropertyServiceIdentifier
}
}
@@ -194,10 +184,7 @@ public enum NowPlayingInfoProperty: NowPlayingInfoKeyValue {
return options
case .assetUrl(let url):
if #available(iOS 10.3, *) {
return url
}
return false
return url
case .chapterCount(let count):
return count != nil ? NSNumber(value: count!) : nil
+29 -15
View File
@@ -15,8 +15,8 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
let queueManager: QueueManager = QueueManager<AudioItem>()
public init() {
super.init()
public override init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(), remoteCommandController: RemoteCommandController = RemoteCommandController()) {
super.init(nowPlayingInfoController: nowPlayingInfoController, remoteCommandController: remoteCommandController)
queueManager.delegate = self
}
@@ -123,18 +123,29 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
- throws: `APError`
*/
public func next() throws {
event.playbackEnd.emit(data: .skippedToNext)
let nextItem = try queueManager.next()
try self.load(item: nextItem, playWhenReady: true)
do {
let nextItem = try queueManager.next()
event.playbackEnd.emit(data: .skippedToNext)
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
} catch APError.QueueError.noNextItem {
if repeatMode == .queue {
event.playbackEnd.emit(data: .skippedToNext)
try jumpToItem(atIndex: 0, playWhenReady: true)
} else {
throw APError.QueueError.noNextItem
}
} catch {
throw error
}
}
/**
Step to the previous item in the queue.
*/
public func previous() throws {
event.playbackEnd.emit(data: .skippedToPrevious)
let previousItem = try queueManager.previous()
try self.load(item: previousItem, playWhenReady: true)
event.playbackEnd.emit(data: .skippedToPrevious)
try self.load(item: previousItem, playWhenReady: repeatMode != .track)
}
/**
@@ -155,8 +166,8 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
- throws: `APError`
*/
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
event.playbackEnd.emit(data: .jumpedToIndex)
let item = try queueManager.jump(to: index)
event.playbackEnd.emit(data: .jumpedToIndex)
try self.load(item: item, playWhenReady: playWhenReady)
}
@@ -191,18 +202,21 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
super.AVWrapperItemDidPlayToEndTime()
switch repeatMode {
case .off: try? self.next()
case .off:
do {
let nextItem = try queueManager.next()
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
} catch { /* playback finished */ }
case .track:
seek(to: 0)
play()
case .queue:
do {
try self.next()
} catch APError.QueueError.noNextItem {
do {
try jumpToItem(atIndex: 0, playWhenReady: true)
} catch { /* TODO: handle possible errors from load */ }
} catch { /* TODO: handle possible errors from load */ }
let nextItem = try queueManager.next()
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
} catch {
try? jumpToItem(atIndex: 0, playWhenReady: true)
}
}
}
@@ -103,7 +103,7 @@ public struct FeedbackCommand: RemoteCommandProtocol {
}
}
public enum RemoteCommand {
public enum RemoteCommand: CustomStringConvertible {
case play
@@ -128,6 +128,23 @@ public enum RemoteCommand {
case dislike(isActive: Bool, localizedTitle: String, localizedShortTitle: String)
case bookmark(isActive: Bool, localizedTitle: String, localizedShortTitle: String)
public var description: String {
switch self {
case .play: return "play"
case .pause: return "pause"
case .stop: return "stop"
case .togglePlayPause: return "togglePlayPause"
case .next: return "nextTrack"
case .previous: return "previousTrack"
case .changePlaybackPosition: return "changePlaybackPosition"
case .skipForward(_): return "skipForward"
case .skipBackward(_): return "skipBackward"
case .like(_, _, _): return "like"
case .dislike(_, _, _): return "dislike"
case .bookmark(_, _, _): return "bookmark"
}
}
/**
All values in an array for convenience.
@@ -19,7 +19,8 @@ public class RemoteCommandController {
weak var audioPlayer: AudioPlayer?
var commandTargetPointers: [String: Any] = [:]
private var enabledCommands: [RemoteCommand] = []
/**
Create a new RemoteCommandController.
@@ -30,10 +31,18 @@ public class RemoteCommandController {
}
internal func enable(commands: [RemoteCommand]) {
self.disable(commands: RemoteCommand.all())
let commandsToDisable = enabledCommands.filter { command in
!commands.contains(where: { $0.description == command.description })
}
self.enabledCommands = commands
commands.forEach { (command) in
self.enable(command: command)
}
commandsToDisable.forEach { (command) in
self.disable(command: command)
}
}
internal func disable(commands: [RemoteCommand]) {
@@ -44,6 +53,7 @@ public class RemoteCommandController {
private func enableCommand<Command: RemoteCommandProtocol>(_ command: Command) {
center[keyPath: command.commandKeyPath].isEnabled = true
center[keyPath: command.commandKeyPath].removeTarget(commandTargetPointers[command.id])
commandTargetPointers[command.id] = center[keyPath: command.commandKeyPath].addTarget(handler: self[keyPath: command.handlerKeyPath])
}
@@ -213,7 +223,7 @@ public class RemoteCommandController {
}
else if let error = error as? APError.QueueError {
switch error {
case .noNextItem, .noPreviousItem, .invalidIndex(_, _):
case .noNextItem, .noPreviousItem, .invalidIndex(_, _), .noNextWhenRepeatModeTrack:
return MPRemoteCommandHandlerStatus.noSuchContent
}
}