Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0758c14909 | |||
| 03c6a7692c | |||
| 02a3606185 | |||
| 7e45a7b2f5 |
@@ -153,7 +153,7 @@ extension PlayerControlsViewModel: AudioPlayerServiceDelegate {
|
||||
updateContent?(.updateMetadata(""))
|
||||
}
|
||||
|
||||
func errorOccured(error _: AudioPlayerError) {}
|
||||
func errorOccurred(error _: AudioPlayerError) {}
|
||||
|
||||
func metadataReceived(metadata: [String: String]) {
|
||||
guard !metadata.isEmpty else { return }
|
||||
|
||||
@@ -96,7 +96,7 @@ extension PlayerViewModel: AudioPlayerServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func errorOccured(error _: AudioPlayerError) {
|
||||
func errorOccurred(error _: AudioPlayerError) {
|
||||
currentPlayingItemIndex = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ protocol AudioPlayerServiceDelegate: AnyObject {
|
||||
func didStartPlaying()
|
||||
func didStopPlaying()
|
||||
func statusChanged(status: AudioPlayerState)
|
||||
func errorOccured(error: AudioPlayerError)
|
||||
func errorOccurred(error: AudioPlayerError)
|
||||
func metadataReceived(metadata: [String: String])
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ extension AudioPlayerService: AudioPlayerDelegate {
|
||||
}
|
||||
|
||||
func audioPlayerUnexpectedError(player _: AudioPlayer, error: AudioPlayerError) {
|
||||
delegate.invoke(invocation: { $0.errorOccured(error: error) })
|
||||
delegate.invoke(invocation: { $0.errorOccurred(error: error) })
|
||||
}
|
||||
|
||||
func audioPlayerDidCancel(player _: AudioPlayer, queuedItems _: [AudioEntryId]) {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'AudioStreaming'
|
||||
s.version = '0.7.0'
|
||||
s.version = '0.8.0'
|
||||
s.license = 'MIT'
|
||||
s.summary = 'An AudioPlayer/Streaming library for iOS written in Swift using AVAudioEngine.'
|
||||
s.homepage = 'https://github.com/dimitris-c/AudioStreaming'
|
||||
|
||||
@@ -811,7 +811,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.7.0;
|
||||
MARKETING_VERSION = 0.8.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
@@ -842,7 +842,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.7.0;
|
||||
MARKETING_VERSION = 0.8.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
|
||||
@@ -31,7 +31,7 @@ internal enum Logger {
|
||||
}
|
||||
|
||||
static func error(_ message: StaticString, category: Category, args: CVarArg...) {
|
||||
proccess(message, category: category, type: .error, args: args)
|
||||
process(message, category: category, type: .error, args: args)
|
||||
}
|
||||
|
||||
static func error(_ message: StaticString, category: Category) {
|
||||
@@ -39,14 +39,14 @@ internal enum Logger {
|
||||
}
|
||||
|
||||
static func debug(_ message: StaticString, category: Category, args: CVarArg...) {
|
||||
proccess(message, category: category, type: .debug, args: args)
|
||||
process(message, category: category, type: .debug, args: args)
|
||||
}
|
||||
|
||||
static func debug(_ message: StaticString, category: Category) {
|
||||
debug(message, category: category, args: [])
|
||||
}
|
||||
|
||||
private static func proccess(_ message: StaticString, category: Category, type: OSLogType, args: CVarArg...) {
|
||||
private static func process(_ message: StaticString, category: Category, type: OSLogType, args: CVarArg...) {
|
||||
guard isEnabled else { return }
|
||||
os_log(message, log: category.toOSLog(), type: type, args)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ internal final class NetworkDataStream {
|
||||
let error: Error?
|
||||
}
|
||||
|
||||
private var lock = UnfairLock()
|
||||
private var streamCallback: StreamCompletion?
|
||||
|
||||
/// The serial queue for all internal async actions.
|
||||
@@ -67,7 +66,6 @@ internal final class NetworkDataStream {
|
||||
|
||||
@discardableResult
|
||||
func responseStream(completion: @escaping StreamCompletion) -> Self {
|
||||
lock.lock(); defer { lock.unlock() }
|
||||
streamCallback = completion
|
||||
return self
|
||||
}
|
||||
@@ -81,7 +79,6 @@ internal final class NetworkDataStream {
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock(); defer { lock.unlock() }
|
||||
guard state.canBecome(.cancelled) else { return }
|
||||
state = .cancelled
|
||||
streamCallback = nil
|
||||
|
||||
@@ -53,7 +53,7 @@ internal class AudioEntry {
|
||||
return Double(audioStreamFormat.mFramesPerPacket) / Double(sampleRate)
|
||||
}
|
||||
|
||||
private var avaragePacketByteSize: Double {
|
||||
private var averagePacketByteSize: Double {
|
||||
let packets = processedPacketsState
|
||||
guard !packets.isEmpty else { return 0 }
|
||||
return Double(packets.sizeTotal / packets.count)
|
||||
@@ -109,7 +109,7 @@ internal class AudioEntry {
|
||||
if packetsCount > estimationMinPacketsPreferred ||
|
||||
(audioStreamFormat.mBytesPerFrame == 0 && packetsCount > estimationMinPackets)
|
||||
{
|
||||
return avaragePacketByteSize / packetDuration * 8
|
||||
return averagePacketByteSize / packetDuration * 8
|
||||
}
|
||||
}
|
||||
return (Double(audioStreamFormat.mBytesPerFrame) * audioStreamFormat.mSampleRate) * 8
|
||||
@@ -151,12 +151,12 @@ extension AudioEntry: AudioStreamSourceDelegate {
|
||||
delegate?.dataAvailable(source: source, data: data)
|
||||
}
|
||||
|
||||
func errorOccured(source: CoreAudioStreamSource, error: Error) {
|
||||
delegate?.errorOccured(source: source, error: error)
|
||||
func errorOccurred(source: CoreAudioStreamSource, error: Error) {
|
||||
delegate?.errorOccurred(source: source, error: error)
|
||||
}
|
||||
|
||||
func endOfFileOccured(source: CoreAudioStreamSource) {
|
||||
delegate?.endOfFileOccured(source: source)
|
||||
func endOfFileOccurred(source: CoreAudioStreamSource) {
|
||||
delegate?.endOfFileOccurred(source: source)
|
||||
}
|
||||
|
||||
func metadataReceived(data: [String: String]) {
|
||||
|
||||
@@ -10,9 +10,9 @@ protocol AudioStreamSourceDelegate: AnyObject {
|
||||
/// Indicates that there's data available
|
||||
func dataAvailable(source: CoreAudioStreamSource, data: Data)
|
||||
/// Indicates an error occurred
|
||||
func errorOccured(source: CoreAudioStreamSource, error: Error)
|
||||
func errorOccurred(source: CoreAudioStreamSource, error: Error)
|
||||
/// Indicates end of file has occurred
|
||||
func endOfFileOccured(source: CoreAudioStreamSource)
|
||||
func endOfFileOccurred(source: CoreAudioStreamSource)
|
||||
/// Indicates metadata read from stream
|
||||
func metadataReceived(data: [String: String])
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ final class FileAudioSource: NSObject, CoreAudioStreamSource {
|
||||
do {
|
||||
try performOpen(seek: offset)
|
||||
} catch {
|
||||
delegate?.errorOccured(source: self, error: error)
|
||||
delegate?.errorOccurred(source: self, error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,11 +139,11 @@ extension FileAudioSource: StreamDelegate {
|
||||
case .hasBytesAvailable:
|
||||
dataAvailable()
|
||||
case .endEncountered:
|
||||
delegate?.endOfFileOccured(source: self)
|
||||
delegate?.endOfFileOccurred(source: self)
|
||||
case .errorOccurred:
|
||||
delegate?.errorOccured(source: self, error: AudioPlayerError.codecError)
|
||||
delegate?.errorOccurred(source: self, error: AudioPlayerError.codecError)
|
||||
case .endEncountered:
|
||||
delegate?.endOfFileOccured(source: self)
|
||||
delegate?.endOfFileOccurred(source: self)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
}
|
||||
|
||||
internal let underlyingQueue: DispatchQueue
|
||||
internal let streamOperationQueue: OperationQueue
|
||||
internal let netStatusService: NetStatusProvider
|
||||
internal var waitingForNetwork = false
|
||||
internal let retrierTimeout: Retrier
|
||||
@@ -66,7 +67,12 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
supportsSeek = false
|
||||
netStatusService = netStatusProvider
|
||||
self.icycastHeadersProcessor = icycastHeadersProcessor
|
||||
self.underlyingQueue = DispatchQueue(label: "remote.audio.source.queue", target: underlyingQueue)
|
||||
self.underlyingQueue = underlyingQueue
|
||||
streamOperationQueue = OperationQueue()
|
||||
streamOperationQueue.underlyingQueue = underlyingQueue
|
||||
streamOperationQueue.maxConcurrentOperationCount = 1
|
||||
streamOperationQueue.isSuspended = true
|
||||
streamOperationQueue.name = "remote.audio.source.data.stream.queue"
|
||||
retrierTimeout = retrier
|
||||
startNetworkService()
|
||||
}
|
||||
@@ -80,12 +86,12 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
let metadataProcessor = MetadataStreamProcessor(parser: metadataParser.eraseToAnyParser())
|
||||
let netStatusProvider = NetStatusService(network: NWPathMonitor())
|
||||
let icyheaderProcessor = IcycastHeadersProcessor()
|
||||
let retrierTimout = Retrier(interval: .seconds(1), maxInterval: 5, underlyingQueue: nil)
|
||||
let retrierTimeout = Retrier(interval: .seconds(1), maxInterval: 5, underlyingQueue: nil)
|
||||
self.init(networking: networking,
|
||||
metadataStreamSource: metadataProcessor,
|
||||
icycastHeadersProcessor: icyheaderProcessor,
|
||||
netStatusProvider: netStatusProvider,
|
||||
retrier: retrierTimout,
|
||||
retrier: retrierTimeout,
|
||||
url: url,
|
||||
underlyingQueue: underlyingQueue,
|
||||
httpHeaders: httpHeaders)
|
||||
@@ -104,6 +110,8 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
func close() {
|
||||
retrierTimeout.cancel()
|
||||
netStatusService.stop()
|
||||
streamOperationQueue.isSuspended = false
|
||||
streamOperationQueue.cancelAllOperations()
|
||||
if let streamTask = streamRequest {
|
||||
streamTask.cancel()
|
||||
networkingClient.remove(task: streamTask)
|
||||
@@ -130,11 +138,11 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
}
|
||||
|
||||
func suspend() {
|
||||
streamRequest?.suspend()
|
||||
streamOperationQueue.isSuspended = true
|
||||
}
|
||||
|
||||
func resume() {
|
||||
streamRequest?.resume()
|
||||
streamOperationQueue.isSuspended = false
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
@@ -156,9 +164,7 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
let request = networkingClient.stream(request: urlRequest)
|
||||
.responseStream { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
self.underlyingQueue.sync {
|
||||
self.handleResponse(event: event)
|
||||
}
|
||||
self.handleResponse(event: event)
|
||||
}
|
||||
.resume()
|
||||
|
||||
@@ -172,13 +178,17 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
switch event {
|
||||
case let .response(urlResponse):
|
||||
parseResponseHeader(response: urlResponse)
|
||||
streamOperationQueue.isSuspended = false
|
||||
case let .stream(event):
|
||||
handleStreamEvent(event: event)
|
||||
case let .complete(event):
|
||||
if let error = event.error {
|
||||
delegate?.errorOccured(source: self, error: error)
|
||||
delegate?.errorOccurred(source: self, error: error)
|
||||
} else {
|
||||
delegate?.endOfFileOccured(source: self)
|
||||
addCompletionOperation { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.delegate?.endOfFileOccurred(source: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,23 +197,25 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
switch event {
|
||||
case let .success(value):
|
||||
if let audioData = value.data {
|
||||
if shouldTryParsingIcycastHeaders {
|
||||
let (header, extractedAudio) = icycastHeadersProcessor.proccess(data: audioData)
|
||||
if let header = header {
|
||||
shouldTryParsingIcycastHeaders = false
|
||||
let parser = IcycastHeaderParser()
|
||||
parsedHeaderOutput = parser.parse(input: header)
|
||||
if let metadataStep = parsedHeaderOutput?.metadataStep {
|
||||
metadataStreamProcessor.metadataAvailable(step: metadataStep)
|
||||
addStreamOperation { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.shouldTryParsingIcycastHeaders {
|
||||
let (header, extractedAudio) = self.icycastHeadersProcessor.process(data: audioData)
|
||||
if let header = header {
|
||||
self.shouldTryParsingIcycastHeaders = false
|
||||
let parser = IcycastHeaderParser()
|
||||
self.parsedHeaderOutput = parser.parse(input: header)
|
||||
if let metadataStep = self.parsedHeaderOutput?.metadataStep {
|
||||
self.metadataStreamProcessor.metadataAvailable(step: metadataStep)
|
||||
}
|
||||
}
|
||||
|
||||
let audioCount = processAudio(data: extractedAudio)
|
||||
relativePosition += audioCount
|
||||
let audioCount = self.processAudio(data: extractedAudio)
|
||||
self.relativePosition += audioCount
|
||||
return
|
||||
}
|
||||
let audioCount = self.processAudio(data: audioData)
|
||||
self.relativePosition += audioCount
|
||||
}
|
||||
let audioCount = processAudio(data: audioData)
|
||||
relativePosition += audioCount
|
||||
}
|
||||
case .failure:
|
||||
if !netStatusService.isConnected {
|
||||
@@ -219,8 +231,8 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
/// - Parameter data: The audio to be processed
|
||||
/// - Returns: An `Int` value representing the amount of audio data bytes.
|
||||
private func processAudio(data: Data) -> Int {
|
||||
if self.metadataStreamProcessor.canProccessMetadata {
|
||||
let extractedAudioData = self.metadataStreamProcessor.proccessMetadata(data: data)
|
||||
if self.metadataStreamProcessor.canProcessMetadata {
|
||||
let extractedAudioData = self.metadataStreamProcessor.processMetadata(data: data)
|
||||
self.delegate?.dataAvailable(source: self, data: extractedAudioData)
|
||||
return extractedAudioData.count
|
||||
} else {
|
||||
@@ -256,9 +268,9 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
// check for error
|
||||
if statusCode == 416 { // range not satisfied error
|
||||
if length >= 0 { seekOffset = length }
|
||||
delegate?.endOfFileOccured(source: self)
|
||||
delegate?.endOfFileOccurred(source: self)
|
||||
} else if statusCode >= 300 {
|
||||
delegate?.errorOccured(source: self, error: NetworkError.serverError)
|
||||
delegate?.errorOccurred(source: self, error: NetworkError.serverError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +278,7 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
var urlRequest = URLRequest(url: url)
|
||||
urlRequest.networkServiceType = .avStreaming
|
||||
urlRequest.cachePolicy = .reloadIgnoringLocalCacheData
|
||||
urlRequest.timeoutInterval = 240
|
||||
urlRequest.timeoutInterval = 60
|
||||
|
||||
for header in additionalRequestHeaders {
|
||||
urlRequest.addValue(header.value, forHTTPHeaderField: header.key)
|
||||
@@ -287,6 +299,25 @@ public class RemoteAudioSource: AudioStreamSource {
|
||||
self.seek(at: self.position)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Network Stream Operation Queue
|
||||
|
||||
/// Schedules the given block on the stream operation queue
|
||||
///
|
||||
/// - Parameter block: A closure to be executed
|
||||
private func addStreamOperation(_ block: @escaping () -> Void) {
|
||||
let operation = BlockOperation(block: block)
|
||||
streamOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
/// Schedules the given block on the stream operation queue as a completion
|
||||
///
|
||||
/// - Parameter block: A closure to be executed
|
||||
private func addCompletionOperation(_ block: @escaping () -> Void) {
|
||||
let operation = BlockOperation(block: block)
|
||||
operation.queuePriority = .veryLow
|
||||
streamOperationQueue.addOperation(operation)
|
||||
}
|
||||
}
|
||||
|
||||
extension RemoteAudioSource: MetadataStreamSourceDelegate {
|
||||
|
||||
@@ -125,7 +125,6 @@ open class AudioPlayer {
|
||||
private let playerRenderProcessor: AudioPlayerRenderProcessor
|
||||
private let frameFilterProcessor: FrameFilterProcessor
|
||||
|
||||
private let audioReadSource: DispatchTimerSource
|
||||
private let serializationQueue: DispatchQueue
|
||||
private let sourceQueue: DispatchQueue
|
||||
|
||||
@@ -142,7 +141,6 @@ open class AudioPlayer {
|
||||
|
||||
serializationQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated)
|
||||
sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated)
|
||||
audioReadSource = DispatchTimerSource(interval: .milliseconds(200), queue: sourceQueue)
|
||||
|
||||
entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(),
|
||||
underlyingQueue: sourceQueue,
|
||||
@@ -166,7 +164,6 @@ open class AudioPlayer {
|
||||
deinit {
|
||||
playerContext.audioPlayingEntry?.close()
|
||||
clearQueue()
|
||||
stopReadProccessFromSource()
|
||||
rendererContext.clean()
|
||||
}
|
||||
|
||||
@@ -195,14 +192,13 @@ open class AudioPlayer {
|
||||
do {
|
||||
try self.startEngineIfNeeded()
|
||||
} catch {
|
||||
self.raiseUnxpected(error: .audioSystemError(.engineFailure))
|
||||
self.raiseUnexpected(error: .audioSystemError(.engineFailure))
|
||||
}
|
||||
}
|
||||
|
||||
sourceQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.processSource()
|
||||
self.startReadProcessFromSourceIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +254,6 @@ open class AudioPlayer {
|
||||
public func stop() {
|
||||
guard playerContext.internalState != .stopped else { return }
|
||||
|
||||
stopReadProccessFromSource()
|
||||
serializationQueue.sync {
|
||||
stopEngine(reason: .userAction)
|
||||
}
|
||||
@@ -289,7 +284,6 @@ open class AudioPlayer {
|
||||
serializationQueue.sync {
|
||||
pauseEngine()
|
||||
}
|
||||
stopReadProccessFromSource()
|
||||
playerContext.audioPlayingEntry?.suspend()
|
||||
sourceQueue.async { [weak self] in
|
||||
self?.processSource()
|
||||
@@ -315,7 +309,6 @@ open class AudioPlayer {
|
||||
}
|
||||
startPlayer(resetBuffers: false)
|
||||
}
|
||||
startReadProcessFromSourceIfNeeded()
|
||||
}
|
||||
|
||||
/// Seeks the audio to the specified time.
|
||||
@@ -424,7 +417,7 @@ open class AudioPlayer {
|
||||
self.playerRenderProcessor.attachCallback(on: unit, audioFormat: self.outputAudioFormat)
|
||||
case let .failure(error):
|
||||
assertionFailure("couldn't create player unit: \(error)")
|
||||
self.raiseUnxpected(error: .audioSystemError(.playerNotFound))
|
||||
self.raiseUnexpected(error: .audioSystemError(.playerNotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,12 +445,12 @@ open class AudioPlayer {
|
||||
fileStreamProcessor.fileStreamCallback = { [weak self] effect in
|
||||
guard let self = self else { return }
|
||||
switch effect {
|
||||
case .proccessSource:
|
||||
case .processSource:
|
||||
self.sourceQueue.async {
|
||||
self.processSource()
|
||||
}
|
||||
case let .raiseError(error):
|
||||
self.raiseUnxpected(error: error)
|
||||
self.raiseUnexpected(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,24 +523,6 @@ open class AudioPlayer {
|
||||
Logger.debug("engine stopped 🛑", category: .generic)
|
||||
}
|
||||
|
||||
/// Starts the timer of `audioReadSource` for proccesing the source read stream
|
||||
///
|
||||
/// This calls `processSource` method every `500 ms`
|
||||
private func startReadProcessFromSourceIfNeeded() {
|
||||
guard audioReadSource.state != .activated else { return }
|
||||
// TODO: this might be needed after all...
|
||||
// audioReadSource.add { [weak self] in
|
||||
// self?.processSource()
|
||||
// }
|
||||
// audioReadSource.activate()
|
||||
}
|
||||
|
||||
/// Stops and removes the handler from the timer, @see `audioReadSource`
|
||||
private func stopReadProccessFromSource() {
|
||||
audioReadSource.suspend()
|
||||
audioReadSource.removeHandler()
|
||||
}
|
||||
|
||||
/// Starts the audio player, reseting the buffers if requested
|
||||
///
|
||||
/// - parameter resetBuffers: A `Bool` value indicating if the buffers should be reset, prior starting the player.
|
||||
@@ -561,7 +536,7 @@ open class AudioPlayer {
|
||||
try player.auAudioUnit.startHardware()
|
||||
} catch {
|
||||
stopEngine(reason: .error)
|
||||
raiseUnxpected(error: .audioSystemError(.playerStartError))
|
||||
raiseUnexpected(error: .audioSystemError(.playerStartError))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +544,6 @@ open class AudioPlayer {
|
||||
private func processSource() {
|
||||
dispatchPrecondition(condition: .onQueue(sourceQueue))
|
||||
|
||||
guard !playerContext.disposedRequested else { return }
|
||||
guard playerContext.internalState != .paused else { return }
|
||||
|
||||
if playerContext.internalState == .pendingNext {
|
||||
@@ -606,7 +580,6 @@ open class AudioPlayer {
|
||||
setCurrentReading(entry: entry, startPlaying: shouldStartPlaying, shouldClearQueue: false)
|
||||
} else if playerContext.audioPlayingEntry == nil {
|
||||
if playerContext.internalState != .stopped {
|
||||
stopReadProccessFromSource()
|
||||
stopEngine(reason: .eof)
|
||||
}
|
||||
}
|
||||
@@ -622,7 +595,7 @@ open class AudioPlayer {
|
||||
playingEntry.seekRequest.lock.unlock()
|
||||
|
||||
if originalSeekToTimeRequested, playerContext.audioReadingEntry === playingEntry {
|
||||
proccessSeekTime()
|
||||
processSeekTime()
|
||||
|
||||
let version = playingEntry.seekRequest.version.value
|
||||
if currSeekVersion == version {
|
||||
@@ -634,7 +607,7 @@ open class AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
private func proccessSeekTime() {
|
||||
private func processSeekTime() {
|
||||
assert(playerContext.audioReadingEntry === playerContext.audioPlayingEntry,
|
||||
"reading and playing entry must be the same")
|
||||
fileStreamProcessor.processSeek()
|
||||
@@ -749,7 +722,7 @@ open class AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
private func raiseUnxpected(error: AudioPlayerError) {
|
||||
private func raiseUnexpected(error: AudioPlayerError) {
|
||||
playerContext.setInternalState(to: .error)
|
||||
// todo raise on main thread from playback thread
|
||||
asyncOnMain { [weak self] in
|
||||
@@ -770,7 +743,7 @@ extension AudioPlayer: AudioStreamSourceDelegate {
|
||||
let openFileStreamStatus = fileStreamProcessor.openFileStream(with: source.audioFileHint)
|
||||
guard openFileStreamStatus == noErr else {
|
||||
let streamError = AudioFileStreamError(status: openFileStreamStatus)
|
||||
raiseUnxpected(error: .audioSystemError(.fileStreamError(streamError)))
|
||||
raiseUnexpected(error: .audioSystemError(.fileStreamError(streamError)))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -780,7 +753,7 @@ extension AudioPlayer: AudioStreamSourceDelegate {
|
||||
guard streamBytesStatus == noErr else {
|
||||
if let playingEntry = playerContext.audioPlayingEntry, playingEntry.has(same: source) {
|
||||
let streamBytesError = AudioFileStreamError(status: streamBytesStatus)
|
||||
raiseUnxpected(error: .streamParseBytesFailure(streamBytesError))
|
||||
raiseUnexpected(error: .streamParseBytesFailure(streamBytesError))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -791,12 +764,12 @@ extension AudioPlayer: AudioStreamSourceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func errorOccured(source: CoreAudioStreamSource, error: Error) {
|
||||
func errorOccurred(source: CoreAudioStreamSource, error: Error) {
|
||||
guard let entry = playerContext.audioReadingEntry, entry.has(same: source) else { return }
|
||||
raiseUnxpected(error: .networkError(.failure(error)))
|
||||
raiseUnexpected(error: .networkError(.failure(error)))
|
||||
}
|
||||
|
||||
func endOfFileOccured(source: CoreAudioStreamSource) {
|
||||
func endOfFileOccurred(source: CoreAudioStreamSource) {
|
||||
let hasSameSource = playerContext.audioReadingEntry?.has(same: source) ?? false
|
||||
guard playerContext.audioReadingEntry == nil || hasSameSource else {
|
||||
source.delegate = nil
|
||||
@@ -827,7 +800,10 @@ extension AudioPlayer: AudioStreamSourceDelegate {
|
||||
playerContext.audioReadingEntry = nil
|
||||
playerContext.entriesLock.unlock()
|
||||
|
||||
processSource()
|
||||
sourceQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.processSource()
|
||||
}
|
||||
}
|
||||
|
||||
func metadataReceived(data: [String: String]) {
|
||||
|
||||
@@ -6,30 +6,31 @@
|
||||
import Foundation
|
||||
|
||||
internal final class AudioPlayerContext {
|
||||
var stopReason = Protected<AudioPlayerStopReason>(.none)
|
||||
var stopReason: Protected<AudioPlayerStopReason>
|
||||
|
||||
var state = Protected<AudioPlayerState>(.ready)
|
||||
var state: Protected<AudioPlayerState>
|
||||
var stateChanged: ((_ oldState: AudioPlayerState, _ newState: AudioPlayerState) -> Void)?
|
||||
|
||||
var muted = Protected<Bool>(false)
|
||||
var muted: Protected<Bool>
|
||||
|
||||
var internalState: AudioPlayer.InternalState {
|
||||
playerInternalState.value
|
||||
}
|
||||
|
||||
let entriesLock = UnfairLock()
|
||||
let entriesLock: UnfairLock
|
||||
var audioReadingEntry: AudioEntry?
|
||||
var audioPlayingEntry: AudioEntry?
|
||||
|
||||
var disposedRequested: Bool
|
||||
|
||||
/// This is the player's internal state to use
|
||||
/// - NOTE: Do not use directly instead use the `internalState` to set and get the property
|
||||
/// or the `setInternalState(to:when:)`method
|
||||
private var playerInternalState = Protected<AudioPlayer.InternalState>(.initial)
|
||||
|
||||
init() {
|
||||
disposedRequested = false
|
||||
stopReason = Protected<AudioPlayerStopReason>(.none)
|
||||
state = Protected<AudioPlayerState>(.ready)
|
||||
muted = Protected<Bool>(false)
|
||||
entriesLock = UnfairLock()
|
||||
}
|
||||
|
||||
/// Sets the internal state if given the `inState` will be evaluated before assignment occurs.
|
||||
|
||||
@@ -22,7 +22,7 @@ public protocol AudioPlayerDelegate: AnyObject {
|
||||
stopReason: AudioPlayerStopReason,
|
||||
progress: Double,
|
||||
duration: Double)
|
||||
/// Tells the delegate when an unexpected error occured.
|
||||
/// Tells the delegate when an unexpected error occurred.
|
||||
/// - note: Probably a good time to recreate the player when this occurs
|
||||
func audioPlayerUnexpectedError(player: AudioPlayer, error: AudioPlayerError)
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ final class AudioRendererContext {
|
||||
|
||||
let packetsSemaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
var discontinuous: Bool = false
|
||||
|
||||
let framesRequiredToStartPlaying: UInt32
|
||||
let framesRequiredAfterRebuffering: UInt32
|
||||
let framesRequiredForDataAfterSeekPlaying: UInt32
|
||||
|
||||
@@ -9,7 +9,7 @@ import AVFoundation
|
||||
|
||||
enum AudioConvertStatus: Int32 {
|
||||
case done = 100
|
||||
case proccessed = 0
|
||||
case processed = 0
|
||||
}
|
||||
|
||||
struct AudioConvertInfo {
|
||||
@@ -20,11 +20,11 @@ struct AudioConvertInfo {
|
||||
}
|
||||
|
||||
enum FileStreamProcessorEffect {
|
||||
case proccessSource
|
||||
case processSource
|
||||
case raiseError(AudioPlayerError)
|
||||
}
|
||||
|
||||
/// An object that handles the proccessing of AudioFileStream, its packets etc.
|
||||
/// An object that handles the processing of AudioFileStream, its packets etc.
|
||||
final class AudioFileStreamProcessor {
|
||||
private let maxCompressedPacketForBitrate = 4096
|
||||
|
||||
@@ -38,8 +38,9 @@ final class AudioFileStreamProcessor {
|
||||
internal var audioConverter: AudioConverterRef?
|
||||
internal var discontinuous: Bool = false
|
||||
internal var inputFormat = AudioStreamBasicDescription()
|
||||
internal var fileFormat: String = ""
|
||||
internal let fa4mFormat = "fa4m"
|
||||
|
||||
internal var currentFileFormat: String = ""
|
||||
internal let fileFormatsForDelayedConverterCreation: Set = ["fa4m", "f4pm"]
|
||||
|
||||
var isFileStreamOpen: Bool {
|
||||
audioFileStream != nil
|
||||
@@ -165,7 +166,7 @@ final class AudioFileStreamProcessor {
|
||||
|
||||
var classDesc = AudioClassDescription()
|
||||
var outputFormat = toFormat
|
||||
if getHardwareCodecClassDescripition(formatId: inputFormat.mFormatID, classDesc: &classDesc) {
|
||||
if getHardwareCodecClassDescription(formatId: inputFormat.mFormatID, classDesc: &classDesc) {
|
||||
AudioConverterNewSpecific(&inputFormat, &outputFormat, 1, &classDesc, &audioConverter)
|
||||
}
|
||||
|
||||
@@ -178,11 +179,12 @@ final class AudioFileStreamProcessor {
|
||||
}
|
||||
}
|
||||
self.inputFormat = inputFormat
|
||||
assignMagicCookieToConverterIfNeeded()
|
||||
}
|
||||
|
||||
private func assignMagicCookieToConverterIfNeeded() {
|
||||
// magic cookie info
|
||||
let fileHint = playerContext.audioReadingEntry?.audioFileHint
|
||||
let isProperFormat = fileHint != kAudioFileAAC_ADTSType && fileHint != kAudioFileM4AType && fileHint != kAudioFileMPEG4Type
|
||||
if let fileStream = audioFileStream, isProperFormat {
|
||||
if let fileStream = audioFileStream {
|
||||
var cookieSize: UInt32 = 0
|
||||
guard AudioFileStreamGetPropertyInfo(fileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, nil) == noErr else {
|
||||
return
|
||||
@@ -263,7 +265,7 @@ final class AudioFileStreamProcessor {
|
||||
var size = UInt32(4)
|
||||
AudioFileStreamGetProperty(fileStream, kAudioFileStreamProperty_FileFormat, &size, &fileFormat)
|
||||
if let stringFileFormat = String(data: Data(fileFormat), encoding: .utf8) {
|
||||
self.fileFormat = stringFileFormat
|
||||
self.currentFileFormat = stringFileFormat
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +295,7 @@ final class AudioFileStreamProcessor {
|
||||
entry.processedPacketsState.bufferSize = packetBufferSize
|
||||
}
|
||||
|
||||
if fileFormat != fa4mFormat {
|
||||
if !fileFormatsForDelayedConverterCreation.contains(currentFileFormat) {
|
||||
createAudioConverter(from: entry.audioStreamFormat, to: outputAudioFormat)
|
||||
}
|
||||
}
|
||||
@@ -331,7 +333,7 @@ final class AudioFileStreamProcessor {
|
||||
i += step
|
||||
}
|
||||
|
||||
if fileFormat == fa4mFormat {
|
||||
if fileFormatsForDelayedConverterCreation.contains(currentFileFormat) {
|
||||
if let inputStreamFormat = playerContext.audioReadingEntry?.audioStreamFormat {
|
||||
createAudioConverter(from: inputStreamFormat, to: outputAudioFormat)
|
||||
}
|
||||
@@ -346,12 +348,12 @@ final class AudioFileStreamProcessor {
|
||||
inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?)
|
||||
{
|
||||
guard let entry = playerContext.audioReadingEntry else { return }
|
||||
guard entry.audioStreamState.processedDataFormat, !playerContext.disposedRequested else { return }
|
||||
guard entry.audioStreamState.processedDataFormat else { return }
|
||||
|
||||
if let playingEntry = playerContext.audioPlayingEntry,
|
||||
playingEntry.seekRequest.requested, playingEntry.calculatedBitrate() > 0
|
||||
{
|
||||
fileStreamCallback?(.proccessSource)
|
||||
fileStreamCallback?(.processSource)
|
||||
if rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
@@ -375,11 +377,11 @@ final class AudioFileStreamProcessor {
|
||||
convertInfo.audioBuffer.mNumberChannels = playingAudioStreamFormat.mChannelsPerFrame
|
||||
}
|
||||
|
||||
updateProccessedPackets(inPacketDescriptions: inPacketDescriptions,
|
||||
updateProcessedPackets(inPacketDescriptions: inPacketDescriptions,
|
||||
inNumberPackets: inNumberPackets)
|
||||
|
||||
var status: OSStatus = noErr
|
||||
packetProccess: while status == noErr {
|
||||
packetProcess: while status == noErr {
|
||||
rendererContext.lock.lock()
|
||||
let bufferContext = rendererContext.bufferContext
|
||||
var used = bufferContext.frameUsedCount
|
||||
@@ -401,8 +403,7 @@ final class AudioFileStreamProcessor {
|
||||
if framesLeftInBuffer > 0 {
|
||||
break
|
||||
}
|
||||
if playerContext.disposedRequested
|
||||
|| playerContext.internalState == .disposed
|
||||
if playerContext.internalState == .disposed
|
||||
|| playerContext.internalState == .pendingNext
|
||||
|| playerContext.internalState == .stopped
|
||||
{
|
||||
@@ -412,7 +413,7 @@ final class AudioFileStreamProcessor {
|
||||
if let playingEntry = playerContext.audioPlayingEntry,
|
||||
playingEntry.seekRequest.requested, playingEntry.calculatedBitrate() > 0
|
||||
{
|
||||
fileStreamCallback?(.proccessSource)
|
||||
fileStreamCallback?(.processSource)
|
||||
if rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
@@ -457,7 +458,7 @@ final class AudioFileStreamProcessor {
|
||||
framesToDecode = start
|
||||
if framesToDecode == 0 {
|
||||
fillUsedFrames(framesCount: framesAdded)
|
||||
continue packetProccess
|
||||
continue packetProcess
|
||||
}
|
||||
prefillLocalBufferList(bufferList: localBufferList,
|
||||
dataOffset: 0,
|
||||
@@ -475,9 +476,9 @@ final class AudioFileStreamProcessor {
|
||||
if status == AudioConvertStatus.done.rawValue {
|
||||
fillUsedFrames(framesCount: framesAdded)
|
||||
return
|
||||
} else if status == AudioConvertStatus.proccessed.rawValue {
|
||||
} else if status == AudioConvertStatus.processed.rawValue {
|
||||
fillUsedFrames(framesCount: framesAdded)
|
||||
continue packetProccess
|
||||
continue packetProcess
|
||||
} else if status != 0 {
|
||||
fileStreamCallback?(.raiseError(.codecError))
|
||||
return
|
||||
@@ -502,9 +503,9 @@ final class AudioFileStreamProcessor {
|
||||
if status == AudioConvertStatus.done.rawValue {
|
||||
fillUsedFrames(framesCount: framesAdded)
|
||||
return
|
||||
} else if status == AudioConvertStatus.proccessed.rawValue {
|
||||
} else if status == AudioConvertStatus.processed.rawValue {
|
||||
fillUsedFrames(framesCount: framesAdded)
|
||||
continue packetProccess
|
||||
continue packetProcess
|
||||
} else if status != 0 {
|
||||
fileStreamCallback?(.raiseError(.codecError))
|
||||
return
|
||||
@@ -545,8 +546,8 @@ final class AudioFileStreamProcessor {
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
private func updateProccessedPackets(inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?,
|
||||
inNumberPackets: UInt32)
|
||||
private func updateProcessedPackets(inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?,
|
||||
inNumberPackets: UInt32)
|
||||
{
|
||||
guard let inPacketDescriptions = inPacketDescriptions else { return }
|
||||
guard let readingEntry = playerContext.audioReadingEntry else { return }
|
||||
@@ -618,12 +619,12 @@ private func _converterCallback(inAudioConverter _: AudioConverterRef,
|
||||
ioNumberDataPackets.pointee = convertInfo.pointee.numberOfPackets
|
||||
convertInfo.pointee.done = true
|
||||
|
||||
return AudioConvertStatus.proccessed.rawValue
|
||||
return AudioConvertStatus.processed.rawValue
|
||||
}
|
||||
|
||||
// MARK: HardwareCodedClass method
|
||||
|
||||
private func getHardwareCodecClassDescripition(formatId: UInt32, classDesc: UnsafeMutablePointer<AudioClassDescription>) -> Bool {
|
||||
private func getHardwareCodecClassDescription(formatId: UInt32, classDesc: UnsafeMutablePointer<AudioClassDescription>) -> Bool {
|
||||
#if os(iOS)
|
||||
var size: UInt32 = 0
|
||||
let formatIdSize = UInt32(MemoryLayout.size(ofValue: formatId))
|
||||
|
||||
@@ -38,31 +38,32 @@ final class IcycastHeadersProcessor {
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
func proccess(data: Data) -> (Data?, Data) {
|
||||
let stopProccessingCheckOne: [UInt8] = Array("\n\n".utf8)
|
||||
let stopProccessingCheckTwo: [UInt8] = Array("\r\n\r\n".utf8)
|
||||
func process(data: Data) -> (Data?, Data) {
|
||||
let stopProcessingCheckOne: [UInt8] = Array("\n\n".utf8)
|
||||
let stopProcessingCheckTwo: [UInt8] = Array("\r\n\r\n".utf8)
|
||||
let icyPrefix: [UInt8] = Array("ICY ".utf8)
|
||||
let httpPrefix: [UInt8] = Array("HTTP".utf8)
|
||||
return data.withUnsafeBytes { buffer -> (Data?, Data) in
|
||||
guard !buffer.isEmpty else { return (nil, data) }
|
||||
var bytesRead = 0
|
||||
let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
// Read through the bytes and stop when our search is complete
|
||||
// Since we don't know the amount of bytes to be proccessed
|
||||
// Since we don't know the amount of bytes to be processed
|
||||
// we add each character up until we found on of the checks as defined above.
|
||||
while bytesRead < buffer.count, !searchComplete {
|
||||
let pointer = bytes + bytesRead
|
||||
icecastHeaders.append(pointer, count: 1)
|
||||
|
||||
if icecastHeaders.count >= stopProccessingCheckOne.count {
|
||||
if icecastHeaders.suffix(stopProccessingCheckOne.count) == stopProccessingCheckOne {
|
||||
if icecastHeaders.count >= stopProcessingCheckOne.count {
|
||||
if icecastHeaders.suffix(stopProcessingCheckOne.count) == stopProcessingCheckOne {
|
||||
iceHeaderAvailable = true
|
||||
searchComplete = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if icecastHeaders.count >= stopProccessingCheckTwo.count {
|
||||
if icecastHeaders.suffix(stopProccessingCheckTwo.count) == stopProccessingCheckTwo {
|
||||
if icecastHeaders.count >= stopProcessingCheckTwo.count {
|
||||
if icecastHeaders.suffix(stopProcessingCheckTwo.count) == stopProcessingCheckTwo {
|
||||
iceHeaderAvailable = true
|
||||
searchComplete = true
|
||||
break
|
||||
|
||||
@@ -13,15 +13,15 @@ protocol MetadataStreamSource {
|
||||
var delegate: MetadataStreamSourceDelegate? { get set }
|
||||
|
||||
/// Returns `true` when the stream header has indicated that we can proccess metadata, otherwise `false`.
|
||||
var canProccessMetadata: Bool { get }
|
||||
var canProcessMetadata: Bool { get }
|
||||
|
||||
/// Assigns the metadata step of the metadata
|
||||
func metadataAvailable(step: Int)
|
||||
|
||||
/// Proccess the received data and extract the metadata if any, returns audio data only.
|
||||
/// Process the received data and extract the metadata if any, returns audio data only.
|
||||
/// - parameter data: A `Data` object for parsing any metadata
|
||||
/// - returns: The extracted audio `Data`
|
||||
func proccessMetadata(data: Data) -> Data
|
||||
func processMetadata(data: Data) -> Data
|
||||
|
||||
/// Resets the processor
|
||||
func reset()
|
||||
@@ -44,7 +44,7 @@ protocol MetadataStreamSource {
|
||||
final class MetadataStreamProcessor: MetadataStreamSource {
|
||||
weak var delegate: MetadataStreamSourceDelegate?
|
||||
|
||||
var canProccessMetadata: Bool {
|
||||
var canProcessMetadata: Bool {
|
||||
return metadataStep > 0
|
||||
}
|
||||
|
||||
@@ -73,10 +73,10 @@ final class MetadataStreamProcessor: MetadataStreamSource {
|
||||
audioDataBytesRead = 0
|
||||
}
|
||||
|
||||
// MARK: Proccess Metadata
|
||||
// MARK: Process Metadata
|
||||
|
||||
@inline(__always)
|
||||
func proccessMetadata(data: Data) -> Data {
|
||||
func processMetadata(data: Data) -> Data {
|
||||
data.withUnsafeBytes { buffer -> Data in
|
||||
guard !buffer.isEmpty else { return data }
|
||||
var audioData = Data()
|
||||
|
||||
@@ -14,7 +14,7 @@ struct HeaderField {
|
||||
}
|
||||
|
||||
enum IcyHeaderField {
|
||||
public static let icyMentaint = "icy-metaint"
|
||||
public static let icyMetaint = "icy-metaint"
|
||||
}
|
||||
|
||||
struct HTTPHeaderParserOutput {
|
||||
@@ -64,7 +64,7 @@ struct HTTPHeaderParser: HTTPHeaderParsing {
|
||||
}
|
||||
|
||||
var metadataStep = 0
|
||||
if let icyMetaint = value(forHTTPHeaderField: IcyHeaderField.icyMentaint, in: input),
|
||||
if let icyMetaint = value(forHTTPHeaderField: IcyHeaderField.icyMetaint, in: input),
|
||||
let intValue = Int(icyMetaint)
|
||||
{
|
||||
metadataStep = intValue
|
||||
|
||||
@@ -23,7 +23,7 @@ struct IcycastHeaderParser: Parser {
|
||||
result[String(key)] = String(value)
|
||||
}
|
||||
}
|
||||
let metadataStep = Int(result[IcyHeaderField.icyMentaint] ?? "") ?? 0
|
||||
let metadataStep = Int(result[IcyHeaderField.icyMetaint] ?? "") ?? 0
|
||||
let contentType = result[HeaderField.contentType.lowercased()] ?? "audio/mpeg"
|
||||
let typeId = audioFileType(mimeType: contentType)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ struct MetadataParser: Parser {
|
||||
|
||||
func parse(input: Data) -> MetadataOutput {
|
||||
guard let string = String(data: input, encoding: .utf8) else { return .failure(.unableToParse) }
|
||||
// remove added bytes (zeros) and seperate the string on every ';' char
|
||||
// remove added bytes (zeros) and separate the string on every ';' char
|
||||
let pairs = string.trimmingCharacters(in: CharacterSet(charactersIn: "\0")).components(separatedBy: ";")
|
||||
let temp: [String: String] = [:]
|
||||
let metadata = pairs.reduce(into: temp) { result, next in
|
||||
|
||||
+8
-8
@@ -18,19 +18,19 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
let processor = MetadataStreamProcessor(parser: parser.eraseToAnyParser())
|
||||
|
||||
// without calling `metadataAvailable(step:)` it should be false
|
||||
XCTAssertFalse(processor.canProccessMetadata)
|
||||
XCTAssertFalse(processor.canProcessMetadata)
|
||||
|
||||
// calling `metadataAvailable(step:)` with zero
|
||||
processor.metadataAvailable(step: 0)
|
||||
|
||||
// it should be false
|
||||
XCTAssertFalse(processor.canProccessMetadata)
|
||||
XCTAssertFalse(processor.canProcessMetadata)
|
||||
|
||||
// calling `metadataAvailable(step:)` with greater zero
|
||||
processor.metadataAvailable(step: 1)
|
||||
|
||||
// it should be true
|
||||
XCTAssertTrue(processor.canProccessMetadata)
|
||||
XCTAssertTrue(processor.canProcessMetadata)
|
||||
}
|
||||
|
||||
func test_Processor_Outputs_Correct_Metadata_ForStep_WithEmptyMetadata() throws {
|
||||
@@ -45,7 +45,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
// this is the step value as received from the http headers
|
||||
processor.metadataAvailable(step: 16000)
|
||||
|
||||
let audio = processor.proccessMetadata(data: data)
|
||||
let audio = processor.processMetadata(data: data)
|
||||
XCTAssertFalse(audio.isEmpty)
|
||||
|
||||
XCTAssertTrue(metadataDelegateSpy.receivedMetadata.called)
|
||||
@@ -64,7 +64,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
// this is the step value as received from the http headers
|
||||
processor.metadataAvailable(step: 16000)
|
||||
|
||||
let audio = processor.proccessMetadata(data: data)
|
||||
let audio = processor.processMetadata(data: data)
|
||||
XCTAssertFalse(audio.isEmpty)
|
||||
|
||||
XCTAssertTrue(metadataDelegateSpy.receivedMetadata.called)
|
||||
@@ -83,7 +83,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
// this is the step value as received from the http headers
|
||||
processor.metadataAvailable(step: 8000)
|
||||
|
||||
let audio = processor.proccessMetadata(data: data)
|
||||
let audio = processor.processMetadata(data: data)
|
||||
XCTAssertFalse(audio.isEmpty)
|
||||
|
||||
XCTAssertTrue(metadataDelegateSpy.receivedMetadata.called)
|
||||
@@ -106,7 +106,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
// this is the step value as received from the http headers
|
||||
processor.metadataAvailable(step: 16000)
|
||||
|
||||
let audio = processor.proccessMetadata(data: data)
|
||||
let audio = processor.processMetadata(data: data)
|
||||
XCTAssertFalse(audio.isEmpty)
|
||||
|
||||
XCTAssertFalse(metadataDelegateSpy.receivedMetadata.called)
|
||||
@@ -122,7 +122,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
// this is the step value as received from the http headers
|
||||
processor.metadataAvailable(step: 16000)
|
||||
|
||||
let audio = processor.proccessMetadata(data: data)
|
||||
let audio = processor.processMetadata(data: data)
|
||||
XCTAssertTrue(audio.isEmpty)
|
||||
|
||||
XCTAssertFalse(metadataDelegateSpy.receivedMetadata.called)
|
||||
|
||||
@@ -34,7 +34,7 @@ class HTTPHeaderParserTests: XCTestCase {
|
||||
let headers: [String: String] =
|
||||
[HeaderField.contentLength: "1000",
|
||||
HeaderField.contentType: "audio/mp3",
|
||||
IcyHeaderField.icyMentaint: "16000"]
|
||||
IcyHeaderField.icyMetaint: "16000"]
|
||||
let httpURLResponse = HTTPURLResponse(url: URL(string: "www.google.com")!,
|
||||
statusCode: 200,
|
||||
httpVersion: "",
|
||||
@@ -57,7 +57,7 @@ class HTTPHeaderParserTests: XCTestCase {
|
||||
let headers: [String: String] =
|
||||
[HeaderField.contentLength.lowercased(): "1000",
|
||||
HeaderField.contentType.lowercased(): "audio/mp3",
|
||||
IcyHeaderField.icyMentaint.lowercased(): "16000"]
|
||||
IcyHeaderField.icyMetaint.lowercased(): "16000"]
|
||||
let httpURLResponse = HTTPURLResponse(url: URL(string: "www.google.com")!,
|
||||
statusCode: 200,
|
||||
httpVersion: "",
|
||||
|
||||
Reference in New Issue
Block a user