Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 843ba9f450 | |||
| 4305110867 | |||
| cd43ecc6f9 | |||
| 274377af3f | |||
| 43824a9700 | |||
| 9e6683674b | |||
| b27332aafb | |||
| 664c56b79c | |||
| 7bb83e87e0 | |||
| 6cad96b4f8 | |||
| 50a58c2306 | |||
| 2dbaf3d4dc | |||
| 851d8704d9 | |||
| 7c4dc27868 |
@@ -283,7 +283,7 @@
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
DevelopmentTeam = HPNZWPB9JK;
|
||||
DevelopmentTeam = B82TF96752;
|
||||
LastSwiftMigration = 0900;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
@@ -591,7 +591,7 @@
|
||||
baseConfigurationReference = C344B34C66182CD1C5AD6DD2 /* Pods-SwiftAudio_Example.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = HPNZWPB9JK;
|
||||
DEVELOPMENT_TEAM = B82TF96752;
|
||||
INFOPLIST_FILE = SwiftAudio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -608,7 +608,7 @@
|
||||
baseConfigurationReference = CAD65BC49F7B60C24AB20FDA /* Pods-SwiftAudio_Example.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = HPNZWPB9JK;
|
||||
DEVELOPMENT_TEAM = B82TF96752;
|
||||
INFOPLIST_FILE = SwiftAudio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
||||
@@ -13,19 +13,23 @@ import SwiftAudio
|
||||
class AudioController {
|
||||
|
||||
static let shared = AudioController()
|
||||
let player = QueuedAudioPlayer()
|
||||
let player: QueuedAudioPlayer
|
||||
let audioSessionController = AudioSessionController.shared
|
||||
|
||||
let sources: [AudioItem] = [
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/67b51d90ffddd6bb3f095059997021b589845f81?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "33 \"GOD\"", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
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")),
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/6f9999d909b017eabef97234dd7a206355720d9d?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "715 - CRΣΣKS", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/bf9bdd403c67fdbe06a582e7b292487c8cfd1f7e?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "____45_____", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI"))
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/67b51d90ffddd6bb3f095059997021b589845f81?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "33 \"GOD\"", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/081447adc23dad4f79ba4f1082615d1c56edf5e1?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "8 (circle)", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/6f9999d909b017eabef97234dd7a206355720d9d?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "715 - CRΣΣKS", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/bf9bdd403c67fdbe06a582e7b292487c8cfd1f7e?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "____45_____", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI"))
|
||||
]
|
||||
|
||||
init() {
|
||||
let controller = RemoteCommandController()
|
||||
player = QueuedAudioPlayer(remoteCommandController: controller)
|
||||
player.remoteCommands = [
|
||||
.stop,
|
||||
.play,
|
||||
.pause,
|
||||
.togglePlayPause,
|
||||
.next,
|
||||
.previous,
|
||||
|
||||
@@ -61,6 +61,10 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
extension ViewController: AudioPlayerDelegate {
|
||||
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AVPlayerWrapperState) {
|
||||
playButton.setTitle(state == .playing ? "Pause" : "Play", for: .normal)
|
||||
@@ -89,10 +93,6 @@ extension ViewController: AudioPlayerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayerItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double) {
|
||||
if !isScrubbing {
|
||||
slider.setValue(Float(seconds), animated: false)
|
||||
|
||||
@@ -189,20 +189,18 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when seeking to a time", {
|
||||
var passed = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
holder.seekCompletion = { passed = true }
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
|
||||
try? wrapper.seek(to: seekTime)
|
||||
}
|
||||
|
||||
it("should eventually be equal to the seeked time", closure: {
|
||||
expect(wrapper.currentTime).toEventually(equal(seekTime))
|
||||
expect(passed).toEventually(beTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -214,12 +212,12 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
}
|
||||
|
||||
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
|
||||
func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason) {
|
||||
|
||||
}
|
||||
|
||||
var state: AVPlayerWrapperState? {
|
||||
didSet {
|
||||
print(state)
|
||||
if let state = state {
|
||||
self.stateUpdate?(state)
|
||||
}
|
||||
@@ -233,10 +231,6 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
func AVWrapperItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
|
||||
}
|
||||
@@ -245,8 +239,9 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
}
|
||||
|
||||
var seekCompletion: (() -> Void)?
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
|
||||
|
||||
seekCompletion?()
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
|
||||
@@ -105,20 +105,18 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when seeking to a time", {
|
||||
var passed = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
holder.seekCompletion = { passed = true }
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
|
||||
try? audioPlayer.seek(to: seekTime)
|
||||
}
|
||||
|
||||
it("should eventually be equal to the seeked time", closure: {
|
||||
expect(audioPlayer.currentTime).toEventually(equal(seekTime))
|
||||
expect(passed).toEventually(beTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -144,6 +142,10 @@ class AudioPlayerTests: QuickSpec {
|
||||
}
|
||||
|
||||
class AudioPlayerDelegateHolder: AudioPlayerDelegate {
|
||||
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
|
||||
var state: AudioPlayerState? {
|
||||
@@ -158,10 +160,6 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
func audioPlayerItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double) {
|
||||
|
||||
}
|
||||
@@ -170,8 +168,9 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
|
||||
|
||||
}
|
||||
|
||||
var seekCompletion: (() -> Void)?
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
|
||||
|
||||
seekCompletion?()
|
||||
}
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double) {
|
||||
|
||||
@@ -122,7 +122,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing the second item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: 1)
|
||||
removed = try? manager.removeItem(at: 1)
|
||||
}
|
||||
|
||||
it("should have one less item", closure: {
|
||||
@@ -134,7 +134,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing the last item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: self.dummyItems.count - 1)
|
||||
removed = try? manager.removeItem(at: self.dummyItems.count - 1)
|
||||
}
|
||||
|
||||
it("should have one less item", closure: {
|
||||
@@ -146,7 +146,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing the current item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: manager.currentIndex)
|
||||
removed = try? manager.removeItem(at: manager.currentIndex)
|
||||
}
|
||||
it("should not remove any items", closure: {
|
||||
expect(removed).to(beNil())
|
||||
@@ -157,7 +157,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing with too large index", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: self.dummyItems.count)
|
||||
removed = try? manager.removeItem(at: self.dummyItems.count)
|
||||
}
|
||||
|
||||
it("should not remove any items", closure: {
|
||||
@@ -169,7 +169,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing with too small index", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: -1)
|
||||
removed = try? manager.removeItem(at: -1)
|
||||
}
|
||||
|
||||
it("should not remove any items", closure: {
|
||||
|
||||
@@ -70,7 +70,7 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
|
||||
context("then removing one item", {
|
||||
beforeEach {
|
||||
try? audioPlayer.removeItem(atIndex: 1)
|
||||
try? audioPlayer.removeItem(at: 1)
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
|
||||
@@ -13,7 +13,7 @@ struct Source {
|
||||
static let path: String = Bundle.main.path(forResource: "TestSound", ofType: "m4a")!
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file)
|
||||
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file, pitchAlgorithmType: .lowQualityZeroLatency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ struct ShortSource {
|
||||
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file)
|
||||
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file, pitchAlgorithmType: .lowQualityZeroLatency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'SwiftAudio'
|
||||
pod 'SwiftAudio', '~> 0.3.5'
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -103,6 +103,16 @@ These commands will be activated for each `AudioItem`. If you need some audio it
|
||||
|
||||
**Remember** to go to App Settings -> Capabilites -> Background Modes -> Check 'Remote notifications'
|
||||
|
||||
#### Custom handlers for remote commands
|
||||
To supply custom handlers for your remote commands, just override the handlers contained in the player's `RemoteCommandController`:
|
||||
```swift
|
||||
let player = QueuedAudioPlayer()
|
||||
player.remoteCommandController.handlePlayCommand = { (event) in
|
||||
// Handle remote command here.
|
||||
}
|
||||
```
|
||||
All available overrides can be found by looking at `RemoteCommandController`.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudio'
|
||||
s.version = '0.3.4'
|
||||
s.version = '0.3.6'
|
||||
s.summary = 'Easy audio streaming for iOS'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
Regular → Executable
+17
-8
@@ -11,10 +11,18 @@ import AVFoundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
public enum PlaybackEndedReason: String {
|
||||
case playedUntilEnd
|
||||
case playerStopped
|
||||
case skippedToNext
|
||||
case skippedToPrevious
|
||||
case jumpedToIndex
|
||||
}
|
||||
|
||||
protocol AVPlayerWrapperDelegate: class {
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState)
|
||||
func AVWrapperItemDidComplete()
|
||||
func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason)
|
||||
func AVWrapper(secondsElapsed seconds: Double)
|
||||
func AVWrapper(failedWithError error: Error?)
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
|
||||
@@ -42,7 +50,7 @@ class AVPlayerWrapper {
|
||||
*/
|
||||
var playWhenReady: Bool { return _playWhenReady }
|
||||
|
||||
private var _playWhenReady: Bool = true
|
||||
fileprivate var _playWhenReady: Bool = true
|
||||
|
||||
/**
|
||||
The current `AudioPlayerState` of the player.
|
||||
@@ -98,9 +106,11 @@ class AVPlayerWrapper {
|
||||
|
||||
/**
|
||||
The rate of the AVPlayer
|
||||
Default is 1.0
|
||||
*/
|
||||
var rate: Float {
|
||||
return avPlayer.rate
|
||||
get { return avPlayer.rate }
|
||||
set { avPlayer.rate = newValue }
|
||||
}
|
||||
|
||||
// MARK: - AVPlayer Config Properties
|
||||
@@ -226,9 +236,8 @@ class AVPlayerWrapper {
|
||||
guard currentItem != nil else {
|
||||
throw APError.PlaybackError.noLoadedItem
|
||||
}
|
||||
let millis = Int64(max(min(seconds, duration), 0) * 1000)
|
||||
let time = CMTime(value: millis, timescale: 1000)
|
||||
avPlayer.seek(to: time) { (finished) in
|
||||
|
||||
avPlayer.seek(to: CMTimeMakeWithSeconds(seconds, 1)) { (finished) in
|
||||
self.delegate?.AVWrapper(seekTo: Int(seconds), didFinish: finished)
|
||||
}
|
||||
}
|
||||
@@ -240,7 +249,6 @@ class AVPlayerWrapper {
|
||||
- parameter playWhenReady: Whether playback should start immediately when the item is ready. Default is `true`
|
||||
*/
|
||||
func load(fromUrlString urlString: String, playWhenReady: Bool = true) throws {
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
throw APError.LoadError.invalidSourceUrl(urlString)
|
||||
}
|
||||
@@ -287,6 +295,7 @@ class AVPlayerWrapper {
|
||||
if !soft {
|
||||
avPlayer.replaceCurrentItem(with: nil)
|
||||
}
|
||||
|
||||
playerTimeObserver.unregisterForBoundaryTimeEvents()
|
||||
playerItemNotificationObserver.stopObservingCurrentItem()
|
||||
}
|
||||
@@ -353,7 +362,7 @@ extension AVPlayerWrapper: AVPlayerItemNotificationObserverDelegate {
|
||||
// MARK: - AVPlayerItemNotificationObserverDelegate
|
||||
|
||||
func itemDidPlayToEndTime() {
|
||||
delegate?.AVWrapperItemDidComplete()
|
||||
delegate?.AVWrapper(itemPlaybackDoneWithReason: .playedUntilEnd)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+10
-2
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public enum SourceType {
|
||||
case stream
|
||||
@@ -19,6 +20,7 @@ public protocol AudioItem {
|
||||
func getTitle() -> String?
|
||||
func getAlbumTitle() -> String?
|
||||
func getSourceType() -> SourceType
|
||||
func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm
|
||||
func getArtwork(_ handler: @escaping (UIImage?) -> Void)
|
||||
|
||||
}
|
||||
@@ -36,14 +38,17 @@ public struct DefaultAudioItem: AudioItem {
|
||||
|
||||
public var sourceType: SourceType
|
||||
|
||||
public var pitchAlgorithmType: AVAudioTimePitchAlgorithm
|
||||
|
||||
public var artwork: UIImage?
|
||||
|
||||
public init(audioUrl: String, artist: String? = nil, title: String? = nil, albumTitle: String? = nil, sourceType: SourceType, artwork: UIImage? = nil) {
|
||||
public init(audioUrl: String, artist: String? = nil, title: String? = nil, albumTitle: String? = nil, sourceType: SourceType, pitchAlgorithmType: AVAudioTimePitchAlgorithm, artwork: UIImage? = nil) {
|
||||
self.audioUrl = audioUrl
|
||||
self.artist = artist
|
||||
self.title = title
|
||||
self.albumTitle = albumTitle
|
||||
self.sourceType = sourceType
|
||||
self.pitchAlgorithmType = pitchAlgorithmType
|
||||
self.artwork = artwork
|
||||
}
|
||||
|
||||
@@ -67,9 +72,12 @@ public struct DefaultAudioItem: AudioItem {
|
||||
return sourceType
|
||||
}
|
||||
|
||||
public func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm {
|
||||
return pitchAlgorithmType
|
||||
}
|
||||
|
||||
public func getArtwork(_ handler: @escaping (UIImage?) -> Void) {
|
||||
handler(artwork)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+19
-14
@@ -14,7 +14,7 @@ public protocol AudioPlayerDelegate: class {
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AudioPlayerState)
|
||||
|
||||
func audioPlayerItemDidComplete()
|
||||
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason)
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double)
|
||||
|
||||
@@ -23,7 +23,7 @@ public protocol AudioPlayerDelegate: class {
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool)
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
let wrapper: AVPlayerWrapper
|
||||
let nowPlayingInfoController: NowPlayingInfoController
|
||||
let remoteCommandController: RemoteCommandController
|
||||
public let remoteCommandController: RemoteCommandController
|
||||
|
||||
var _currentItem: AudioItem?
|
||||
|
||||
@@ -69,13 +69,6 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
return wrapper.duration
|
||||
}
|
||||
|
||||
/**
|
||||
The current rate of the underlying `AudioPlayer`.
|
||||
*/
|
||||
public var rate: Float {
|
||||
return wrapper.rate
|
||||
}
|
||||
|
||||
/**
|
||||
The current state of the underlying `AudioPlayer`.
|
||||
*/
|
||||
@@ -123,6 +116,15 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
set { wrapper.volume = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The player rate
|
||||
Default is 1.0
|
||||
*/
|
||||
public var rate: Float {
|
||||
get { return wrapper.rate }
|
||||
set { wrapper.rate = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
/**
|
||||
@@ -130,10 +132,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
- parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`.
|
||||
*/
|
||||
init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default(), remoteCommandController: RemoteCommandController? = nil) {
|
||||
self.wrapper = AVPlayerWrapper()
|
||||
self.nowPlayingInfoController = NowPlayingInfoController(infoCenter: infoCenter)
|
||||
self.remoteCommandController = RemoteCommandController()
|
||||
self.remoteCommandController = remoteCommandController ?? RemoteCommandController()
|
||||
|
||||
self.wrapper.delegate = self
|
||||
self.remoteCommandController.audioPlayer = self
|
||||
@@ -156,6 +158,8 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
try self.wrapper.load(fromFilePath: item.getSourceUrl(), playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
wrapper.currentItem?.audioTimePitchAlgorithm = item.getPitchAlgorithmType()
|
||||
|
||||
self._currentItem = item
|
||||
set(item: item)
|
||||
setArtwork(forItem: item)
|
||||
@@ -187,6 +191,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
Stop playback, resetting the player.
|
||||
*/
|
||||
public func stop() {
|
||||
AVWrapper(itemPlaybackDoneWithReason: .playerStopped)
|
||||
self.reset()
|
||||
self.wrapper.stop()
|
||||
}
|
||||
@@ -280,8 +285,8 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
self.delegate?.audioPlayer(playerDidChangeState: state)
|
||||
}
|
||||
|
||||
func AVWrapperItemDidComplete() {
|
||||
self.delegate?.audioPlayerItemDidComplete()
|
||||
func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason) {
|
||||
self.delegate?.audioPlayer(itemPlaybackEndedWithReason: reason)
|
||||
}
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
|
||||
Regular → Executable
+47
-9
@@ -20,10 +20,10 @@ class QueueManager<T> {
|
||||
}
|
||||
|
||||
public var nextItems: [T] {
|
||||
guard _currentIndex < _items.count else {
|
||||
guard _currentIndex + 1 < _items.count else {
|
||||
return []
|
||||
}
|
||||
return Array(_items[_currentIndex + 1..<items.count])
|
||||
return Array(_items[_currentIndex + 1..<_items.count])
|
||||
}
|
||||
|
||||
public var previousItems: [T] {
|
||||
@@ -71,6 +71,21 @@ class QueueManager<T> {
|
||||
_items.append(contentsOf: items)
|
||||
}
|
||||
|
||||
/**
|
||||
Add an array of items to the queue at a given index.
|
||||
|
||||
- parameter items: The `AudioItem`s to be added.
|
||||
- parameter at: The index to insert the items at.
|
||||
*/
|
||||
public func addItems(_ items: [T], at index: Int) throws {
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for addition has to be positive and smaller than the count of current items (\(_items.count))")
|
||||
}
|
||||
|
||||
_items.insert(contentsOf: items, at: index)
|
||||
if (_currentIndex >= index) { _currentIndex = _currentIndex + _items.count }
|
||||
}
|
||||
|
||||
/**
|
||||
Get the next item in the queue, if there are any.
|
||||
Will update the current item.
|
||||
@@ -119,9 +134,10 @@ class QueueManager<T> {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot jump to the current item")
|
||||
}
|
||||
|
||||
guard index >= 0 && items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(items.count))")
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(_items.count))")
|
||||
}
|
||||
|
||||
_currentIndex = index
|
||||
return _items[index]
|
||||
}
|
||||
@@ -140,14 +156,15 @@ class QueueManager<T> {
|
||||
}
|
||||
|
||||
guard fromIndex >= 0 && fromIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
guard toIndex >= 0 && toIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
_items.insert(_items.remove(at: fromIndex), at: toIndex)
|
||||
let item = try removeItem(at: fromIndex)
|
||||
try addItems([item], at: toIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,16 +175,37 @@ class QueueManager<T> {
|
||||
- returns: The removed item.
|
||||
*/
|
||||
@discardableResult
|
||||
public func remove(atIndex index: Int) throws -> T {
|
||||
public func removeItem(at index: Int) throws -> T {
|
||||
guard index != _currentIndex else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot remove the current item!")
|
||||
}
|
||||
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be postivie and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
if index < _currentIndex {
|
||||
_currentIndex = _currentIndex - 1
|
||||
}
|
||||
|
||||
return _items.remove(at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove upcoming items.
|
||||
*/
|
||||
public func removeUpcomingItems() {
|
||||
let nextIndex = _currentIndex + 1
|
||||
guard nextIndex < _items.count else { return }
|
||||
_items.removeSubrange(nextIndex..<_items.count)
|
||||
}
|
||||
|
||||
/**
|
||||
Removes all items for queue
|
||||
*/
|
||||
public func clearQueue() {
|
||||
_currentIndex = 0
|
||||
_items.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+31
-6
@@ -21,14 +21,22 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
*/
|
||||
public var automaticallyPlayNextSong: Bool = true
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default(), remoteCommandController: RemoteCommandController? = nil) {
|
||||
super.init(infoCenter: infoCenter, remoteCommandController: remoteCommandController)
|
||||
}
|
||||
|
||||
public override var currentItem: AudioItem? {
|
||||
return queueManager.current
|
||||
}
|
||||
|
||||
/**
|
||||
Stops the player and clears the queue.
|
||||
*/
|
||||
public override func stop() {
|
||||
super.stop()
|
||||
queueManager.clearQueue()
|
||||
}
|
||||
|
||||
/**
|
||||
The previous items held by the queue.
|
||||
*/
|
||||
@@ -77,12 +85,17 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
public func add(items: [AudioItem], at index: Int) throws {
|
||||
try queueManager.addItems(items, at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
Step to the next item in the queue.
|
||||
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func next() throws {
|
||||
AVWrapper(itemPlaybackDoneWithReason: .skippedToNext)
|
||||
let nextItem = try queueManager.next()
|
||||
try self.loadItem(nextItem, playWhenReady: true)
|
||||
}
|
||||
@@ -91,6 +104,7 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
Step to the previous item in the queue.
|
||||
*/
|
||||
public func previous() throws {
|
||||
AVWrapper(itemPlaybackDoneWithReason: .skippedToPrevious)
|
||||
let previousItem = try queueManager.previous()
|
||||
try self.loadItem(previousItem, playWhenReady: true)
|
||||
}
|
||||
@@ -101,8 +115,8 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
- parameter index: The index of the item to remove.
|
||||
- throws: `APError.QueueError`
|
||||
*/
|
||||
public func removeItem(atIndex index: Int) throws {
|
||||
try queueManager.remove(atIndex: index)
|
||||
public func removeItem(at index: Int) throws {
|
||||
try queueManager.removeItem(at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,6 +127,8 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
|
||||
AVWrapper(itemPlaybackDoneWithReason: .jumpedToIndex)
|
||||
|
||||
let item = try queueManager.jump(to: index)
|
||||
try self.loadItem(item, playWhenReady: playWhenReady)
|
||||
}
|
||||
@@ -128,10 +144,19 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
try queueManager.moveItem(fromIndex: fromIndex, toIndex: toIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all upcoming items, those returned by `next()`
|
||||
*/
|
||||
public func removeUpcomingItems() {
|
||||
queueManager.removeUpcomingItems()
|
||||
}
|
||||
|
||||
// MARK: - AVPlayerWrapperDelegate
|
||||
|
||||
override func AVWrapperItemDidComplete() {
|
||||
super.AVWrapperItemDidComplete()
|
||||
override func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason) {
|
||||
super.AVWrapper(itemPlaybackDoneWithReason: reason)
|
||||
guard reason == .playedUntilEnd else { return }
|
||||
|
||||
if automaticallyPlayNextSong {
|
||||
try? self.next()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class RemoteCommandController {
|
||||
|
||||
var commandTargetPointers: [String: Any] = [:]
|
||||
|
||||
init() {}
|
||||
public init() {}
|
||||
|
||||
/**
|
||||
Enable a set of RemoteCommands. Calling this will disable all earlier set commands, so include all commands that needs to be active.
|
||||
@@ -62,7 +62,6 @@ public class RemoteCommandController {
|
||||
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))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +81,17 @@ public class RemoteCommandController {
|
||||
|
||||
// MARK: - Handlers
|
||||
|
||||
lazy var handlePlayCommand: RemoteCommandHandler = { (event) in
|
||||
public lazy var handlePlayCommand: RemoteCommandHandler = self.handlePlayCommandDefault
|
||||
public lazy var handlePauseCommand: RemoteCommandHandler = self.handlePauseCommandDefault
|
||||
public lazy var handleStopCommand: RemoteCommandHandler = self.handleStopCommandDefault
|
||||
public lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = self.handleTogglePlayPauseCommandDefault
|
||||
public lazy var handleSkipForwardCommand: RemoteCommandHandler = self.handleSkipForwardCommandDefault
|
||||
public lazy var handleSkipBackwardCommand: RemoteCommandHandler = self.handleSkipBackwardDefault
|
||||
public lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = self.handleChangePlaybackPositionCommandDefault
|
||||
public lazy var handleNextTrackCommand: RemoteCommandHandler = self.handleNextTrackCommandDefault
|
||||
public lazy var handlePreviousTrackCommand: RemoteCommandHandler = self.handlePreviousTrackCommandDefault
|
||||
|
||||
private func handlePlayCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.play()
|
||||
@@ -95,7 +104,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePauseCommand: RemoteCommandHandler = { (event) in
|
||||
private func handlePauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.pause()
|
||||
@@ -108,7 +117,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleStopCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleStopCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
audioPlayer.stop()
|
||||
return .success
|
||||
@@ -116,7 +125,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleTogglePlayPauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.togglePlaying()
|
||||
@@ -129,7 +138,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleSkipForwardCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleSkipForwardCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
@@ -144,7 +153,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleSkipBackwardCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleSkipBackwardDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
@@ -159,7 +168,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleChangePlaybackPositionCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let event = event as? MPChangePlaybackPositionCommandEvent,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
@@ -173,7 +182,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleNextTrackCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleNextTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.next()
|
||||
@@ -186,7 +195,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePreviousTrackCommand: RemoteCommandHandler = { (event) in
|
||||
private func handlePreviousTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.previous()
|
||||
|
||||
@@ -13,8 +13,8 @@ import MediaPlayer
|
||||
*/
|
||||
public class SimpleAudioPlayer: AudioPlayer {
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default(), remoteCommandController: RemoteCommandController? = nil) {
|
||||
super.init(infoCenter: infoCenter, remoteCommandController: remoteCommandController)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user