Compare commits

..

3 Commits

Author SHA1 Message Date
dimitris-c 8f0e894f86 fix a glitch sound on pause and play 2024-03-10 19:22:47 +02:00
Dimitris C decb12641d fix incorrect stopReason on finish delegate method (#66) 2024-02-29 14:49:51 +02:00
Dimitris C 4e485f924a Fixes an issue with seek on FileAudioSource (#65) 2024-02-27 19:03:57 +02:00
4 changed files with 73 additions and 59 deletions
@@ -73,14 +73,8 @@ final class FileAudioSource: NSObject, CoreAudioStreamSource {
}
private func performOpen(seek seekOffset: Int) throws {
var reopened = false
let streamStatus = inputStream?.streamStatus ?? .closed
if streamStatus == .notOpen || streamStatus == .closed || streamStatus == .error || streamStatus == .atEnd {
reopened = true
close()
try open()
}
close()
try open()
guard let inputStream = inputStream else {
return
@@ -91,11 +85,6 @@ final class FileAudioSource: NSObject, CoreAudioStreamSource {
} else {
position = 0
}
if !reopened {
if inputStream.hasBytesAvailable {
dataAvailable()
}
}
}
private func dataAvailable() {
@@ -142,23 +142,28 @@ open class AudioPlayer {
serializationQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated)
sourceQueue = DispatchQueue(label: "source.queue", qos: .default)
entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(),
underlyingQueue: sourceQueue,
outputAudioFormat: outputAudioFormat)
fileStreamProcessor = AudioFileStreamProcessor(playerContext: playerContext,
rendererContext: rendererContext,
outputAudioFormat: outputAudioFormat.basicStreamDescription)
playerRenderProcessor = AudioPlayerRenderProcessor(playerContext: playerContext,
rendererContext: rendererContext,
outputAudioFormat: outputAudioFormat.basicStreamDescription)
frameFilterProcessor = FrameFilterProcessor(mixerNodeProvider: {
engine.mainMixerNode
})
entryProvider = AudioEntryProvider(
networkingClient: NetworkingClient(),
underlyingQueue: sourceQueue,
outputAudioFormat: outputAudioFormat
)
fileStreamProcessor = AudioFileStreamProcessor(
playerContext: playerContext,
rendererContext: rendererContext,
outputAudioFormat: outputAudioFormat.basicStreamDescription)
playerRenderProcessor = AudioPlayerRenderProcessor(
playerContext: playerContext,
rendererContext: rendererContext,
outputAudioFormat: outputAudioFormat.basicStreamDescription)
frameFilterProcessor = FrameFilterProcessor(
mixerNodeProvider: {
engine.mainMixerNode
}
)
configPlayerContext()
configPlayerNode()
setupEngine()
@@ -509,6 +514,7 @@ open class AudioPlayer {
/// Pauses the audio engine and stops the player's hardware
private func pauseEngine() {
guard isEngineRunning else { return }
audioEngine.reset()
audioEngine.pause()
player.auAudioUnit.stopHardware()
Logger.debug("engine paused ⏸", category: .generic)
@@ -653,24 +659,6 @@ open class AudioPlayer {
let isPlayingSameItemProbablySeek = playerContext.audioPlayingEntry === nextEntry
let notifyDelegateEntryFinishedPlaying: (AudioEntry?, Bool) -> Void = { [weak self] entry, _ in
guard let self = self else { return }
if let entry = entry, !isPlayingSameItemProbablySeek {
let entryId = entry.id
let progressInFrames = entry.progressInFrames()
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
let duration = entry.duration()
asyncOnMain {
self.delegate?.audioPlayerDidFinishPlaying(player: self,
entryId: entryId,
stopReason: self.stopReason,
progress: progress,
duration: duration)
}
}
}
if let nextEntry = nextEntry {
if !isPlayingSameItemProbablySeek {
nextEntry.lock.withLock {
@@ -685,7 +673,22 @@ open class AudioPlayer {
let playingQueueEntryId = playerContext.audioPlayingEntry?.id ?? AudioEntryId(id: "")
playerContext.entriesLock.unlock()
notifyDelegateEntryFinishedPlaying(entry, isPlayingSameItemProbablySeek)
if let entry = entry, !isPlayingSameItemProbablySeek {
let entryId = entry.id
let progressInFrames = entry.progressInFrames()
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
let duration = entry.duration()
asyncOnMain { [weak self] in
guard let self else { return }
self.delegate?.audioPlayerDidFinishPlaying(
player: self,
entryId: entryId,
stopReason: self.stopReason,
progress: progress,
duration: duration
)
}
}
if !isPlayingSameItemProbablySeek {
playerContext.setInternalState(to: .waitingForData)
@@ -695,10 +698,29 @@ open class AudioPlayer {
}
}
} else {
notifyDelegateEntryFinishedPlaying(entry, isPlayingSameItemProbablySeek)
playerContext.entriesLock.lock()
playerContext.audioPlayingEntry = nil
playerContext.entriesLock.unlock()
if let entry = entry, !isPlayingSameItemProbablySeek {
let entryId = entry.id
let progressInFrames = entry.progressInFrames()
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
let duration = entry.duration()
sourceQueue.async { [weak self] in
guard let self else { return }
self.processSource()
asyncOnMain {
self.delegate?.audioPlayerDidFinishPlaying(
player: self,
entryId: entryId,
stopReason: self.stopReason,
progress: progress,
duration: duration
)
}
}
}
}
sourceQueue.async { [weak self] in
self?.processSource()
@@ -42,7 +42,9 @@ internal final class AudioPlayerContext {
when inState: ((AudioPlayer.InternalState) -> Bool)? = nil)
{
let newValues = playerStateAndStopReason(for: state)
stopReason.write { $0 = newValues.stopReason }
if let stopReason = newValues.stopReason {
self.stopReason.write { $0 = stopReason }
}
guard state != internalState else { return }
if let inState = inState, !inState(internalState) {
return
@@ -30,26 +30,27 @@ extension AudioPlayer {
/// Helper method that returns `AudioPlayerState` and `StopReason` based on the given `InternalState`
/// - Parameter internalState: A value of `InternalState`
/// - Returns: A tuple of `(AudioPlayerState, AudioPlayerStopReason)`
func playerStateAndStopReason(for internalState: AudioPlayer.InternalState) -> (state: AudioPlayerState,
stopReason: AudioPlayerStopReason)
func playerStateAndStopReason(
for internalState: AudioPlayer.InternalState
) -> (state: AudioPlayerState, stopReason: AudioPlayerStopReason?)
{
switch internalState {
case .initial:
return (.ready, .none)
return (.ready, AudioPlayerStopReason.none)
case .running, .playing, .waitingForDataAfterSeek:
return (.playing, .none)
return (.playing, AudioPlayerStopReason.none)
case .pendingNext, .rebuffering, .waitingForData:
return (.bufferring, .none)
return (.bufferring, AudioPlayerStopReason.none)
case .stopped:
return (.stopped, .userAction)
return (.stopped, nil)
case .paused:
return (.paused, .none)
return (.paused, AudioPlayerStopReason.none)
case .disposed:
return (.disposed, .userAction)
case .error:
return (.error, .error)
return (.error, AudioPlayerStopReason.error)
default:
return (.ready, .none)
return (.ready, AudioPlayerStopReason.none)
}
}