Compare commits

..

6 Commits

Author SHA1 Message Date
dimitris-c 0758c14909 Bumping version 2021-12-06 20:01:12 +02:00
Dimitris C 03c6a7692c Fixes an issue where the memory is not released after paused audio (#33) 2021-12-06 19:58:50 +02:00
dimitris-c 02a3606185 Fixes unit tests 2021-10-05 18:55:34 +03:00
Dimitris C 7e45a7b2f5 Removes unneeded DispatchTimerSource (#32)
Correct Spelling issues
Fix an issue in RemoteAudioSource when icycast headers are not available
2021-10-05 18:32:55 +03:00
Dimitris C 30b4189778 Bumping version (#30) 2021-09-07 15:47:59 +03:00
Dimitris C 8bdc2a64f7 Fixes a memory leak issue in RemoteAudioSource (#29) 2021-09-07 15:36:51 +03:00
22 changed files with 119 additions and 144 deletions
@@ -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 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'AudioStreaming'
s.version = '0.6.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'
+2 -2
View File
@@ -811,7 +811,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 0.6.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.6.0;
MARKETING_VERSION = 0.8.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+3 -3
View File
@@ -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)
}
@@ -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
}
@@ -86,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)
@@ -110,7 +110,7 @@ public class RemoteAudioSource: AudioStreamSource {
func close() {
retrierTimeout.cancel()
netStatusService.stop()
streamOperationQueue.isSuspended = true
streamOperationQueue.isSuspended = false
streamOperationQueue.cancelAllOperations()
if let streamTask = streamRequest {
streamTask.cancel()
@@ -138,12 +138,10 @@ public class RemoteAudioSource: AudioStreamSource {
}
func suspend() {
streamRequest?.suspend()
streamOperationQueue.isSuspended = true
}
func resume() {
streamRequest?.resume()
streamOperationQueue.isSuspended = false
}
@@ -185,11 +183,11 @@ public class RemoteAudioSource: AudioStreamSource {
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 {
addCompletionOperation { [weak self] in
guard let self = self else { return }
self.delegate?.endOfFileOccured(source: self)
self.delegate?.endOfFileOccurred(source: self)
}
}
}
@@ -202,7 +200,7 @@ public class RemoteAudioSource: AudioStreamSource {
addStreamOperation { [weak self] in
guard let self = self else { return }
if self.shouldTryParsingIcycastHeaders {
let (header, extractedAudio) = self.icycastHeadersProcessor.proccess(data: audioData)
let (header, extractedAudio) = self.icycastHeadersProcessor.process(data: audioData)
if let header = header {
self.shouldTryParsingIcycastHeaders = false
let parser = IcycastHeaderParser()
@@ -210,11 +208,10 @@ public class RemoteAudioSource: AudioStreamSource {
if let metadataStep = self.parsedHeaderOutput?.metadataStep {
self.metadataStreamProcessor.metadataAvailable(step: metadataStep)
}
let audioCount = self.processAudio(data: extractedAudio)
self.relativePosition += audioCount
return
}
let audioCount = self.processAudio(data: extractedAudio)
self.relativePosition += audioCount
return
}
let audioCount = self.processAudio(data: audioData)
self.relativePosition += audioCount
@@ -234,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 {
@@ -271,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)
}
}
@@ -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,23 +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 }
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.
@@ -560,7 +536,7 @@ open class AudioPlayer {
try player.auAudioUnit.startHardware()
} catch {
stopEngine(reason: .error)
raiseUnxpected(error: .audioSystemError(.playerStartError))
raiseUnexpected(error: .audioSystemError(.playerStartError))
}
}
@@ -568,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 {
@@ -605,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)
}
}
@@ -621,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 {
@@ -633,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()
@@ -748,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
@@ -769,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
}
}
@@ -779,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
}
@@ -790,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
@@ -826,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
@@ -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: "",