Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c4a7310f | |||
| 9d2d2594a1 | |||
| 4e790876cb | |||
| b19d01bdfc | |||
| 3c8ecb353c | |||
| cafd513468 | |||
| 7b8a4f318d |
@@ -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",
|
||||
|
||||
@@ -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
@@ -3,7 +3,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SwiftAudio",
|
||||
platforms: [.iOS(.v10)],
|
||||
platforms: [.iOS(.v11)],
|
||||
products: [
|
||||
.library(
|
||||
name: "SwiftAudioEx",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioEx'
|
||||
s.version = '0.13.2'
|
||||
s.version = '0.14.2'
|
||||
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
|
||||
|
||||
@@ -22,6 +22,7 @@ public struct APError {
|
||||
case noPreviousItem
|
||||
case noNextItem
|
||||
case invalidIndex(index: Int, message: String)
|
||||
case noNextWhenRepeatModeTrack
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -234,6 +234,14 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
self.enableRemoteCommands(remoteCommands)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Syncs the current remoteCommands with the iOS command center.
|
||||
Can be used to update item states - e.g. like, dislike and bookmark.
|
||||
*/
|
||||
public func syncRemoteCommandsWithCommandCenter() {
|
||||
self.enableRemoteCommands(remoteCommands)
|
||||
}
|
||||
|
||||
// MARK: - NowPlayingInfo
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -124,8 +124,19 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
*/
|
||||
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()
|
||||
try self.load(item: nextItem, playWhenReady: repeatMode != .track)
|
||||
} catch APError.QueueError.noNextItem {
|
||||
if repeatMode == .queue {
|
||||
try jumpToItem(atIndex: 0, playWhenReady: true)
|
||||
} else {
|
||||
throw APError.QueueError.noNextItem
|
||||
}
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +145,7 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
public func previous() throws {
|
||||
event.playbackEnd.emit(data: .skippedToPrevious)
|
||||
let previousItem = try queueManager.previous()
|
||||
try self.load(item: previousItem, playWhenReady: true)
|
||||
try self.load(item: previousItem, playWhenReady: repeatMode != .track)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,11 +209,9 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
||||
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 */ }
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user