Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b93581515 | |||
| 31b9e89dba | |||
| a83c2f702f | |||
| 8644bf24fb | |||
| 69a979cb98 | |||
| 6ba43e70ea | |||
| 6f19009000 | |||
| 64677ad6ce | |||
| 3894309706 | |||
| e44f16258f |
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftAudioPlayer
|
||||
|
||||
struct AudioInfo: Hashable {
|
||||
var index: Int = 0
|
||||
@@ -44,6 +45,12 @@ struct AudioInfo: Hashable {
|
||||
let artist: String = "SwiftAudioPlayer Sample App"
|
||||
let releaseDate: Int = 1550790640
|
||||
|
||||
var lockscreenInfo: SALockScreenInfo {
|
||||
get {
|
||||
return SALockScreenInfo(title: self.title, artist: self.artist, artwork: nil, releaseDate: self.releaseDate)
|
||||
}
|
||||
}
|
||||
|
||||
var savedUrl: URL? {
|
||||
get {
|
||||
return savedUrls[index]
|
||||
|
||||
@@ -295,7 +295,7 @@ class ViewController: UIViewController {
|
||||
self.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
|
||||
self.selectedAudio.addSavedUrl(url)
|
||||
|
||||
SAPlayer.shared.startSavedAudio(withSavedUrl: url)
|
||||
SAPlayer.shared.startSavedAudio(withSavedUrl: url, mediaInfo: self.selectedAudio.lockscreenInfo)
|
||||
self.lastPlayedAudioIndex = self.selectedAudio.index
|
||||
}
|
||||
})
|
||||
@@ -312,9 +312,9 @@ class ViewController: UIViewController {
|
||||
@IBAction func streamTouched(_ sender: Any) {
|
||||
if !isStreaming {
|
||||
if selectedAudio.index == 2 { // radio
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, bitrate: .low)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, bitrate: .low, mediaInfo: selectedAudio.lockscreenInfo)
|
||||
} else {
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, mediaInfo: selectedAudio.lockscreenInfo)
|
||||
}
|
||||
|
||||
lastPlayedAudioIndex = selectedAudio.index
|
||||
|
||||
@@ -88,7 +88,7 @@ override func viewDidLoad() {
|
||||
}
|
||||
}
|
||||
```
|
||||
Look at the [Updates](#SAPlayer.Updates) section to see usage details and other updates to follow.
|
||||
Look at the [Updates](#saplayerupdates) section to see usage details and other updates to follow.
|
||||
|
||||
|
||||
For realtime audio manipulations, [AVAudioUnit](https://developer.apple.com/documentation/avfoundation/avaudiounit) nodes are used. For example to adjust the reverb through a slider in the UI:
|
||||
@@ -113,6 +113,7 @@ For a more detailed explanation on usage, look at the [Realtime Audio Manipulati
|
||||
|
||||
For more details and specifics look at the [API documentation](#api-in-detail) below.
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
### Issues
|
||||
|
||||
+23
-26
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,12 +522,10 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
private func becomeDeviceAudioPlayer() {
|
||||
do {
|
||||
if #available(iOS 11.0, *) {
|
||||
// try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, policy: .longForm, options: [])
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, policy: .longFormAudio, options: [])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode(rawValue: convertFromAVAudioSessionMode(AVAudioSession.Mode.default)), options: .allowAirPlay)
|
||||
}
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode(rawValue: convertFromAVAudioSessionMode(AVAudioSession.Mode.default)), options: .allowAirPlay)
|
||||
|
||||
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
|
||||
} catch {
|
||||
Log.monitor("Problem setting up AVAudioSession to play in:: \(error.localizedDescription)")
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '5.0.2'
|
||||
s.version = '5.0.3'
|
||||
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/<TWITTER_USERNAME>'
|
||||
s.social_media_url = 'https://twitter.com/_tanhakabir'
|
||||
|
||||
s.ios.deployment_target = '10.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user