Compare commits

...

2 Commits

Author SHA1 Message Date
tanhakabir c513c723ed Release 5.0.4 2021-05-08 18:57:11 -07:00
tanhakabir b34a264aec Fix bug introduced by queuing for lockscreen info (#108)
* add extra data for queuing and change order of clearing mediaInfo

* make SAPlayer the only source for mediaInfo

Co-authored-by: @dylancom
Co-authored-by: @dezinezync
2021-05-08 18:55:08 -07:00
5 changed files with 53 additions and 50 deletions
+1
View File
@@ -14,6 +14,7 @@ Thus, using [AudioToolbox](https://developer.apple.com/documentation/audiotoolbo
1. Realtime audio manipulation that includes going up to 10x speed, using [equalizers and other manipulations](https://developer.apple.com/documentation/avfaudio/avaudiouniteq)
1. Stream online audio using AVAudioEngine
1. Stream radio
1. Play locally saved audio with the same API
1. Download audio
1. Queue up downloaded and streamed audio for autoplay
+21 -22
View File
@@ -183,7 +183,7 @@ public class SAPlayer {
public var audioQueued: [URL] {
get {
return presenter.audioQueue.map { (queued) -> URL in
return queued.1
return queued.url
}
}
}
@@ -233,11 +233,7 @@ public class SAPlayer {
- Note: Setting this to nil clears the information displayed on the lockscreen media player.
*/
public var mediaInfo: SALockScreenInfo? = nil {
didSet {
presenter.handleLockscreenInfo(info: mediaInfo)
}
}
public var mediaInfo: SALockScreenInfo? = nil
private init() {
presenter = SAPlayerPresenter(delegate: self)
@@ -414,14 +410,14 @@ extension SAPlayer {
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
*/
public func startSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
// Because we support queueing, we want to clear off any existing players.
// Therefore, instantiate new player every time, destroy any existing ones.
// This prevents a crash where an owning engine already exists.
presenter.handleClear()
presenter.handlePlaySavedAudio(withSavedUrl: url)
}
@available(*, deprecated, renamed: "startSavedAudio")
public func initializeSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
presenter.handlePlaySavedAudio(withSavedUrl: url)
}
/**
@@ -453,14 +449,14 @@ extension SAPlayer {
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
*/
public func startRemoteAudio(withRemoteUrl url: URL, bitrate: SAPlayerBitrate = .high, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
// Because we support queueing, we want to clear off any existing players.
// Therefore, instantiate new player every time, destroy any existing ones.
// This prevents a crash where an owning engine already exists.
presenter.handleClear()
presenter.handlePlayStreamedAudio(withRemoteUrl: url, bitrate: bitrate)
}
@available(*, deprecated, renamed: "startRemoteAudio")
public func initializeRemoteAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
presenter.handlePlayStreamedAudio(withRemoteUrl: url, bitrate: .high)
}
/**
@@ -474,18 +470,21 @@ extension SAPlayer {
Queues remote audio to be played next. The URLs in the queue can be both remote or on disk but once the queued audio starts playing it will start buffering and loading then. This means no guarantee for a 'gapless' playback where there might be several moments in between one audio ending and another starting due to buffering remote audio.
- Parameter withRemoteUrl: The URL of the remote audio.
- Parameter bitrate: The bitrate of the streamed audio. By default the bitrate is set to high for streaming saved audio files. If you want to stream radios then you should use the `low` bitrate option.
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
*/
public func queueRemoteAudio(withRemoteUrl url: URL) {
presenter.handleQueueStreamedAudio(withRemoteUrl: url)
public func queueRemoteAudio(withRemoteUrl url: URL, bitrate: SAPlayerBitrate = .high, mediaInfo: SALockScreenInfo? = nil) {
presenter.handleQueueStreamedAudio(withRemoteUrl: url, mediaInfo: mediaInfo, bitrate: bitrate)
}
/**
Queues saved audio to be played next. The URLs in the queuecan be both remote or on disk but once the queued audio starts playing it will start buffering and loading then. This means no guarantee for a 'gapless' playback where there might be several moments in between one audio ending and another starting due to buffering remote audio.
- Parameter withSavedUrl: The URL of the audio saved on the device.
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
*/
public func queueSavedAudio(withSavedUrl url: URL) {
presenter.handleQueueSavedAudio(withSavedUrl: url)
public func queueSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
presenter.handleQueueSavedAudio(withSavedUrl: url, mediaInfo: mediaInfo)
}
/**
+1
View File
@@ -27,6 +27,7 @@ import Foundation
import CoreMedia
protocol SAPlayerDelegate: AnyObject, LockScreenViewProtocol {
var mediaInfo: SALockScreenInfo? { get set }
var skipForwardSeconds: Double { get set }
var skipBackwardSeconds: Double { get set }
+28 -26
View File
@@ -28,6 +28,20 @@ import AVFoundation
import MediaPlayer
class SAPlayerPresenter {
struct QueueItem {
var loc: Location
var url: URL
var mediaInfo: SALockScreenInfo?
var bitrate: SAPlayerBitrate
init(loc: Location, url: URL, mediaInfo: SALockScreenInfo?, bitrate: SAPlayerBitrate = .high) {
self.loc = loc
self.url = url
self.mediaInfo = mediaInfo
self.bitrate = bitrate
}
}
enum Location {
case remote
case disk
@@ -41,14 +55,13 @@ class SAPlayerPresenter {
private var key: String?
private var isPlaying: SAPlayingStatus = .buffering
private var mediaInfo: SALockScreenInfo?
private var urlKeyMap: [Key: URL] = [:]
var durationRef:UInt = 0
var needleRef:UInt = 0
var playingStatusRef:UInt = 0
var audioQueue: [(Location, URL)] = []
var audioQueue: [QueueItem] = []
init(delegate: SAPlayerDelegate?) {
self.delegate = delegate
@@ -70,7 +83,7 @@ class SAPlayerPresenter {
needle = nil
duration = nil
key = nil
mediaInfo = nil
delegate?.mediaInfo = nil
delegate?.clearLockScreenInfo()
AudioClockDirector.shared.detachFromChangesInDuration(withID: durationRef)
@@ -79,29 +92,21 @@ class SAPlayerPresenter {
}
func handlePlaySavedAudio(withSavedUrl url: URL) {
// Because we support queueing, we want to clear off any existing players.
// Therefore, instantiate new player every time, destroy any existing ones.
// This prevents a crash where an owning engine already exists.
handleClear()
attachForUpdates(url: url)
delegate?.startAudioDownloaded(withSavedUrl: url)
}
func handlePlayStreamedAudio(withRemoteUrl url: URL, bitrate: SAPlayerBitrate) {
// Because we support queueing, we want to clear off any existing players.
// Therefore, instantiate new player every time, destroy any existing ones.
// This prevents a crash where an owning engine already exists.
handleClear()
attachForUpdates(url: url)
delegate?.startAudioStreamed(withRemoteUrl: url, bitrate: bitrate)
}
func handleQueueStreamedAudio(withRemoteUrl url: URL) {
audioQueue.append((.remote, url))
func handleQueueStreamedAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo?, bitrate: SAPlayerBitrate) {
audioQueue.append(QueueItem(loc: .remote, url: url, mediaInfo: mediaInfo, bitrate: bitrate))
}
func handleQueueSavedAudio(withSavedUrl url: URL) {
audioQueue.append((.disk, url))
func handleQueueSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo?) {
audioQueue.append(QueueItem(loc: .disk, url: url, mediaInfo: mediaInfo))
}
private func attachForUpdates(url: URL) {
@@ -120,7 +125,7 @@ class SAPlayerPresenter {
self.delegate?.updateLockscreenPlaybackDuration(duration: duration)
self.duration = duration
self.delegate?.setLockScreenInfo(withMediaInfo: self.mediaInfo, duration: duration)
self.delegate?.setLockScreenInfo(withMediaInfo: self.delegate?.mediaInfo, duration: duration)
})
needleRef = AudioClockDirector.shared.attachToChangesInNeedle(closure: { [weak self] (key, needle) in
@@ -164,11 +169,6 @@ class SAPlayerPresenter {
delegate?.clearEngine()
detachFromUpdates()
}
@available(iOS 10.0, *)
func handleLockscreenInfo(info: SALockScreenInfo?) {
self.mediaInfo = info
}
}
//MARK:- Used by outside world including:
@@ -238,24 +238,26 @@ extension SAPlayerPresenter {
return
}
let nextAudioURL = audioQueue.removeFirst()
let key = nextAudioURL.1.key
let key = nextAudioURL.url.key
Log.info("getting ready to play \(nextAudioURL)")
AudioQueueDirector.shared.changeInQueue(key, url: nextAudioURL.1)
AudioQueueDirector.shared.changeInQueue(key, url: nextAudioURL.url)
handleClear()
delegate?.mediaInfo = nextAudioURL.mediaInfo
// We need to give a second to clean up the previous engine properly. Deinit takes some time.
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] (_) in
guard let self = self else { return }
switch nextAudioURL.0 {
switch nextAudioURL.loc {
case .remote:
self.handlePlayStreamedAudio(withRemoteUrl: nextAudioURL.1, bitrate: .high) // TODO fix to add option for low birate
self.handlePlayStreamedAudio(withRemoteUrl: nextAudioURL.url, bitrate: nextAudioURL.bitrate)
break
case .disk:
self.handlePlaySavedAudio(withSavedUrl: nextAudioURL.1)
self.handlePlaySavedAudio(withSavedUrl: nextAudioURL.url)
}
self.shouldPlayImmediately = true
+2 -2
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioPlayer'
s.version = '5.0.3'
s.version = '5.0.4'
s.summary = 'SwiftAudioPlayer is a Swift based audio player that can handle streaming from a remote location and audio manipulation.'
# This description is used to generate tags and improve search results.
@@ -26,7 +26,7 @@ SwiftAudioPlayer is a Swift based audio player that can handle streaming from a
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'tanhakabir' => 'tanhakabir.ca@gmail.com', 'JonMercer' => 'mercer.jon@gmail.com' }
s.source = { :git => 'https://github.com/tanhakabir/SwiftAudioPlayer.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/_tanhakabir'
s.social_media_url = 'https://twitter.com/_tanhakabir'
s.ios.deployment_target = '10.0'