diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 0e2cf37..f419cc4 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -11,7 +11,9 @@ 0773265B205ED6C400C4D1CD /* AVPlayerItemNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07732658205ED6C400C4D1CD /* AVPlayerItemNotificationObserver.swift */; }; 0773265C205ED6C400C4D1CD /* AVPlayerTimeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07732659205ED6C400C4D1CD /* AVPlayerTimeObserver.swift */; }; 07732660205ED7BF00C4D1CD /* AudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0773265F205ED7BF00C4D1CD /* AudioItem.swift */; }; + 0775574B2061C1820002C6A1 /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0775574A2061C1820002C6A1 /* RemoteCommand.swift */; }; 07F41B1A205FC0B100E25749 /* AudioSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F41B19205FC0B100E25749 /* AudioSessionController.swift */; }; + 07F41B2220614BDC00E25749 /* RemoteCommandController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F41B2120614BDC00E25749 /* RemoteCommandController.swift */; }; 0A2CA8B0DD7E300ABFCF1956E6B58248 /* Nimble.h in Headers */ = {isa = PBXBuildFile; fileRef = 2727DBDF3F41A52F002F6B1992165881 /* Nimble.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0AB1F4B2325838C42320440B1E6BAB1E /* QuickSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 38A82DE4A0130E4C946A904FEDDD74CA /* QuickSpec.m */; }; 12BBD20E3BEA5D34DCF748EF73904EAF /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEA1DF1C7C22E7B20D2DC30FB9D3EF1 /* Configuration.swift */; }; @@ -170,7 +172,9 @@ 07732658205ED6C400C4D1CD /* AVPlayerItemNotificationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AVPlayerItemNotificationObserver.swift; sourceTree = ""; }; 07732659205ED6C400C4D1CD /* AVPlayerTimeObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AVPlayerTimeObserver.swift; sourceTree = ""; }; 0773265F205ED7BF00C4D1CD /* AudioItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioItem.swift; path = SwiftAudio/Classes/AudioItem.swift; sourceTree = ""; }; + 0775574A2061C1820002C6A1 /* RemoteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RemoteCommand.swift; path = SwiftAudio/Classes/RemoteCommand.swift; sourceTree = ""; }; 07F41B19205FC0B100E25749 /* AudioSessionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AudioSessionController.swift; path = SwiftAudio/Classes/AudioSessionController.swift; sourceTree = ""; }; + 07F41B2120614BDC00E25749 /* RemoteCommandController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RemoteCommandController.swift; path = SwiftAudio/Classes/RemoteCommandController.swift; sourceTree = ""; }; 0A9D7EA20C39A55A1EF0C23094895A75 /* Quick-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Quick-dummy.m"; sourceTree = ""; }; 0AB0094EAAC3C18B000459B228F01637 /* ThrowAssertion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThrowAssertion.swift; path = Sources/Nimble/Matchers/ThrowAssertion.swift; sourceTree = ""; }; 0AC83F8F84F3F843896F4907ACFE3394 /* Pods-SwiftAudio_Tests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftAudio_Tests-resources.sh"; sourceTree = ""; }; @@ -390,6 +394,8 @@ EEC13E9D5F4FD604F703ACBF80ECA35E /* NowPlayingInfoProperty.swift */, 2B276281A286DB68E700474617B8AE3B /* TimeEventFrequency.swift */, 07F41B19205FC0B100E25749 /* AudioSessionController.swift */, + 07F41B2120614BDC00E25749 /* RemoteCommandController.swift */, + 0775574A2061C1820002C6A1 /* RemoteCommand.swift */, 07732656205ED6C400C4D1CD /* Observer */, 25961FA3FF989AFD1E08A55F4E464276 /* AVPlayerWrapper */, 853A8C0AF6F0B4902930084E06DEA640 /* Support Files */, @@ -980,7 +986,9 @@ 5E48B05B2B78E0AD05E2DBA8B1862AE5 /* MediaItemProperty.swift in Sources */, 0773265A205ED6C400C4D1CD /* AVPlayerObserver.swift in Sources */, FCE55A287889D68B475EBFBE8AB26E4B /* NowPlayingInfoController.swift in Sources */, + 0775574B2061C1820002C6A1 /* RemoteCommand.swift in Sources */, 07732660205ED7BF00C4D1CD /* AudioItem.swift in Sources */, + 07F41B2220614BDC00E25749 /* RemoteCommandController.swift in Sources */, 6E0C5034AB99CC7E6B0BF002D488D85D /* NowPlayingInfoProperty.swift in Sources */, A45D4CA6BC297DB896B20F391DF60D61 /* SwiftAudio-dummy.m in Sources */, 0773265B205ED6C400C4D1CD /* AVPlayerItemNotificationObserver.swift in Sources */, diff --git a/Example/SwiftAudio/Images.xcassets/22AMI.imageset/22AMillion.jpg b/Example/SwiftAudio/Images.xcassets/22AMI.imageset/22AMillion.jpg new file mode 100644 index 0000000..4f5c56f Binary files /dev/null and b/Example/SwiftAudio/Images.xcassets/22AMI.imageset/22AMillion.jpg differ diff --git a/Example/SwiftAudio/Images.xcassets/22AMI.imageset/Contents.json b/Example/SwiftAudio/Images.xcassets/22AMI.imageset/Contents.json new file mode 100644 index 0000000..4ef2565 --- /dev/null +++ b/Example/SwiftAudio/Images.xcassets/22AMI.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "22AMillion.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/SwiftAudio/ViewController.swift b/Example/SwiftAudio/ViewController.swift index 0449425..1ceef0d 100644 --- a/Example/SwiftAudio/ViewController.swift +++ b/Example/SwiftAudio/ViewController.swift @@ -21,13 +21,20 @@ class ViewController: UIViewController { var audioPlayer: AudioPlayer = AudioPlayer() let audioSessionController: AudioSessionController = AudioSessionController.shared let localSource = DefaultAudioItem(audioUrl: Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!, artist: "Artist", title: "Title", albumTitle: "Album", sourceType: .file, artwork: #imageLiteral(resourceName: "cover")) - let streamSource = DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/4839b070015ab7d6de9fec1756e1f3096d908fba", artist: "Artist", title: "Title", albumTitle: "Album", sourceType: .stream, artwork: #imageLiteral(resourceName: "cover")) + let streamSource = DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/081447adc23dad4f79ba4f1082615d1c56edf5e1?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "8 (circle)", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")) var artwork: MPMediaItemArtwork! override func viewDidLoad() { super.viewDidLoad() audioPlayer.delegate = self + audioPlayer.enableRemoteCommands([ + .stop, + .togglePlayPause, + .skipForward(preferredIntervals: [30]), + .skipBackward(preferredIntervals: [30]), + .changePlaybackPosition + ]) try? audioSessionController.set(category: .playback) try? audioSessionController.activateSession() let image = #imageLiteral(resourceName: "cover") @@ -37,15 +44,15 @@ class ViewController: UIViewController { } @IBAction func playA(_ sender: Any) { - audioPlayer.load(item: localSource) + try? audioPlayer.load(item: localSource) } @IBAction func playB(_ sender: Any) { - audioPlayer.load(item: streamSource) + try? audioPlayer.load(item: streamSource) } @IBAction func togglePlay(_ sender: Any) { - audioPlayer.togglePlaying() + try? audioPlayer.togglePlaying() } @IBAction func startScrubbing(_ sender: UISlider) { @@ -53,7 +60,7 @@ class ViewController: UIViewController { } @IBAction func scrubbing(_ sender: UISlider) { - audioPlayer.seek(to: Double(slider.value)) + try? audioPlayer.seek(to: Double(slider.value)) } func update() { diff --git a/SwiftAudio/Classes/AudioPlayer.swift b/SwiftAudio/Classes/AudioPlayer.swift index cf74e4c..5db2fa7 100644 --- a/SwiftAudio/Classes/AudioPlayer.swift +++ b/SwiftAudio/Classes/AudioPlayer.swift @@ -28,10 +28,10 @@ public class AudioPlayer { let wrapper: AVPlayerWrapper let nowPlayingInfoController: NowPlayingInfoController - let remoteCommandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared() public weak var delegate: AudioPlayerDelegate? public var currentItem: AudioItem? + public let remoteCommandController: RemoteCommandController /** Set this to false to disable automatic updating of now playing info for control center and lock screen. @@ -108,7 +108,7 @@ public class AudioPlayer { set { wrapper.volume = newValue } } - // MARK: - Public Methods + // MARK: - Init /** Create a new AudioManager. @@ -119,25 +119,27 @@ public class AudioPlayer { public init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) { self.wrapper = AVPlayerWrapper() self.nowPlayingInfoController = NowPlayingInfoController(infoCenter: infoCenter) + self.remoteCommandController = RemoteCommandController() self.wrapper.delegate = self - - connectToCommandCenter() + self.remoteCommandController.audioPlayer = self } + // MARK: - Player Actions + /** Load an AudioItem into the manager. - parameter item: The AudioItem to load. The info given in this item is the one used for the InfoCenter. - parameter playWhenReady: Immediately start playback when the item is ready. Default is `true`. If you disable this you have to call play() or togglePlay() when the `state` switches to `ready`. */ - public func load(item: AudioItem, playWhenReady: Bool = true) { + public func load(item: AudioItem, playWhenReady: Bool = true) throws { switch item.getSourceType() { case .stream: - try? self.wrapper.load(fromUrlString: item.getSourceUrl(), playWhenReady: playWhenReady) + try self.wrapper.load(fromUrlString: item.getSourceUrl(), playWhenReady: playWhenReady) case .file: - try? self.wrapper.load(fromFilePath: item.getSourceUrl(), playWhenReady: playWhenReady) + try self.wrapper.load(fromFilePath: item.getSourceUrl(), playWhenReady: playWhenReady) } self.currentItem = item @@ -148,22 +150,22 @@ public class AudioPlayer { /** Toggle playback status. */ - public func togglePlaying() { - try? self.wrapper.togglePlaying() + public func togglePlaying() throws { + try self.wrapper.togglePlaying() } /** Start playback */ - public func play() { - try? self.wrapper.play() + public func play() throws { + try self.wrapper.play() } /** Pause playback */ - public func pause() { - try? self.wrapper.pause() + public func pause() throws { + try self.wrapper.pause() } /** @@ -177,14 +179,18 @@ public class AudioPlayer { /** Seek to a specific time in the item. */ - public func seek(to seconds: TimeInterval) { - try? self.wrapper.seek(to: seconds) + public func seek(to seconds: TimeInterval) throws { + try self.wrapper.seek(to: seconds) } - // MARK: - Private + // MARK: - Remote Command Center - private func reset() { - self.currentItem = nil + /** + Set the remote commands that should be activated and handled. + Calling this will disable all earlier enabled commands, so include all commands you need. + */ + public func enableRemoteCommands(_ commands: [RemoteCommand]) { + self.remoteCommandController.enable(commands: commands) } // MARK: - NowPlayingInfo @@ -229,99 +235,26 @@ public class AudioPlayer { nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.playbackRate(Double(wrapper.rate))) } - // MARK: - Remote Commands Handlers + // MARK: - Private - func connectToCommandCenter() { - self.remoteCommandCenter.playCommand.addTarget(handler: handlePlayCommand(event:)) - self.remoteCommandCenter.pauseCommand.addTarget(handler: handlePauseCommand(event:)) - self.remoteCommandCenter.togglePlayPauseCommand.addTarget(handler: handleTogglePlaybackCommand(event:)) - self.remoteCommandCenter.stopCommand.addTarget(handler: handleStopCommand(event:)) - self.remoteCommandCenter.skipForwardCommand.addTarget(handler: handleSkipForwardCommand(event:)) - remoteCommandCenter.skipForwardCommand.preferredIntervals = [15] - self.remoteCommandCenter.skipBackwardCommand.addTarget(handler: handleSkipBackwardCommand(event:)) - remoteCommandCenter.skipBackwardCommand.preferredIntervals = [15] - - self.remoteCommandCenter.changePlaybackPositionCommand.addTarget(handler: handleChangePlaybackPositionCommand(event:)) + private func reset() { + self.currentItem = nil } - func getRemoteCommandHandlerStatus(forError error: Error) -> MPRemoteCommandHandlerStatus { - if let error = error as? APError.PlaybackError { - switch error { - case .noLoadedItem: - return MPRemoteCommandHandlerStatus.noActionableNowPlayingItem - } - } - return MPRemoteCommandHandlerStatus.commandFailed - } - func handlePlayCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - do { - try self.wrapper.play() - return MPRemoteCommandHandlerStatus.success - } - catch let error { - return getRemoteCommandHandlerStatus(forError: error) - } - } +// func connectToCommandCenter() { +// self.remoteCommandCenter.playCommand.addTarget(handler: handlePlayCommand(event:)) +// self.remoteCommandCenter.pauseCommand.addTarget(handler: handlePauseCommand(event:)) +// self.remoteCommandCenter.togglePlayPauseCommand.addTarget(handler: handleTogglePlaybackCommand(event:)) +// self.remoteCommandCenter.stopCommand.addTarget(handler: handleStopCommand(event:)) +// self.remoteCommandCenter.skipForwardCommand.addTarget(handler: handleSkipForwardCommand(event:)) +// remoteCommandCenter.skipForwardCommand.preferredIntervals = [15] +// self.remoteCommandCenter.skipBackwardCommand.addTarget(handler: handleSkipBackwardCommand(event:)) +// remoteCommandCenter.skipBackwardCommand.preferredIntervals = [15] +// +// self.remoteCommandCenter.changePlaybackPositionCommand.addTarget(handler: handleChangePlaybackPositionCommand(event:)) +// } - func handlePauseCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - do { - try self.wrapper.pause() - return MPRemoteCommandHandlerStatus.success - } - catch let error { - return getRemoteCommandHandlerStatus(forError: error) - } - } - - func handleTogglePlaybackCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - do { - try self.wrapper.togglePlaying() - return MPRemoteCommandHandlerStatus.success - } - catch let error { - return getRemoteCommandHandlerStatus(forError: error) - } - } - - func handleStopCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - self.wrapper.stop() - return MPRemoteCommandHandlerStatus.success - } - - func handleSkipForwardCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - if let command = event.command as? MPSkipIntervalCommand { - if let interval = command.preferredIntervals.first { - self.seek(to: currentTime + interval.doubleValue) - return MPRemoteCommandHandlerStatus.success - } - } - - return MPRemoteCommandHandlerStatus.commandFailed - } - - func handleSkipBackwardCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - if let command = event.command as? MPSkipIntervalCommand { - if let interval = command.preferredIntervals.first { - self.seek(to: currentTime - interval.doubleValue) - return MPRemoteCommandHandlerStatus.success - } - } - return MPRemoteCommandHandlerStatus.commandFailed - } - - func handleChangePlaybackPositionCommand(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - if let event = event as? MPChangePlaybackPositionCommandEvent { - do { - try wrapper.seek(to: event.positionTime) - return MPRemoteCommandHandlerStatus.success - } - catch let error { - return getRemoteCommandHandlerStatus(forError: error) - } - } - return MPRemoteCommandHandlerStatus.commandFailed - } } extension AudioPlayer: AVPlayerWrapperDelegate { diff --git a/SwiftAudio/Classes/RemoteCommand.swift b/SwiftAudio/Classes/RemoteCommand.swift new file mode 100644 index 0000000..cc758d3 --- /dev/null +++ b/SwiftAudio/Classes/RemoteCommand.swift @@ -0,0 +1,103 @@ +// +// RemoteCommand.swift +// SwiftAudio +// +// Created by Jørgen Henrichsen on 20/03/2018. +// + +import Foundation +import MediaPlayer + + +public typealias RemoteCommandHandler = (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus + +public protocol RemoteCommandProtocol { + associatedtype Command: MPRemoteCommand + + var commandKeyPath: KeyPath { get } + var handlerKeyPath: KeyPath { get } +} + +public struct BaseRemoteCommand: RemoteCommandProtocol { + + public static let play = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.playCommand, handlerKeyPath: \RemoteCommandController.handlePlayCommand) + + public static let pause = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.pauseCommand, handlerKeyPath: \RemoteCommandController.handlePauseCommand) + + public static let stop = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.stopCommand, handlerKeyPath: \RemoteCommandController.handleStopCommand) + + public static let togglePlayPause = BaseRemoteCommand(commandKeyPath: \MPRemoteCommandCenter.togglePlayPauseCommand, handlerKeyPath: \RemoteCommandController.handleTogglePlayPauseCommand) + + + public typealias Command = MPRemoteCommand + + public var commandKeyPath: KeyPath + + public var handlerKeyPath: KeyPath + +} + +public struct ChangePlaybackPositionCommand: RemoteCommandProtocol { + + public static let changePlaybackPosition = ChangePlaybackPositionCommand(commandKeyPath: \MPRemoteCommandCenter.changePlaybackPositionCommand, handlerKeyPath: \RemoteCommandController.handleChangePlaybackPositionCommand) + + public typealias Command = MPChangePlaybackPositionCommand + + public var commandKeyPath: KeyPath + + public var handlerKeyPath: KeyPath + +} + +public struct SkipIntervalCommand: RemoteCommandProtocol { + + public static let skipForward = SkipIntervalCommand(commandKeyPath: \MPRemoteCommandCenter.skipForwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipForwardCommand) + + public static let skipBackward = SkipIntervalCommand(commandKeyPath: \MPRemoteCommandCenter.skipBackwardCommand, handlerKeyPath: \RemoteCommandController.handleSkipBackwardCommand) + + public typealias Command = MPSkipIntervalCommand + + public var commandKeyPath: KeyPath + + public var handlerKeyPath: KeyPath + + func set(preferredIntervals: [NSNumber]) -> SkipIntervalCommand { + MPRemoteCommandCenter.shared()[keyPath: commandKeyPath].preferredIntervals = preferredIntervals + return self + } + +} + +public enum RemoteCommand { + + case play + + case pause + + case stop + + case togglePlayPause + + case changePlaybackPosition + + case skipForward(preferredIntervals: [NSNumber]) + + case skipBackward(preferredIntervals: [NSNumber]) + + /** + All values in an array for convenience. + Don't use for associated values. + */ + static func all() -> [RemoteCommand] { + return [ + .play, + .pause, + .stop, + .togglePlayPause, + .changePlaybackPosition, + .skipForward(preferredIntervals: []), + .skipBackward(preferredIntervals: []), + ] + } + +} diff --git a/SwiftAudio/Classes/RemoteCommandController.swift b/SwiftAudio/Classes/RemoteCommandController.swift new file mode 100644 index 0000000..04b9f62 --- /dev/null +++ b/SwiftAudio/Classes/RemoteCommandController.swift @@ -0,0 +1,159 @@ +// +// File.swift +// SwiftAudio +// +// Created by Jørgen Henrichsen on 20/03/2018. +// + +import Foundation +import MediaPlayer + + +public class RemoteCommandController { + + private let center = MPRemoteCommandCenter.shared() + + weak var audioPlayer: AudioPlayer? + + init() {} + + /** + Enable a set of RemoteCommands. Calling this will disable all earlier set commands, so include all commands that needs to be active. + + - parameter commands: The RemoteCommands that is to be enabled. + */ + public func enable(commands: [RemoteCommand]) { + self.disable(commands: RemoteCommand.all()) + commands.forEach { (command) in + self.enable(command: command) + } + } + + private func disable(commands: [RemoteCommand]) { + commands.forEach { (command) in + self.disable(command: command) + } + } + + private func enableCommand(_ command: Command) { + center[keyPath: command.commandKeyPath].addTarget(handler: self[keyPath: command.handlerKeyPath]) + } + + private func disableCommand(_ command: Command) { + center[keyPath: command.commandKeyPath].removeTarget(self) + } + + 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 .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)) + + } + } + + 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 .changePlaybackPosition: self.disableCommand(ChangePlaybackPositionCommand.changePlaybackPosition) + case .skipForward(_): self.disableCommand(SkipIntervalCommand.skipForward) + case .skipBackward(_): self.disableCommand(SkipIntervalCommand.skipBackward) + } + } + + // MARK: - Handlers + + lazy var handlePlayCommand: RemoteCommandHandler = { (event) in + do { + try self.audioPlayer?.play() + return MPRemoteCommandHandlerStatus.success + } + catch let error { + return self.getRemoteCommandHandlerStatus(forError: error) + } + + } + + lazy var handlePauseCommand: RemoteCommandHandler = { (event) in + do { + try self.audioPlayer?.pause() + return MPRemoteCommandHandlerStatus.success + } + catch let error { + return self.getRemoteCommandHandlerStatus(forError: error) + } + } + + lazy var handleStopCommand: RemoteCommandHandler = { (event) in + self.audioPlayer?.stop() + return .success + } + + lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { (event) in + do { + try self.audioPlayer?.togglePlaying() + return MPRemoteCommandHandlerStatus.success + } + catch let error { + return self.getRemoteCommandHandlerStatus(forError: error) + } + } + + 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 + } + + return MPRemoteCommandHandlerStatus.commandFailed + } + + lazy var handleSkipBackwardCommand: 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 + } + + return MPRemoteCommandHandlerStatus.commandFailed + } + + lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { (event) in + if let event = event as? MPChangePlaybackPositionCommandEvent { + do { + try self.audioPlayer?.seek(to: event.positionTime) + return MPRemoteCommandHandlerStatus.success + } + catch let error { + return self.getRemoteCommandHandlerStatus(forError: error) + } + } + return MPRemoteCommandHandlerStatus.commandFailed + } + + private func getRemoteCommandHandlerStatus(forError error: Error) -> MPRemoteCommandHandlerStatus { + if let error = error as? APError.PlaybackError { + switch error { + case .noLoadedItem: + return MPRemoteCommandHandlerStatus.noActionableNowPlayingItem + } + } + return MPRemoteCommandHandlerStatus.commandFailed + } + + +}