From 8cdc2cc28572e8da5f57337ab658402091df66ad Mon Sep 17 00:00:00 2001 From: Dimitris C Date: Wed, 11 Nov 2020 19:59:45 +0000 Subject: [PATCH] Improvements in seek functionality --- .../Core/Extensions/AudioFileStream+Helpers.swift | 5 +++++ .../Streaming/AudioPlayer/AudioPlayer.swift | 9 ++++----- .../Processors/AudioFileStreamProcessor.swift | 14 +++++++++----- .../Streaming/AudioSource/AudioEntry.swift | 11 +++++++++-- .../Streaming/AudioSource/AudioEntryProvider.swift | 14 ++++++++++---- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/AudioStreaming/Core/Extensions/AudioFileStream+Helpers.swift b/AudioStreaming/Core/Extensions/AudioFileStream+Helpers.swift index 0bdae38..1db6790 100644 --- a/AudioStreaming/Core/Extensions/AudioFileStream+Helpers.swift +++ b/AudioStreaming/Core/Extensions/AudioFileStream+Helpers.swift @@ -41,6 +41,7 @@ public enum AudioFileStreamError: CustomDebugStringConvertible { case unsupportedProperty case valueUnknown case unknownError + case noError public init(status: OSStatus) { switch status { @@ -68,6 +69,8 @@ public enum AudioFileStreamError: CustomDebugStringConvertible { self = .unspecifiedError case kAudioFileStreamError_DiscontinuityCantRecover: self = .discontinuityCantRecover + case noErr: + self = .noError default: self = .unknownError } @@ -104,6 +107,8 @@ public enum AudioFileStreamError: CustomDebugStringConvertible { return "The property value is not present in this file before the audio data." case .unknownError: return "An unknown error occurred" + case .noError: + return "No error" } } } diff --git a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift index 9632182..4987a1d 100644 --- a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift +++ b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift @@ -72,10 +72,7 @@ public final class AudioPlayer { let playingEntry = playerContext.audioPlayingEntry playerContext.entriesLock.unlock() guard let entry = playingEntry, !entry.seekRequest.requested else { return 0 } - - return entry.lock.around { - return Double(entry.seekTime) + (Double(entry.framesState.played) / outputAudioFormat.sampleRate) - } + return entry.progress } /// The current configuration of the player. @@ -128,7 +125,9 @@ public final class AudioPlayer { sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated, target: underlyingQueue) audioReadSource = DispatchTimerSource(interval: .milliseconds(200), queue: sourceQueue) - entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(), underlyingQueue: sourceQueue) + entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(), + underlyingQueue: sourceQueue, + outputAudioFormat: outputAudioFormat) fileStreamProcessor = AudioFileStreamProcessor(playerContext: playerContext, rendererContext: rendererContext, diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift index 238dba4..359e7c1 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift @@ -100,9 +100,11 @@ final class AudioFileStreamProcessor { } let dataOffset = Double(readingEntry.audioStreamState.dataOffset) - let seekTimeToProgress = readingEntry.seekRequest.time / readingEntry.duration() let dataLengthInBytes = Double(readingEntry.audioDataLengthBytes()) - var seekByteOffset = Int64((dataOffset + seekTimeToProgress) * dataLengthInBytes) + let entryDuration = readingEntry.duration() + let duration = entryDuration < readingEntry.progress && entryDuration > 0 ? readingEntry.progress : entryDuration + + var seekByteOffset = Int64(dataOffset + (readingEntry.seekRequest.time / duration) * dataLengthInBytes) if seekByteOffset > readingEntry.length - (2 * Int(readingEntry.processedPacketsState.bufferSize)) { seekByteOffset = Int64(readingEntry.length - (2 * Int(readingEntry.processedPacketsState.bufferSize))) @@ -118,14 +120,16 @@ final class AudioFileStreamProcessor { var packetsAlignedByteOffset: Int64 = 0 let seekPacket = Int64(floor(readingEntry.seekRequest.time / readingEntry.packetDuration)) - guard AudioFileStreamSeek(stream, seekPacket, &packetsAlignedByteOffset, &ioFlags) == noErr else { - Logger.error("seek failed", category: .generic) + let seekStatus = AudioFileStreamSeek(stream, seekPacket, &packetsAlignedByteOffset, &ioFlags) + guard seekStatus == noErr else { + let streamError = AudioFileStreamError(status: seekStatus) + Logger.error("seek failed %@", category: .generic, args: streamError.debugDescription) return } let dataOffset = Int64(readingEntry.audioStreamState.dataOffset) - seekByteOffset = packetsAlignedByteOffset + dataOffset if !ioFlags.contains(.offsetIsEstimated) { + seekByteOffset = packetsAlignedByteOffset + dataOffset let delta = Double((seekByteOffset - dataOffset) - packetsAlignedByteOffset) / bitrate * 8 readingEntry.lock.lock() diff --git a/AudioStreaming/Streaming/AudioSource/AudioEntry.swift b/AudioStreaming/Streaming/AudioSource/AudioEntry.swift index ff30436..24b18cb 100644 --- a/AudioStreaming/Streaming/AudioSource/AudioEntry.swift +++ b/AudioStreaming/Streaming/AudioSource/AudioEntry.swift @@ -34,6 +34,11 @@ internal class AudioEntry { source.length } + var progress: Double { + lock.lock(); defer { lock.unlock() } + return seekTime + (Double(framesState.played) / outputAudioFormat.sampleRate) + } + var audioStreamFormat = AudioStreamBasicDescription() /// Hold the seek time, if a seek was requested @@ -55,10 +60,12 @@ internal class AudioEntry { } private let source: AudioStreamSource + private let outputAudioFormat: AVAudioFormat - init(source: AudioStreamSource, entryId: AudioEntryId) { + init(source: AudioStreamSource, entryId: AudioEntryId, outputAudioFormat: AVAudioFormat) { self.source = source - id = entryId + self.outputAudioFormat = outputAudioFormat + self.id = entryId seekTime = 0.0 seekRequest = SeekRequest() diff --git a/AudioStreaming/Streaming/AudioSource/AudioEntryProvider.swift b/AudioStreaming/Streaming/AudioSource/AudioEntryProvider.swift index 37199ea..84b162b 100644 --- a/AudioStreaming/Streaming/AudioSource/AudioEntryProvider.swift +++ b/AudioStreaming/Streaming/AudioSource/AudioEntryProvider.swift @@ -6,7 +6,7 @@ // Copyright © 2020 Decimal. All rights reserved. // -import Foundation +import AVFoundation protocol AudioEntryProviding { func provideAudioEntry(url: URL, headers: [String: String]) -> AudioEntry @@ -16,22 +16,28 @@ protocol AudioEntryProviding { final class AudioEntryProvider: AudioEntryProviding { private let networkingClient: NetworkingClient private let underlyingQueue: DispatchQueue + private let outputAudioFormat: AVAudioFormat - init(networkingClient: NetworkingClient, underlyingQueue: DispatchQueue) { + init(networkingClient: NetworkingClient, + underlyingQueue: DispatchQueue, + outputAudioFormat: AVAudioFormat) { self.networkingClient = networkingClient self.underlyingQueue = underlyingQueue + self.outputAudioFormat = outputAudioFormat } func provideAudioEntry(url: URL, headers: [String: String]) -> AudioEntry { let source = provideAudioSource(url: url, headers: headers) return AudioEntry(source: source, - entryId: AudioEntryId(id: url.absoluteString)) + entryId: AudioEntryId(id: url.absoluteString), + outputAudioFormat: outputAudioFormat) } func provideAudioEntry(url: URL) -> AudioEntry { let source = provideAudioSource(url: url, headers: [:]) return AudioEntry(source: source, - entryId: AudioEntryId(id: url.absoluteString)) + entryId: AudioEntryId(id: url.absoluteString), + outputAudioFormat: outputAudioFormat) } func provideAudioSource(url: URL, headers: [String: String]) -> AudioStreamSource {