Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 843ba9f450 | |||
| 4305110867 | |||
| cd43ecc6f9 | |||
| 274377af3f | |||
| 43824a9700 | |||
| 9e6683674b | |||
| b27332aafb | |||
| 664c56b79c | |||
| 7bb83e87e0 |
@@ -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";
|
||||
|
||||
@@ -17,10 +17,10 @@ class AudioController {
|
||||
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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudio'
|
||||
s.version = '0.3.5'
|
||||
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
+16
-11
@@ -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)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
@@ -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
+29
-4
@@ -29,6 +29,14 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user