Compare commits

..

3 Commits

Author SHA1 Message Date
tanhakabir e3e4e4dd46 Release 4.2.0 2021-04-16 10:05:57 -07:00
tanhakabir b60e567a83 fix recursive polling logic (#91) 2021-04-16 10:05:21 -07:00
tanhakabir 17e0ee5dd8 Update example app to include a radio stream link (#89)
* update links

* update example app to show podcast, soundbite, and radio
2021-04-06 21:16:13 -07:00
10 changed files with 50 additions and 115 deletions
-4
View File
@@ -15,7 +15,6 @@
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 */; };
@@ -102,7 +101,6 @@
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>"; };
@@ -299,7 +297,6 @@
A4681FB52200FDF30018AB51 /* Converter */,
A4681FAA2200F8280018AB51 /* Parser */,
A4681FA82200F5A20018AB51 /* AudioThrottler.swift */,
A411CE3D25F55C0E0039E1CD /* AudioThrottlerNew.swift */,
);
path = Engine;
sourceTree = "<group>";
@@ -577,7 +574,6 @@
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 */,
);
@@ -72,8 +72,8 @@
<rect key="frame" x="16" y="60" width="343" height="32"/>
<segments>
<segment title="Soundbite"/>
<segment title="Acquired"/>
<segment title="Y Combinator"/>
<segment title="Podcast"/>
<segment title="Radio"/>
</segments>
<connections>
<action selector="audioSelected:" destination="vXZ-lx-hvc" eventType="valueChanged" id="oYE-yq-348"/>
+7 -7
View File
@@ -13,18 +13,18 @@ struct AudioInfo: Hashable {
var urls: [URL] = [URL(string: "https://www.fesliyanstudios.com/musicfiles/2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com/15SecVersion2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com.mp3")!,
URL(string: "https://chtbl.com/track/18338/traffic.libsyn.com/secure/acquired/acquired_-_armrev_2.mp3?dest-id=376122")!,
URL(string: "https://backtracks.fm/ycombinator/pr/0f685f72-29b1-11e9-9bcf-0ece7a7d2472/111---jake-klamka-and-kevin-hale---y-combinator.mp3?s=1&amp;sd=1&amp;u=1549423185")!]
URL(string: "https://ice6.somafm.com/groovesalad-256-mp3")!]
var url: URL {
switch index {
case 0:
return URL(string: "https://www.fesliyanstudios.com/musicfiles/2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com/15SecVersion2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com.mp3")!
return urls[0]
case 1:
return URL(string: "https://chtbl.com/track/18338/traffic.libsyn.com/secure/acquired/acquired_-_armrev_2.mp3?dest-id=376122")!
return urls[1]
case 2:
return URL(string: "https://backtracks.fm/ycombinator/pr/0f685f72-29b1-11e9-9bcf-0ece7a7d2472/111---jake-klamka-and-kevin-hale---y-combinator.mp3?s=1&amp;sd=1&amp;u=1549423185")!
return urls[2]
default:
return URL(string: "https://www.fesliyanstudios.com/musicfiles/2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com/15SecVersion2019-04-23_-_Trusted_Advertising_-_www.fesliyanstudios.com.mp3")!
return urls[0]
}
}
@@ -33,9 +33,9 @@ struct AudioInfo: Hashable {
case 0:
return "Soundbite"
case 1:
return "Acquired"
return "Podcast"
case 2:
return "Y Combinator"
return "Radio"
default:
return "Soundbite"
}
+1
View File
@@ -173,6 +173,7 @@ class AudioEngine: AudioEngineProtocol {
Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] (timer: Timer) in
guard let self = self else { return }
guard self.playingStatus != .ended else {
// Log.test("END TIMER")
self.delegate = nil
return
}
+14 -3
View File
@@ -71,6 +71,7 @@ class AudioStreamEngine: AudioEngine {
private var numberOfBuffersScheduledInTotal = 0 {
didSet {
// Log.test(numberOfBuffersScheduledInTotal)
Log.debug("number of buffers scheduled in total: \(numberOfBuffersScheduledInTotal)")
if numberOfBuffersScheduledInTotal == 0 {
pause()
@@ -86,6 +87,7 @@ class AudioStreamEngine: AudioEngine {
private var numberOfBuffersScheduledFromPoll = 0 {
didSet {
if numberOfBuffersScheduledFromPoll > MAX_POLL_BUFFER_COUNT {
// Log.test("🛑 🛑 STOP POLLING")
shouldPollForNextBuffer = false
}
@@ -149,7 +151,7 @@ class AudioStreamEngine: AudioEngine {
guard let self = self else { return }
guard self.playingStatus != .ended else { return }
self.pollForNextBufferRecursive()
self.pollForNextBuffer()
self.updateNetworkBufferRange()
self.updateNeedle()
self.updateIsPlaying()
@@ -162,9 +164,16 @@ 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 pollForNextBufferRecursive() {
private func pollForNextBuffer() {
guard shouldPollForNextBuffer else { return }
// Log.test("POLL INIT")
pollForNextBufferRecursive()
}
private func pollForNextBufferRecursive() {
// Log.test("POLL")
do {
var nextScheduledBuffer: AVAudioPCMBuffer! = try converter.pullBuffer()
numberOfBuffersScheduledFromPoll += 1
@@ -174,15 +183,17 @@ class AudioStreamEngine: AudioEngine {
queue.async { [weak self] in
if #available(iOS 11.0, *) {
// to make sure the pcm buffers are properly free'd from memory we need to nil them after the player has used them
self?.playerNode.scheduleBuffer(nextScheduledBuffer, completionCallbackType: .dataRendered, completionHandler: { (_) in
self?.playerNode.scheduleBuffer(nextScheduledBuffer, completionCallbackType: .dataConsumed, completionHandler: { (_) in
nextScheduledBuffer = nil
self?.numberOfBuffersScheduledInTotal -= 1
// Log.test("POLL DATA RENDERED")
self?.pollForNextBufferRecursive()
})
} else {
self?.playerNode.scheduleBuffer(nextScheduledBuffer) {
nextScheduledBuffer = nil
self?.numberOfBuffersScheduledInTotal -= 1
// Log.test("POLL OLD")
self?.pollForNextBufferRecursive()
}
}
+11 -33
View File
@@ -42,15 +42,6 @@ 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
@@ -102,7 +93,6 @@ class AudioThrottler: AudioThrottleable {
//Init
let url: AudioURL
weak var delegate: AudioThrottleDelegate?
private var state: State
private var networkData: [NetworkDataWrapper] = []
var shouldThrottle = false
@@ -120,16 +110,12 @@ 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 }
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)
guard let self = self else {return}
Log.debug("received stream data of size \(pto.getData().count) and progress: \(pto.getProgress())")
self.delegate?.didUpdate(networkStreamProgress: pto.getProgress())
if let totalBytesExpected = pto.getTotalBytesExpected() {
@@ -143,8 +129,7 @@ class AudioThrottler: AudioThrottleable {
self.networkData.append(wrappedNetworkData)
if !self.shouldThrottle {
self.state = .PUSH_DATA
Log.debug("sending up packet from stream untrottled at start: \(wrappedNetworkData.startOffset)", state: self.state.rawValue)
Log.debug("sending up packet from stream untrottled at start: \(wrappedNetworkData.startOffset)")
//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
@@ -158,7 +143,6 @@ 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) {
@@ -168,14 +152,14 @@ class AudioThrottler: AudioThrottleable {
}
func tellByteOffset(offset: UInt64) {
Log.debug("offset \(offset)", state: self.state.rawValue)
Log.debug("offset \(offset)")
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())", state: self.state.rawValue)
Log.debug("offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset) is next sent: \(wrappedNetworkData.isNextSent())")
if wrappedNetworkData.alreadySent {
Log.debug("already sent offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)", state: self.state.rawValue)
Log.debug("already sent offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
var bytesSent: UInt = 0
var current = wrappedNetworkData
@@ -185,16 +169,14 @@ class AudioThrottler: AudioThrottleable {
while bytesSent < largestPollingOffsetDifference {
if let next = current.next {
if !next.alreadySent {
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)
Log.info("Sending next network packet with range: \(next.startOffset) to \(next.endOffset), have sent \(bytesSent) bytes so far from \(largestPollingOffsetDifference) bytes")
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)", state: self.state.rawValue)
self.state = .END_OF_DATA
Log.debug("next package doesn't exist, bytes sent so far: \(bytesSent)")
return
}
}
@@ -202,19 +184,16 @@ class AudioThrottler: AudioThrottleable {
return
}
Log.info("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)", self.state.rawValue)
Log.info("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
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)", self.state.rawValue)
Log.info("seek with offset: \(offset)")
if networkData.count == 0 {
byteOffsetBecauseOfSeek = UInt(offset)
@@ -246,7 +225,7 @@ class AudioThrottler: AudioThrottleable {
d.alreadySent = false
wrappedData.alreadySent = true
Log.info("\(d) ::: \(wrappedData)", self.state.rawValue)
Log.info("\(d) ::: \(wrappedData)")
delegate?.shouldProcess(networkData: wrappedData.data)
return
@@ -263,6 +242,5 @@ class AudioThrottler: AudioThrottleable {
func invalidate() {
AudioDataManager.shared.deleteStream(withRemoteURL: url)
self.state = .CLEAN_UP
}
}
-49
View File
@@ -1,49 +0,0 @@
//
// 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#>
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

+14 -16
View File
@@ -23,8 +23,6 @@ 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() {}
@@ -44,11 +42,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, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func test(_ logMessage: Any, 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
@@ -65,16 +63,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, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func error(_ logMessage: Any, 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
if logLevel.rawValue <= LogLevel.EXTERNAL_DEBUG.rawValue {
let log = OSLog(subsystem: SUBSYSTEM, category: "WARNING")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
@@ -91,11 +89,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, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func monitor(_ logMessage: Any, 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
@@ -112,16 +110,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, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func warn(_ logMessage: Any, 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
if logLevel.rawValue <= LogLevel.EXTERNAL_DEBUG.rawValue {
let log = OSLog(subsystem: SUBSYSTEM, category: "DEBUG")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
@@ -138,11 +136,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, _ state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func info(_ logMessage: Any, 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
@@ -159,11 +157,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?..., state:State? = nil, classPath: String = #file, functionName: String = #function, lineNumber: Int = #line) {
public static func debug(_ logMessage: Any?..., 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, "\(state ?? "") \(logMessage)")
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioPlayer'
s.version = '4.1.0'
s.version = '4.2.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.