Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf724ab04e | |||
| 97909bacce | |||
| 30b0189f61 | |||
| 5bde849bf0 | |||
| b3b519ab4c | |||
| f3b62cc756 | |||
| a56d3314ad | |||
| f75d743cd9 | |||
| f8876d821e | |||
| 81d93fd886 | |||
| 22bbe7fa5a | |||
| 475a2a2b37 | |||
| b4b571f68b | |||
| 1dfc5095e9 | |||
| b6da69c798 | |||
| ce07c919a9 |
+4
@@ -15,6 +15,7 @@
|
||||
831B263D357A5FA2DDC7B1AE4B374092 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; };
|
||||
8F93DB166237195ED222EE55B6404625 /* Pods-SwiftAudioPlayer_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B0B76CB1439F4D361322144E5A65C3A /* Pods-SwiftAudioPlayer_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
A40DBE292391D9CA00F86146 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40DBE282391D9C900F86146 /* Data.swift */; };
|
||||
A411CE3E25F55C0E0039E1CD /* AudioThrottlerNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = A411CE3D25F55C0E0039E1CD /* AudioThrottlerNew.swift */; };
|
||||
A411CE4625F9609D0039E1CD /* SAPlayerFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */; };
|
||||
A41AA0D2238BB9B600A467E1 /* SAPlayingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = A41AA0D1238BB9B600A467E1 /* SAPlayingStatus.swift */; };
|
||||
A4681FC6220113880018AB51 /* SAPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F8D2200E00E0018AB51 /* SAPlayer.swift */; };
|
||||
@@ -101,6 +102,7 @@
|
||||
A19C8F889C787C19BE4123C1896AF501 /* Pods-SwiftAudioPlayer_Example-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftAudioPlayer_Example-resources.sh"; sourceTree = "<group>"; };
|
||||
A39F2A138CF40C1051CA9E227429A86D /* SwiftAudioPlayer.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftAudioPlayer.modulemap; sourceTree = "<group>"; };
|
||||
A40DBE282391D9C900F86146 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
|
||||
A411CE3D25F55C0E0039E1CD /* AudioThrottlerNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioThrottlerNew.swift; sourceTree = "<group>"; };
|
||||
A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerFeatures.swift; sourceTree = "<group>"; };
|
||||
A41AA0D1238BB9B600A467E1 /* SAPlayingStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayingStatus.swift; sourceTree = "<group>"; };
|
||||
A4523BC8220A0B3C0079C4BC /* Credited_LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = Credited_LICENSE; sourceTree = "<group>"; };
|
||||
@@ -297,6 +299,7 @@
|
||||
A4681FB52200FDF30018AB51 /* Converter */,
|
||||
A4681FAA2200F8280018AB51 /* Parser */,
|
||||
A4681FA82200F5A20018AB51 /* AudioThrottler.swift */,
|
||||
A411CE3D25F55C0E0039E1CD /* AudioThrottlerNew.swift */,
|
||||
);
|
||||
path = Engine;
|
||||
sourceTree = "<group>";
|
||||
@@ -574,6 +577,7 @@
|
||||
A4681FCD2201139E0018AB51 /* AudioStreamEngine.swift in Sources */,
|
||||
A411CE4625F9609D0039E1CD /* SAPlayerFeatures.swift in Sources */,
|
||||
A4681FD9220113CD0018AB51 /* AudioStreamWorker.swift in Sources */,
|
||||
A411CE3E25F55C0E0039E1CD /* AudioThrottlerNew.swift in Sources */,
|
||||
A4681FDF220113E20018AB51 /* DirectorThreadSafeClosures.swift in Sources */,
|
||||
A4681FCB220113980018AB51 /* AudioEngine.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ Thus, using [AudioToolbox](https://developer.apple.com/documentation/audiotoolbo
|
||||
1. Play locally saved audio with the same API
|
||||
1. Download audio
|
||||
1. Queue up downloaded and streamed audio for autoplay
|
||||
1. Uses only 1-2% CPU for optimal performance for the rest of your app
|
||||
1. You're able to install taps and any other AVAudioEngine features to do cool things like skipping silences
|
||||
|
||||
### Special Features
|
||||
@@ -33,7 +34,7 @@ iOS 10.0 and higher.
|
||||
### Running the Example Project
|
||||
|
||||
1. Clone repo
|
||||
2. CD to directory
|
||||
2. CD to the `Example` folder where the Example app lives
|
||||
3. Run `pod install` in terminal
|
||||
4. Build and run
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
Log.info(url)
|
||||
super.init(url: url, delegate: delegate, engineAudioFormat: AudioEngine.defaultEngineAudioFormat)
|
||||
do {
|
||||
converter = try AudioConverter(withRemoteUrl: url, toEngineAudioFormat: AudioEngine.defaultEngineAudioFormat)
|
||||
converter = try AudioConverter(withRemoteUrl: url, toEngineAudioFormat: AudioEngine.defaultEngineAudioFormat, withPCMBufferSize: PCM_BUFFER_SIZE)
|
||||
} catch {
|
||||
delegate?.didError()
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
guard let self = self else { return }
|
||||
guard self.playingStatus != .ended else { return }
|
||||
|
||||
self.pollForNextBuffer()
|
||||
self.pollForNextBufferRecursive()
|
||||
self.updateNetworkBufferRange()
|
||||
self.updateNeedle()
|
||||
self.updateIsPlaying()
|
||||
@@ -162,11 +162,11 @@ class AudioStreamEngine: AudioEngine {
|
||||
//Called when
|
||||
//1. First time audio is finally parsed
|
||||
//2. When we run to the end of the network buffer and we're waiting again
|
||||
private func pollForNextBuffer() {
|
||||
private func pollForNextBufferRecursive() {
|
||||
guard shouldPollForNextBuffer else { return }
|
||||
|
||||
do {
|
||||
var nextScheduledBuffer: AVAudioPCMBuffer! = try converter.pullBuffer(withSize: PCM_BUFFER_SIZE)
|
||||
var nextScheduledBuffer: AVAudioPCMBuffer! = try converter.pullBuffer()
|
||||
numberOfBuffersScheduledFromPoll += 1
|
||||
numberOfBuffersScheduledInTotal += 1
|
||||
|
||||
@@ -177,13 +177,13 @@ class AudioStreamEngine: AudioEngine {
|
||||
self?.playerNode.scheduleBuffer(nextScheduledBuffer, completionCallbackType: .dataRendered, completionHandler: { (_) in
|
||||
nextScheduledBuffer = nil
|
||||
self?.numberOfBuffersScheduledInTotal -= 1
|
||||
self?.pollForNextBufferRecursionHelper()
|
||||
self?.pollForNextBufferRecursive()
|
||||
})
|
||||
} else {
|
||||
self?.playerNode.scheduleBuffer(nextScheduledBuffer) {
|
||||
nextScheduledBuffer = nil
|
||||
self?.numberOfBuffersScheduledInTotal -= 1
|
||||
self?.pollForNextBufferRecursionHelper()
|
||||
self?.pollForNextBufferRecursive()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,32 +200,6 @@ class AudioStreamEngine: AudioEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private func pollForNextBufferRecursionHelper() {
|
||||
do {
|
||||
let nextScheduledBuffer = try converter.pullBuffer(withSize: PCM_BUFFER_SIZE)
|
||||
Log.debug("processed buffer for engine of frame lengthL \(nextScheduledBuffer.frameLength)")
|
||||
numberOfBuffersScheduledInTotal += 1
|
||||
|
||||
queue.async { [weak self] in
|
||||
self?.playerNode.scheduleBuffer(nextScheduledBuffer) {
|
||||
self?.numberOfBuffersScheduledInTotal -= 1
|
||||
self?.pollForNextBufferRecursionHelper()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch ConverterError.reachedEndOfFile {
|
||||
Log.info(ConverterError.reachedEndOfFile.localizedDescription)
|
||||
} catch ConverterError.notEnoughData {
|
||||
shouldPollForNextBuffer = true
|
||||
Log.debug(ConverterError.notEnoughData.localizedDescription)
|
||||
} catch ConverterError.superConcerningShouldNeverHappen {
|
||||
Log.error(ConverterError.superConcerningShouldNeverHappen.localizedDescription)
|
||||
} catch {
|
||||
Log.debug(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateNetworkBufferRange() { //for ui
|
||||
let range = converter.pollNetworkAudioAvailabilityRange()
|
||||
isPlayable = (numberOfBuffersScheduledInTotal >= MIN_BUFFERS_TO_BE_PLAYABLE && range.1 > 0) && predictedStreamDuration > 0
|
||||
|
||||
@@ -42,6 +42,15 @@ protocol AudioThrottleable {
|
||||
}
|
||||
|
||||
class AudioThrottler: AudioThrottleable {
|
||||
enum State: String {
|
||||
case INIT
|
||||
case WAITING_FOR_DATA
|
||||
case RECEIVING_DATA
|
||||
case PUSH_DATA
|
||||
case END_OF_DATA
|
||||
case CLEAN_UP
|
||||
}
|
||||
|
||||
private class NetworkDataWrapper: NSObject {
|
||||
let startOffset: UInt
|
||||
var data: Data
|
||||
@@ -93,6 +102,7 @@ class AudioThrottler: AudioThrottleable {
|
||||
//Init
|
||||
let url: AudioURL
|
||||
weak var delegate: AudioThrottleDelegate?
|
||||
private var state: State
|
||||
|
||||
private var networkData: [NetworkDataWrapper] = []
|
||||
var shouldThrottle = false
|
||||
@@ -110,12 +120,16 @@ class AudioThrottler: AudioThrottleable {
|
||||
var largestPollingOffsetDifference: UInt64 = 1
|
||||
|
||||
required init(withRemoteUrl url: AudioURL, withDelegate delegate: AudioThrottleDelegate) {
|
||||
self.state = .INIT
|
||||
self.url = url
|
||||
self.delegate = delegate
|
||||
|
||||
self.state = .WAITING_FOR_DATA
|
||||
AudioDataManager.shared.startStream(withRemoteURL: url) { [weak self] (pto: StreamProgressPTO) in
|
||||
guard let self = self else {return}
|
||||
Log.debug("received stream data of size \(pto.getData().count) and progress: \(pto.getProgress())")
|
||||
guard let self = self else { return }
|
||||
if !self.shouldThrottle { self.state = .RECEIVING_DATA }
|
||||
|
||||
Log.debug("received stream data of size \(pto.getData().count) and progress: \(pto.getProgress())", state: self.state.rawValue)
|
||||
self.delegate?.didUpdate(networkStreamProgress: pto.getProgress())
|
||||
|
||||
if let totalBytesExpected = pto.getTotalBytesExpected() {
|
||||
@@ -129,7 +143,8 @@ class AudioThrottler: AudioThrottleable {
|
||||
self.networkData.append(wrappedNetworkData)
|
||||
|
||||
if !self.shouldThrottle {
|
||||
Log.debug("sending up packet from stream untrottled at start: \(wrappedNetworkData.startOffset)")
|
||||
self.state = .PUSH_DATA
|
||||
Log.debug("sending up packet from stream untrottled at start: \(wrappedNetworkData.startOffset)", state: self.state.rawValue)
|
||||
//NOTE: the order here matters.
|
||||
//We have to set to true before sending up to be processed because
|
||||
//tellByteOffset() is ran in a separate thread than this one
|
||||
@@ -143,6 +158,7 @@ class AudioThrottler: AudioThrottleable {
|
||||
|
||||
func tellAudioFormatFound() {
|
||||
shouldThrottle = true //the above layer has enough info that we can throttle
|
||||
self.state = .RECEIVING_DATA
|
||||
}
|
||||
|
||||
func tellBytesPerAudioPacket(count: UInt64) {
|
||||
@@ -152,14 +168,14 @@ class AudioThrottler: AudioThrottleable {
|
||||
}
|
||||
|
||||
func tellByteOffset(offset: UInt64) {
|
||||
Log.debug("offset \(offset)")
|
||||
Log.debug("offset \(offset)", state: self.state.rawValue)
|
||||
|
||||
for wrappedNetworkData in networkData {
|
||||
if wrappedNetworkData.containsOffset(UInt(offset)) {
|
||||
Log.debug("offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset) is next sent: \(wrappedNetworkData.isNextSent())")
|
||||
Log.debug("offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset) is next sent: \(wrappedNetworkData.isNextSent())", state: self.state.rawValue)
|
||||
|
||||
if wrappedNetworkData.alreadySent {
|
||||
Log.debug("already sent offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
|
||||
Log.debug("already sent offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)", state: self.state.rawValue)
|
||||
|
||||
var bytesSent: UInt = 0
|
||||
var current = wrappedNetworkData
|
||||
@@ -169,14 +185,16 @@ class AudioThrottler: AudioThrottleable {
|
||||
while bytesSent < largestPollingOffsetDifference {
|
||||
if let next = current.next {
|
||||
if !next.alreadySent {
|
||||
Log.info("Sending next network packet with range: \(next.startOffset) to \(next.endOffset), have sent \(bytesSent) bytes so far from \(largestPollingOffsetDifference) bytes")
|
||||
self.state = .PUSH_DATA
|
||||
Log.info("Sending next network packet with range: \(next.startOffset) to \(next.endOffset), have sent \(bytesSent) bytes so far from \(largestPollingOffsetDifference) bytes", self.state.rawValue)
|
||||
next.alreadySent = true
|
||||
delegate?.shouldProcess(networkData: next.data)
|
||||
}
|
||||
bytesSent += next.byteCount
|
||||
current = next
|
||||
} else {
|
||||
Log.debug("next package doesn't exist, bytes sent so far: \(bytesSent)")
|
||||
Log.debug("next package doesn't exist, bytes sent so far: \(bytesSent)", state: self.state.rawValue)
|
||||
self.state = .END_OF_DATA
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -184,16 +202,19 @@ class AudioThrottler: AudioThrottleable {
|
||||
return
|
||||
}
|
||||
|
||||
Log.info("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
|
||||
Log.info("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)", self.state.rawValue)
|
||||
wrappedNetworkData.alreadySent = true
|
||||
self.state = .PUSH_DATA
|
||||
delegate?.shouldProcess(networkData: wrappedNetworkData.data)
|
||||
return
|
||||
} else {
|
||||
self.state = .END_OF_DATA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tellSeek(offset: UInt64) {
|
||||
Log.info("seek with offset: \(offset)")
|
||||
Log.info("seek with offset: \(offset)", self.state.rawValue)
|
||||
|
||||
if networkData.count == 0 {
|
||||
byteOffsetBecauseOfSeek = UInt(offset)
|
||||
@@ -225,7 +246,7 @@ class AudioThrottler: AudioThrottleable {
|
||||
|
||||
d.alreadySent = false
|
||||
wrappedData.alreadySent = true
|
||||
Log.info("\(d) ::: \(wrappedData)")
|
||||
Log.info("\(d) ::: \(wrappedData)", self.state.rawValue)
|
||||
|
||||
delegate?.shouldProcess(networkData: wrappedData.data)
|
||||
return
|
||||
@@ -242,5 +263,6 @@ class AudioThrottler: AudioThrottleable {
|
||||
|
||||
func invalidate() {
|
||||
AudioDataManager.shared.deleteStream(withRemoteURL: url)
|
||||
self.state = .CLEAN_UP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// AudioThrottlerNew.swift
|
||||
// SwiftAudioPlayer
|
||||
//
|
||||
// Created by Tanha Kabir on 3/7/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AudioThrottlerNew: AudioThrottleable {
|
||||
enum State: String {
|
||||
case INIT
|
||||
case WAITING_FOR_DATA
|
||||
case RECEIVING_DATA
|
||||
case PUSH_DATA
|
||||
case END_OF_DATA
|
||||
case CLEAN_UP
|
||||
}
|
||||
|
||||
private var delegate: AudioThrottleDelegate
|
||||
|
||||
required init(withRemoteUrl url: AudioURL, withDelegate delegate: AudioThrottleDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
func tellAudioFormatFound() {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func tellByteOffset(offset: UInt64) {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func tellSeek(offset: UInt64) {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func tellBytesPerAudioPacket(count: UInt64) {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func pollRangeOfBytesAvailable() -> (UInt64, UInt64) {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
<#code#>
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,8 @@ import AudioToolbox
|
||||
protocol AudioConvertable {
|
||||
var engineAudioFormat: AVAudioFormat {get}
|
||||
|
||||
init(withRemoteUrl url: AudioURL, toEngineAudioFormat: AVAudioFormat) throws
|
||||
func pullBuffer(withSize size: AVAudioFrameCount) throws -> AVAudioPCMBuffer
|
||||
init(withRemoteUrl url: AudioURL, toEngineAudioFormat: AVAudioFormat, withPCMBufferSize size: AVAudioFrameCount) throws
|
||||
func pullBuffer() throws -> AVAudioPCMBuffer
|
||||
func pollPredictedDuration() -> Duration?
|
||||
func pollNetworkAudioAvailabilityRange() -> (Needle, Duration)
|
||||
func seek(_ needle: Needle)
|
||||
@@ -70,13 +70,20 @@ class AudioConverter: AudioConvertable {
|
||||
|
||||
//From protocol
|
||||
public var engineAudioFormat: AVAudioFormat
|
||||
let pcmBufferSize: AVAudioFrameCount
|
||||
|
||||
//Field
|
||||
var converter: AudioConverterRef? //set by AudioConverterNew
|
||||
var currentAudioPacketIndex: AVAudioPacketCount = 0
|
||||
|
||||
required init(withRemoteUrl url: AudioURL, toEngineAudioFormat: AVAudioFormat) throws {
|
||||
// use to store reference to the allocated buffers from the converter to properly deallocate them before the next packet is being converted
|
||||
var converterBuffer: UnsafeMutableRawPointer?
|
||||
var converterDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?
|
||||
|
||||
required init(withRemoteUrl url: AudioURL, toEngineAudioFormat: AVAudioFormat, withPCMBufferSize size: AVAudioFrameCount) throws {
|
||||
self.engineAudioFormat = toEngineAudioFormat
|
||||
self.pcmBufferSize = size
|
||||
|
||||
do {
|
||||
parser = try AudioParser(withRemoteUrl: url, parsedFileAudioFormatCallback: {
|
||||
[weak self] (fileAudioFormat: AVAudioFormat) in
|
||||
@@ -108,17 +115,17 @@ class AudioConverter: AudioConvertable {
|
||||
}
|
||||
}
|
||||
|
||||
func pullBuffer(withSize size: AVAudioFrameCount) throws -> AVAudioPCMBuffer {
|
||||
func pullBuffer() throws -> AVAudioPCMBuffer {
|
||||
guard let converter = converter else {
|
||||
Log.debug("reader_error trying to read before converter has been created")
|
||||
throw ConverterError.cannotCreatePCMBufferWithoutConverter
|
||||
}
|
||||
|
||||
guard let pcmBuffer = AVAudioPCMBuffer(pcmFormat: engineAudioFormat, frameCapacity: size) else {
|
||||
guard let pcmBuffer = AVAudioPCMBuffer(pcmFormat: engineAudioFormat, frameCapacity: pcmBufferSize) else {
|
||||
Log.monitor(ConverterError.failedToCreatePCMBuffer.errorDescription as Any)
|
||||
throw ConverterError.failedToCreatePCMBuffer
|
||||
}
|
||||
pcmBuffer.frameLength = size
|
||||
pcmBuffer.frameLength = pcmBufferSize
|
||||
|
||||
/**
|
||||
The whole thing is wrapped in queue.sync() because the converter listener
|
||||
@@ -127,7 +134,7 @@ class AudioConverter: AudioConvertable {
|
||||
*/
|
||||
return try queue.sync { () -> AVAudioPCMBuffer in
|
||||
let framesPerPacket = engineAudioFormat.streamDescription.pointee.mFramesPerPacket
|
||||
var numberOfPacketsWeWantTheBufferToFill = size / framesPerPacket
|
||||
var numberOfPacketsWeWantTheBufferToFill = pcmBuffer.frameLength / framesPerPacket
|
||||
|
||||
let context = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
|
||||
let status = AudioConverterFillComplexBuffer(converter, ConverterListener, context, &numberOfPacketsWeWantTheBufferToFill, pcmBuffer.mutableAudioBufferList, nil)
|
||||
|
||||
@@ -65,6 +65,10 @@ func ConverterListener(_ converter: AudioConverterRef, _ packetCount: UnsafeMuta
|
||||
return ReaderShouldNotHappenError
|
||||
}
|
||||
|
||||
if let lastBuffer = selfAudioConverter.converterBuffer {
|
||||
lastBuffer.deallocate()
|
||||
}
|
||||
|
||||
// Copy data over (note we've only processing a single packet of data at a time)
|
||||
var packet = audioPacket.1
|
||||
let packetByteCount = packet.count //this is not the count of an array
|
||||
@@ -75,6 +79,12 @@ func ConverterListener(_ converter: AudioConverterRef, _ packetCount: UnsafeMuta
|
||||
})
|
||||
ioData.pointee.mBuffers.mDataByteSize = UInt32(packetByteCount)
|
||||
|
||||
selfAudioConverter.converterBuffer = ioData.pointee.mBuffers.mData
|
||||
|
||||
if let lastDescription = selfAudioConverter.converterDescriptions {
|
||||
lastDescription.deallocate()
|
||||
}
|
||||
|
||||
// Handle packet descriptions for compressed formats (MP3, AAC, etc)
|
||||
let fileFormatDescription = fileAudioFormat.streamDescription.pointee
|
||||
if fileFormatDescription.mFormatID != kAudioFormatLinearPCM {
|
||||
@@ -86,6 +96,8 @@ func ConverterListener(_ converter: AudioConverterRef, _ packetCount: UnsafeMuta
|
||||
outPacketDescriptions?.pointee?.pointee.mVariableFramesInPacket = 0
|
||||
}
|
||||
|
||||
selfAudioConverter.converterDescriptions = outPacketDescriptions?.pointee
|
||||
|
||||
packetCount.pointee = 1
|
||||
|
||||
//we've successfully given a packet to the LPCM buffer now we can process the next audio packet
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
+16
-14
@@ -23,6 +23,8 @@ enum LogLevel: Int {
|
||||
// Specify which types of log messages to display. Default level is set to WARN, which means Log will print any log messages of type only WARN, ERROR, MONITOR, and TEST. To print DEBUG and INFO logs, set the level to a lower value.
|
||||
var logLevel: LogLevel = LogLevel.MONITOR
|
||||
|
||||
typealias State = String
|
||||
|
||||
class Log {
|
||||
private init() {}
|
||||
|
||||
@@ -42,11 +44,11 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func test(_ logMessage: Any, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func test(_ logMessage: Any, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.TEST.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "TEST ❇️❇️❇️❇️")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,16 +65,16 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func error(_ logMessage: Any, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func error(_ logMessage: Any, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.ERROR.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "ERROR 🛑🛑🛑🛑")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
|
||||
if logLevel.rawValue <= LogLevel.EXTERNAL_DEBUG.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "WARNING")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +91,11 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func monitor(_ logMessage: Any, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func monitor(_ logMessage: Any, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.ERROR.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "ERROR 🔥🔥🔥🔥")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,16 +112,16 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func warn(_ logMessage: Any, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func warn(_ logMessage: Any, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.WARN.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "WARN ⚠️⚠️⚠️⚠️")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
|
||||
if logLevel.rawValue <= LogLevel.EXTERNAL_DEBUG.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "DEBUG")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +138,11 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func info(_ logMessage: Any, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func info(_ logMessage: Any, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.INFO.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "INFO 🖤🖤🖤🖤")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,11 +159,11 @@ class Log {
|
||||
- Parameter functionName: automatically generated based on the function that called this function
|
||||
- Parameter lineNumber: automatically generated based on the line that called this function
|
||||
*/
|
||||
public static func debug(_ logMessage: Any?..., classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
public static func debug(_ logMessage: Any?..., state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
|
||||
let fileName = URLUtil.getNameFromStringPath(classPath)
|
||||
if logLevel.rawValue <= LogLevel.DEBUG.rawValue {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "DEBUG 🐝🐝🐝🐝")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '4.0.0'
|
||||
s.version = '4.1.0'
|
||||
s.summary = 'SwiftAudioPlayer is a Swift based audio player that can handle streaming from a remote location and audio manipulation.'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
Reference in New Issue
Block a user