Improvements in seek functionality
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user