Improvements in seek functionality

This commit is contained in:
Dimitris C
2020-11-15 20:14:59 +00:00
parent 430db697f4
commit 8cdc2cc285
5 changed files with 37 additions and 16 deletions
@@ -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"
}
}
}
@@ -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,
@@ -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()
@@ -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()
@@ -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 {