Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 879a2816be | |||
| 2c3ebefd54 | |||
| 862dd47509 | |||
| dee22d5193 | |||
| 9dd479e377 | |||
| ddf26e206e | |||
| e319134eb8 | |||
| 9599f66a0f | |||
| ef231a2570 | |||
| 350e6ec064 |
@@ -0,0 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## 7.3.0
|
||||
|
||||
- Take in PR from @cntrump to use the non-deprecated subscription pattern in loop feature
|
||||
|
||||
+2
@@ -135,6 +135,7 @@
|
||||
A470FE1B25F9AEB900F135FF /* AudioQueueDirector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioQueueDirector.swift; sourceTree = "<group>"; };
|
||||
A470FE2025F9AF1400F135FF /* AudioQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioQueue.swift; sourceTree = "<group>"; };
|
||||
A4827770262A216C00B6918A /* StreamingDownloadDirector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingDownloadDirector.swift; sourceTree = "<group>"; };
|
||||
A4883AC926CC25DE0073B8B6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
|
||||
A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerDownloader.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B3221B74C900D5A353 /* SAPlayerHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerHelpers.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerUpdateSubscription.swift; sourceTree = "<group>"; };
|
||||
@@ -255,6 +256,7 @@
|
||||
children = (
|
||||
15DF3E7F1B5E10B1BBE49D3E9A67C938 /* LICENSE */,
|
||||
B8C829A46249957CD3056074B5CC0BBB /* README.md */,
|
||||
A4883AC926CC25DE0073B8B6 /* CHANGELOG.md */,
|
||||
6EC04ECC8F7CB2AF2E4E042A6A8ECFA1 /* SwiftAudioPlayer.podspec */,
|
||||
A4523BC8220A0B3C0079C4BC /* Credited_LICENSE */,
|
||||
);
|
||||
|
||||
@@ -171,7 +171,7 @@ You can also directly access and modify the queue from `SAPlayer.shared.audioQue
|
||||
|
||||
The engine can handle audio manipulations like speed, pitch, effects, etc. To do this, nodes for effects must be finalized before initialize is called. Look at [audio manipulation documentation](#realtime-audio-manipulation) for more information.
|
||||
|
||||
### Lockscreen Media Player
|
||||
### LockScreen Media Player
|
||||
|
||||
Update and set what displays on the lockscreen's media player when the player is active.
|
||||
|
||||
|
||||
@@ -27,20 +27,35 @@ import Foundation
|
||||
import MediaPlayer
|
||||
import UIKit
|
||||
|
||||
public protocol LockScreenViewPresenter : AnyObject {
|
||||
func getIsPlaying() -> Bool
|
||||
func handlePlay()
|
||||
func handlePause()
|
||||
func handleSkipBackward()
|
||||
func handleSkipForward()
|
||||
func handleSeek(toNeedle needle: Double)
|
||||
}
|
||||
|
||||
// MARK: - Set up lockscreen audio controls
|
||||
// Documentation: https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos/controlling_background_audio
|
||||
protocol LockScreenViewProtocol {
|
||||
public protocol LockScreenViewProtocol {
|
||||
var skipForwardSeconds: Double { get set }
|
||||
var skipBackwardSeconds: Double { get set }
|
||||
}
|
||||
|
||||
extension LockScreenViewProtocol {
|
||||
public extension LockScreenViewProtocol {
|
||||
func clearLockScreenInfo() {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.playCommand.removeTarget(nil)
|
||||
commandCenter.pauseCommand.removeTarget(nil)
|
||||
commandCenter.skipBackwardCommand.removeTarget(nil)
|
||||
commandCenter.skipForwardCommand.removeTarget(nil)
|
||||
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
|
||||
}
|
||||
|
||||
@available(iOS 10.0, tvOS 10.0, *)
|
||||
func setLockScreenInfo(withMediaInfo info: SALockScreenInfo?, duration: Duration) {
|
||||
func setLockScreenInfo(withMediaInfo info: SALockScreenInfo?, duration: Double) {
|
||||
var nowPlayingInfo:[String : Any] = [:]
|
||||
|
||||
guard let info = info else {
|
||||
@@ -82,7 +97,7 @@ extension LockScreenViewProtocol {
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/36754934/update-mpremotecommandcenter-play-pause-button
|
||||
func setLockScreenControls(presenter: SAPlayerPresenter) { //FIXME: this is weird
|
||||
func setLockScreenControls(presenter: LockScreenViewPresenter) {
|
||||
// Get the shared MPRemoteCommandCenter
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
|
||||
@@ -146,29 +161,29 @@ extension LockScreenViewProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLockscreenElapsedTime(needle: Needle) {
|
||||
func updateLockScreenElapsedTime(needle: Double) {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: Double(needle))
|
||||
}
|
||||
|
||||
func updateLockscreenPlaybackDuration(duration: Duration) {
|
||||
func updateLockScreenPlaybackDuration(duration: Double) {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = NSNumber(value: duration)
|
||||
}
|
||||
|
||||
func updateLockscreenPaused(){
|
||||
func updateLockScreenPaused(){
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0.0
|
||||
}
|
||||
|
||||
func updateLockscreenPlaying(){
|
||||
func updateLockScreenPlaying(){
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
|
||||
}
|
||||
|
||||
func updateLockscreenChangePlaybackRate(speed: Float){
|
||||
func updateLockScreenChangePlaybackRate(speed: Float){
|
||||
if speed > 0.0{
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = speed
|
||||
}
|
||||
}
|
||||
|
||||
func updateLockscreenSkipIntervals() {
|
||||
func updateLockScreenSkipIntervals() {
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.skipBackwardCommand.isEnabled = skipBackwardSeconds > 0
|
||||
commandCenter.skipBackwardCommand.preferredIntervals = [skipBackwardSeconds] as [NSNumber]
|
||||
|
||||
@@ -64,9 +64,14 @@ struct FileStorage {
|
||||
// MARK:- Audio
|
||||
extension FileStorage {
|
||||
struct Audio {
|
||||
private static let directory: FileManager.SearchPathDirectory = AudioDataManager.shared.downloadDirectory
|
||||
private init() {}
|
||||
|
||||
private static var directory: FileManager.SearchPathDirectory {
|
||||
get {
|
||||
return AudioDataManager.shared.downloadDirectory
|
||||
}
|
||||
}
|
||||
|
||||
static func isStored(_ id: ID) -> Bool {
|
||||
guard let url = locate(id)?.path else {
|
||||
return false
|
||||
|
||||
@@ -182,7 +182,14 @@ public class SAPlayer {
|
||||
/**
|
||||
List of queued audio for playback. You can edit this list as you wish to modify the queue.
|
||||
*/
|
||||
public var audioQueued: [SAAudioQueueItem] = []
|
||||
public var audioQueued: [SAAudioQueueItem] {
|
||||
get {
|
||||
return presenter.audioQueue
|
||||
}
|
||||
set {
|
||||
presenter.audioQueue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Total duration of current audio initialized. Returns nil if no audio is initialized in player.
|
||||
|
||||
@@ -147,7 +147,7 @@ extension SAPlayer {
|
||||
|
||||
guard playingStatusId == nil else { return }
|
||||
|
||||
playingStatusId = SAPlayer.Updates.PlayingStatus.subscribe({ (url, status) in
|
||||
playingStatusId = SAPlayer.Updates.PlayingStatus.subscribe({ (status) in
|
||||
if status == .ended && enabled {
|
||||
SAPlayer.shared.seekTo(seconds: 0.0)
|
||||
SAPlayer.shared.play()
|
||||
|
||||
@@ -46,13 +46,11 @@ class SAPlayerPresenter {
|
||||
|
||||
init(delegate: SAPlayerDelegate?) {
|
||||
self.delegate = delegate
|
||||
|
||||
delegate?.setLockScreenControls(presenter: self)
|
||||
|
||||
|
||||
durationRef = AudioClockDirector.shared.attachToChangesInDuration(closure: { [weak self] (duration) in
|
||||
guard let self = self else { throw DirectorError.closureIsDead }
|
||||
|
||||
self.delegate?.updateLockscreenPlaybackDuration(duration: duration)
|
||||
self.delegate?.updateLockScreenPlaybackDuration(duration: duration)
|
||||
self.duration = duration
|
||||
|
||||
self.delegate?.setLockScreenInfo(withMediaInfo: self.delegate?.mediaInfo, duration: duration)
|
||||
@@ -62,7 +60,7 @@ class SAPlayerPresenter {
|
||||
guard let self = self else { throw DirectorError.closureIsDead }
|
||||
|
||||
self.needle = needle
|
||||
self.delegate?.updateLockscreenElapsedTime(needle: needle)
|
||||
self.delegate?.updateLockScreenElapsedTime(needle: needle)
|
||||
})
|
||||
|
||||
playingStatusRef = AudioClockDirector.shared.attachToChangesInPlayingStatus(closure: { [weak self] (isPlaying) in
|
||||
@@ -106,11 +104,13 @@ class SAPlayerPresenter {
|
||||
|
||||
func handlePlaySavedAudio(withSavedUrl url: URL) {
|
||||
resetCacheForNewAudio(url: url)
|
||||
delegate?.setLockScreenControls(presenter: self)
|
||||
delegate?.startAudioDownloaded(withSavedUrl: url)
|
||||
}
|
||||
|
||||
func handlePlayStreamedAudio(withRemoteUrl url: URL, bitrate: SAPlayerBitrate) {
|
||||
resetCacheForNewAudio(url: url)
|
||||
delegate?.setLockScreenControls(presenter: self)
|
||||
delegate?.startAudioStreamed(withRemoteUrl: url, bitrate: bitrate)
|
||||
}
|
||||
|
||||
@@ -156,16 +156,7 @@ class SAPlayerPresenter {
|
||||
//MARK:- Used by outside world including:
|
||||
// SPP, lock screen, directors
|
||||
extension SAPlayerPresenter {
|
||||
func handlePause() {
|
||||
delegate?.pauseEngine()
|
||||
self.delegate?.updateLockscreenPaused()
|
||||
}
|
||||
|
||||
func handlePlay() {
|
||||
delegate?.playEngine()
|
||||
self.delegate?.updateLockscreenPlaying()
|
||||
}
|
||||
|
||||
|
||||
func handleTogglePlayingAndPausing() {
|
||||
if isPlaying == .playing {
|
||||
handlePause()
|
||||
@@ -173,35 +164,46 @@ extension SAPlayerPresenter {
|
||||
handlePlay()
|
||||
}
|
||||
}
|
||||
|
||||
func handleSkipForward() {
|
||||
guard let forward = delegate?.skipForwardSeconds else { return }
|
||||
handleSeek(toNeedle: (needle ?? 0) + forward)
|
||||
|
||||
func handleAudioRateChanged(rate: Float) {
|
||||
delegate?.updateLockScreenChangePlaybackRate(speed: rate)
|
||||
}
|
||||
|
||||
func handleScrubbingIntervalsChanged() {
|
||||
delegate?.updateLockScreenSkipIntervals()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK:- For lock screen
|
||||
extension SAPlayerPresenter : LockScreenViewPresenter {
|
||||
|
||||
func getIsPlaying() -> Bool {
|
||||
return isPlaying == .playing
|
||||
}
|
||||
|
||||
func handlePlay() {
|
||||
delegate?.playEngine()
|
||||
self.delegate?.updateLockScreenPlaying()
|
||||
}
|
||||
|
||||
func handlePause() {
|
||||
delegate?.pauseEngine()
|
||||
self.delegate?.updateLockScreenPaused()
|
||||
}
|
||||
|
||||
func handleSkipBackward() {
|
||||
guard let backward = delegate?.skipForwardSeconds else { return }
|
||||
handleSeek(toNeedle: (needle ?? 0) - backward)
|
||||
}
|
||||
|
||||
func handleSkipForward() {
|
||||
guard let forward = delegate?.skipForwardSeconds else { return }
|
||||
handleSeek(toNeedle: (needle ?? 0) + forward)
|
||||
}
|
||||
|
||||
func handleSeek(toNeedle needle: Needle) {
|
||||
delegate?.seekEngine(toNeedle: needle)
|
||||
}
|
||||
|
||||
func handleAudioRateChanged(rate: Float) {
|
||||
delegate?.updateLockscreenChangePlaybackRate(speed: rate)
|
||||
}
|
||||
|
||||
func handleScrubbingIntervalsChanged() {
|
||||
delegate?.updateLockscreenSkipIntervals()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK:- For lock screen
|
||||
extension SAPlayerPresenter {
|
||||
func getIsPlaying() -> Bool {
|
||||
return isPlaying == .playing
|
||||
}
|
||||
}
|
||||
|
||||
//MARK:- AVAudioEngineDelegate
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '7.2.0'
|
||||
s.version = '7.4.0'
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user