Merge branch 'main' of https://github.com/dimitris-c/AudioStreaming into main
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user