This commit is contained in:
Dimitris C
2021-01-16 11:40:01 +02:00
4 changed files with 78 additions and 34 deletions
@@ -12,6 +12,7 @@ enum AudioContent: Int, CaseIterable {
case offradio
case enlefko
case pepper966
case kosmos
case radiox
case khruangbin
case piano
@@ -26,6 +27,8 @@ enum AudioContent: Int, CaseIterable {
return "Enlefko (stream)"
case .pepper966:
return "Pepper 96.6 (stream)"
case .kosmos:
return "Kosmos 93.6 (stream)"
case .radiox:
return "Radio X (stream)"
case .khruangbin:
@@ -47,6 +50,8 @@ enum AudioContent: Int, CaseIterable {
return URL(string: "https://s3.yesstreaming.net:17062/stream")!
case .pepper966:
return URL(string: "https://ample-09.radiojar.com/pepper.m4a?1593699983=&rj-tok=AAABcw_1KyMAIViq2XpI098ZSQ&rj-ttl=5")!
case .kosmos:
return URL(string: "https://radiostreaming.ert.gr/ert-kosmos")!
case .radiox:
return URL(string: "https://media-ssl.musicradio.com/RadioXLondon")!
case .khruangbin:
@@ -131,7 +131,7 @@ public final class AudioPlayer {
entriesQueue = PlayerQueueEntries()
serializationQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated)
sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated, target: serializationQueue)
sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated)
audioReadSource = DispatchTimerSource(interval: .milliseconds(200), queue: sourceQueue)
entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(),
@@ -152,7 +152,6 @@ public final class AudioPlayer {
}
deinit {
// todo more stuff to release...
playerContext.audioPlayingEntry?.close()
clearQueue()
stopReadProccessFromSource()
@@ -175,18 +174,21 @@ public final class AudioPlayer {
public func play(url: URL, headers: [String: String]) {
let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers)
audioEntry.delegate = self
clearQueue()
entriesQueue.enqueue(item: audioEntry, type: .upcoming)
playerContext.setInternalState(to: .pendingNext)
checkRenderWaitingAndNotifyIfNeeded()
sourceQueue.async { [weak self] in
guard let self = self else { return }
serializationQueue.sync {
clearQueue()
entriesQueue.enqueue(item: audioEntry, type: .upcoming)
playerContext.setInternalState(to: .pendingNext)
do {
try self.startEngineIfNeeded()
} catch {
self.raiseUnxpected(error: .audioSystemError(.engineFailure))
}
}
sourceQueue.async { [weak self] in
guard let self = self else { return }
self.processSource()
self.startReadProcessFromSourceIfNeeded()
}
@@ -194,19 +196,46 @@ public final class AudioPlayer {
/// Queues the specified URL
///
/// - Parameter url: A `URL` specifying the audio context to be played.
/// - Parameter url: A `URL` specifying the audio content to be played.
public func queue(url: URL) {
queue(url: url, headers: [:])
}
/// Queues the specified URLs
///
/// - Parameter url: A `URL` specifying the audio content to be played.
public func queue(urls: [URL]) {
queue(urls: urls, headers: [:])
}
/// Queues the specified URL
///
/// - Parameter url: A `URL` specifying the audio context to be played.
/// - Parameter url: A `URL` specifying the audio content to be played.
/// - parameter headers: A `Dictionary` specifying any additional headers to be pass to the network request.
public func queue(url: URL, headers: [String: String]) {
let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers)
audioEntry.delegate = self
entriesQueue.enqueue(item: audioEntry, type: .upcoming)
serializationQueue.sync {
let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers)
audioEntry.delegate = self
entriesQueue.enqueue(item: audioEntry, type: .upcoming)
}
checkRenderWaitingAndNotifyIfNeeded()
sourceQueue.async { [weak self] in
self?.processSource()
}
}
/// Queues the specified URLs
///
/// - Parameter url: A array of `URL`s specifying the audio content to be played.
/// - parameter headers: A `Dictionary` specifying any additional headers to be pass to the network request.
public func queue(urls: [URL], headers: [String: String]) {
serializationQueue.sync {
for url in urls {
let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers)
audioEntry.delegate = self
entriesQueue.enqueue(item: audioEntry, type: .upcoming)
}
}
checkRenderWaitingAndNotifyIfNeeded()
sourceQueue.async { [weak self] in
self?.processSource()
@@ -218,7 +247,10 @@ public final class AudioPlayer {
guard playerContext.internalState != .stopped else { return }
stopReadProccessFromSource()
stopEngine(reason: .userAction)
serializationQueue.sync {
stopEngine(reason: .userAction)
}
checkRenderWaitingAndNotifyIfNeeded()
sourceQueue.async { [weak self] in
guard let self = self else { return }
self.playerContext.audioReadingEntry?.delegate = nil
@@ -235,7 +267,6 @@ public final class AudioPlayer {
self.processSource()
}
checkRenderWaitingAndNotifyIfNeeded()
}
/// Pauses the audio playback
@@ -243,8 +274,9 @@ public final class AudioPlayer {
if playerContext.internalState != .paused, playerContext.internalState.contains(.running) {
stateBeforePaused = playerContext.internalState
playerContext.setInternalState(to: .paused)
pauseEngine()
serializationQueue.sync {
pauseEngine()
}
stopReadProccessFromSource()
playerContext.audioPlayingEntry?.suspend()
sourceQueue.async { [weak self] in
@@ -257,20 +289,20 @@ public final class AudioPlayer {
public func resume() {
guard playerContext.internalState == .paused else { return }
playerContext.setInternalState(to: stateBeforePaused)
// check if seek time requested and reset buffers
do {
try startEngine()
} catch {
Logger.debug("resuming audio engine failed: %@", category: .generic, args: error.localizedDescription)
}
if let playingEntry = playerContext.audioReadingEntry {
if playingEntry.seekRequest.requested {
rendererContext.resetBuffers()
serializationQueue.sync {
do {
try startEngine()
} catch {
Logger.debug("resuming audio engine failed: %@", category: .generic, args: error.localizedDescription)
}
playingEntry.resume()
if let playingEntry = playerContext.audioReadingEntry {
if playingEntry.seekRequest.requested {
rendererContext.resetBuffers()
}
playingEntry.resume()
}
startPlayer(resetBuffers: false)
}
startPlayer(resetBuffers: false)
startReadProcessFromSourceIfNeeded()
}
@@ -546,7 +578,7 @@ public final class AudioPlayer {
let entry = entriesQueue.dequeue(type: .upcoming)
let shouldStartPlaying = playerContext.audioPlayingEntry == nil
playerContext.setInternalState(to: .waitingForData)
setCurrentReading(entry: entry, startPlaying: shouldStartPlaying, shouldClearQueue: true)
setCurrentReading(entry: entry, startPlaying: shouldStartPlaying, shouldClearQueue: false)
} else if playerContext.audioPlayingEntry == nil {
if playerContext.internalState != .stopped {
stopReadProccessFromSource()
@@ -578,7 +610,8 @@ public final class AudioPlayer {
}
private func proccessSeekTime() {
assert(playerContext.audioReadingEntry === playerContext.audioPlayingEntry, "reading and playing entry must be the same")
assert(playerContext.audioReadingEntry === playerContext.audioPlayingEntry,
"reading and playing entry must be the same")
fileStreamProcessor.processSeek()
}
@@ -450,7 +450,7 @@ final class AudioFileStreamProcessor {
fillUsedFrames(framesCount: framesAdded)
return
} else if status != 0 {
/// raise undexpected error... codec error
fileStreamCallback?(.raiseError(.codecError))
return
}
@@ -479,7 +479,7 @@ final class AudioFileStreamProcessor {
fillUsedFrames(framesCount: framesAdded)
continue packetProccess
} else if status != 0 {
/// raise undexpected error... codec error
fileStreamCallback?(.raiseError(.codecError))
return
}
} else {
@@ -506,7 +506,7 @@ final class AudioFileStreamProcessor {
fillUsedFrames(framesCount: framesAdded)
continue packetProccess
} else if status != 0 {
/// raise undexpected error... codec error
fileStreamCallback?(.raiseError(.codecError))
return
}
}
+7 -1
View File
@@ -35,8 +35,14 @@ player.play(url: URL(fileURLWithPath: "your-local-path/to/audio-file.mp3")!)
### Queueing audio files
```
let player = AudioPlayer()
// when you want to queue a single url
player.queue(url: URL(string: "https://your-remote-url/to/audio-file.mp3")!)
player.queue(url: URL(fileURLWithPath: "your-local-path/to/audio-file.mp3")!)
// or if you want to queue a list of urls use
player.queue(urls: [
URL(fileURLWithPath: "your-local-path/to/audio-file.mp3")!,
URL(fileURLWithPath: "your-local-path/to/audio-file-2.mp3")!
])
```
### Adjusting playback properties