Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c325caa914 | |||
| dd54d81573 | |||
| ebc282d5c2 | |||
| 80ce253f92 | |||
| fe2395066f | |||
| 3e66b4b4d4 | |||
| 58bbc97a1b | |||
| 8d9e9d92f4 | |||
| 03392c21e0 |
@@ -212,12 +212,12 @@
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
DevelopmentTeam = R2392A68YQ;
|
||||
DevelopmentTeam = H9Y26B6GZB;
|
||||
LastSwiftMigration = 1120;
|
||||
};
|
||||
607FACE41AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
DevelopmentTeam = R2392A68YQ;
|
||||
DevelopmentTeam = H9Y26B6GZB;
|
||||
LastSwiftMigration = 1120;
|
||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||
};
|
||||
@@ -475,11 +475,11 @@
|
||||
baseConfigurationReference = 65A66AB4C3016E8BB53FF3E0 /* Pods-SwiftAudioPlayer_Example.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = R2392A68YQ;
|
||||
DEVELOPMENT_TEAM = H9Y26B6GZB;
|
||||
INFOPLIST_FILE = SwiftAudioPlayer/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo-test.SwiftAudioPlayer-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -490,11 +490,11 @@
|
||||
baseConfigurationReference = 4B5DD2AE0B23A759D18926DC /* Pods-SwiftAudioPlayer_Example.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = R2392A68YQ;
|
||||
DEVELOPMENT_TEAM = H9Y26B6GZB;
|
||||
INFOPLIST_FILE = SwiftAudioPlayer/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo-test.SwiftAudioPlayer-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -504,7 +504,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BBD877782CC67FBCC7BF7532 /* Pods-SwiftAudioPlayer_Tests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = R2392A68YQ;
|
||||
DEVELOPMENT_TEAM = H9Y26B6GZB;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
@@ -526,7 +526,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0B7D1E6C00E83B4AF8AA1781 /* Pods-SwiftAudioPlayer_Tests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = R2392A68YQ;
|
||||
DEVELOPMENT_TEAM = H9Y26B6GZB;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
|
||||
@@ -59,7 +59,7 @@ class ViewController: UIViewController {
|
||||
self.currentUrlLocationLabel.text = "remote url: \(selectedAudio.url.absoluteString)"
|
||||
}
|
||||
}
|
||||
|
||||
var freq:[Int] = [0,0,0,0,0,0,0,0,0,0]
|
||||
@IBOutlet weak var currentUrlLocationLabel: UILabel!
|
||||
@IBOutlet weak var bufferProgress: UIProgressView!
|
||||
@IBOutlet weak var scrubberSlider: UISlider!
|
||||
@@ -103,6 +103,8 @@ class ViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
SAPlayer.shared.DEBUG_MODE = true
|
||||
|
||||
isPlayable = false
|
||||
selectedAudio = AudioInfo(index: 0)
|
||||
|
||||
@@ -149,6 +151,8 @@ class ViewController: UIViewController {
|
||||
|
||||
if buffer.bufferingProgress >= 0.99 {
|
||||
self.streamButton.isEnabled = false
|
||||
} else {
|
||||
self.streamButton.isEnabled = true
|
||||
}
|
||||
|
||||
self.isPlayable = buffer.isReadyForPlaying
|
||||
@@ -183,6 +187,16 @@ class ViewController: UIViewController {
|
||||
let node = AVAudioUnitReverb()
|
||||
SAPlayer.shared.audioModifiers.append(node)
|
||||
node.wetDryMix = 300
|
||||
let frequency:[Int] = [60,170,310,600,1000,3000,6000,12000,14000,16000]
|
||||
let node2 = AVAudioUnitEQ(numberOfBands:frequency.count)
|
||||
node2.globalGain = 1
|
||||
for i in 0...(node2.bands.count-1) {
|
||||
node2.bands[i].frequency = Float(frequency[i])
|
||||
node2.bands[i].gain = 0
|
||||
node2.bands[i].bypass = false
|
||||
node2.bands[i].filterType = .parametric
|
||||
}
|
||||
SAPlayer.shared.audioModifiers.append(node2)
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
@@ -260,8 +274,12 @@ class ViewController: UIViewController {
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
streamButton.setTitle("Cancel streaming", for: .normal)
|
||||
downloadButton.isEnabled = false
|
||||
isStreaming = true
|
||||
} else {
|
||||
// TODO
|
||||
SAPlayer.shared.stopStreamingRemoteAudio()
|
||||
streamButton.setTitle("Stream", for: .normal)
|
||||
downloadButton.isEnabled = true
|
||||
isStreaming = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +294,19 @@ class ViewController: UIViewController {
|
||||
@IBAction func skipForwardTouched(_ sender: Any) {
|
||||
SAPlayer.shared.skipForward()
|
||||
}
|
||||
@IBAction func setEqualizerValue(_ sender: Any) {
|
||||
if let slider = sender as? UISlider{
|
||||
print("slider of index:", slider.tag, "is changed to", slider.value)
|
||||
freq[slider.tag] = Int(slider.value)
|
||||
print("current frequency : ",freq)
|
||||
if let node = SAPlayer.shared.audioModifiers[2] as? AVAudioUnitEQ{
|
||||
for i in 0...(node.bands.count - 1){
|
||||
node.bands[i].gain = Float(freq[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class AudioDiskEngine: AudioEngine {
|
||||
}
|
||||
|
||||
override func invalidate() {
|
||||
super.invalidate()
|
||||
//Nothing to invalidate for disk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,11 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
func updateIsPlaying() {
|
||||
if !bufferedSeconds.isPlayable {
|
||||
playingStatus = .buffering
|
||||
if bufferedSeconds.bufferingProgress > 0.999 {
|
||||
playingStatus = .ended
|
||||
} else {
|
||||
playingStatus = .buffering
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -236,12 +236,6 @@ class AudioStreamEngine: AudioEngine {
|
||||
var currentTime = TimeInterval(playerTime.sampleTime) / playerTime.sampleRate
|
||||
currentTime = currentTime > 0 ? currentTime : 0
|
||||
|
||||
if currentTime > predictedStreamDuration {
|
||||
Log.info("reached end of audio")
|
||||
seek(toNeedle: 0)
|
||||
pause()
|
||||
playingStatus = .ended
|
||||
}
|
||||
needle = (currentTime + currentTimeOffset)
|
||||
}
|
||||
|
||||
@@ -297,6 +291,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
}
|
||||
|
||||
override func invalidate() {
|
||||
super.invalidate()
|
||||
converter.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,28 +57,39 @@ public enum ConverterError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .cannotLockQueue:
|
||||
Log.warn("Failed to lock queue")
|
||||
return "Failed to lock queue"
|
||||
case .converterFailed(let status):
|
||||
Log.warn(localizedDescriptionFromConverterError(status))
|
||||
return localizedDescriptionFromConverterError(status)
|
||||
case .failedToCreateDestinationFormat:
|
||||
Log.warn("Failed to create a destination (processing) format")
|
||||
return "Failed to create a destination (processing) format"
|
||||
case .failedToCreatePCMBuffer:
|
||||
Log.warn("Failed to create PCM buffer for reading data")
|
||||
return "Failed to create PCM buffer for reading data"
|
||||
case .notEnoughData:
|
||||
Log.warn("Not enough data for read-conversion operation")
|
||||
return "Not enough data for read-conversion operation"
|
||||
case .parserMissingDataFormat:
|
||||
Log.warn("Parser is missing a valid data format")
|
||||
return "Parser is missing a valid data format"
|
||||
case .reachedEndOfFile:
|
||||
Log.warn("Reached the end of the file")
|
||||
return "Reached the end of the file"
|
||||
case .unableToCreateConverter(let status):
|
||||
return localizedDescriptionFromConverterError(status)
|
||||
case .superConcerningShouldNeverHappen:
|
||||
Log.warn("Weird unexpected reader error. Should not have happened")
|
||||
return "Weird unexpected reader error. Should not have happened"
|
||||
case .cannotCreatePCMBufferWithoutConverter:
|
||||
Log.warn("Could not create a PCM Buffer because reader does not have a converter yet")
|
||||
return "Could not create a PCM Buffer because reader does not have a converter yet"
|
||||
case .throttleParsingBuffersForEngine:
|
||||
Log.warn("Preventing the reader from creating more PCM buffers since the player has more than 60 seconds of audio already to play")
|
||||
return "Preventing the reader from creating more PCM buffers since the player has more than 60 seconds of audio already to play"
|
||||
case .failedToCreateParser:
|
||||
Log.warn("Could not create a parser")
|
||||
return "Could not create a parser"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
}
|
||||
|
||||
guard self.task == dataTask else {
|
||||
Log.error("stream_error not the same task") //Probably because of seek
|
||||
Log.error("stream_error not the same task 638283") //Probably because of seek
|
||||
return
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
}
|
||||
|
||||
guard self.task == dataTask else {
|
||||
Log.error("stream_error not the same task")
|
||||
Log.error("stream_error not the same task 517253")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -293,8 +293,8 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
guard self.task == task else {
|
||||
Log.error("stream_error not the same task")
|
||||
if self.task != task && self.task != nil {
|
||||
Log.error("stream_error not the same task 3901833")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@ import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public class SAPlayer {
|
||||
public var DEBUG_MODE: Bool = false {
|
||||
didSet {
|
||||
if(DEBUG_MODE) {
|
||||
logLevel = LogLevel.EXTERNAL_DEBUG
|
||||
} else {
|
||||
logLevel = LogLevel.MONITOR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Access to the player.
|
||||
*/
|
||||
@@ -302,6 +312,10 @@ extension SAPlayer {
|
||||
presenter.handlePlayStreamedAudio(withRemoteUrl: url)
|
||||
}
|
||||
|
||||
public func stopStreamingRemoteAudio() {
|
||||
presenter.handleStopStreamingAudio()
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the player to the state before initializing audio and setting media info.
|
||||
*/
|
||||
@@ -326,6 +340,12 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
player = AudioStreamEngine(withRemoteUrl: url, delegate: presenter)
|
||||
}
|
||||
|
||||
func clearEngine() {
|
||||
player?.pause()
|
||||
player?.invalidate()
|
||||
player = nil
|
||||
}
|
||||
|
||||
func playEngine() {
|
||||
becomeDeviceAudioPlayer()
|
||||
player?.play()
|
||||
|
||||
@@ -32,6 +32,7 @@ protocol SAPlayerDelegate: AnyObject, LockScreenViewProtocol {
|
||||
|
||||
func startAudioDownloaded(withSavedUrl url: AudioURL)
|
||||
func startAudioStreamed(withRemoteUrl url: AudioURL)
|
||||
func clearEngine()
|
||||
func playEngine()
|
||||
func pauseEngine()
|
||||
func seekEngine(toNeedle needle: Needle) //TODO ensure that engine cleans up out of bounds
|
||||
|
||||
@@ -83,9 +83,7 @@ class SAPlayerPresenter {
|
||||
}
|
||||
|
||||
private func attachForUpdates(url: URL) {
|
||||
AudioClockDirector.shared.detachFromChangesInDuration(withID: durationRef)
|
||||
AudioClockDirector.shared.detachFromChangesInNeedle(withID: needleRef)
|
||||
AudioClockDirector.shared.detachFromChangesInPlayingStatus(withID: playingStatusRef)
|
||||
detachFromUpdates()
|
||||
|
||||
self.key = url.key
|
||||
urlKeyMap[url.key] = url
|
||||
@@ -125,6 +123,17 @@ class SAPlayerPresenter {
|
||||
})
|
||||
}
|
||||
|
||||
private func detachFromUpdates() {
|
||||
AudioClockDirector.shared.detachFromChangesInDuration(withID: durationRef)
|
||||
AudioClockDirector.shared.detachFromChangesInNeedle(withID: needleRef)
|
||||
AudioClockDirector.shared.detachFromChangesInPlayingStatus(withID: playingStatusRef)
|
||||
}
|
||||
|
||||
func handleStopStreamingAudio() {
|
||||
delegate?.clearEngine()
|
||||
detachFromUpdates()
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
func handleLockscreenInfo(info: SALockScreenInfo?) {
|
||||
self.mediaInfo = info
|
||||
|
||||
+25
-14
@@ -9,22 +9,23 @@
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Possible levels of log messages to log
|
||||
enum LogLevel: Int {
|
||||
case DEBUG = 1
|
||||
case INFO = 2
|
||||
case WARN = 3
|
||||
case ERROR = 4
|
||||
case EXTERNAL_DEBUG = 5
|
||||
case MONITOR = 6
|
||||
case TEST = 7
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
class Log {
|
||||
private init() {}
|
||||
|
||||
// Possible levels of log messages to log
|
||||
public enum LogLevel: Int {
|
||||
case DEBUG = 1
|
||||
case INFO = 2
|
||||
case WARN = 3
|
||||
case ERROR = 4
|
||||
case MONITOR = 5
|
||||
case TEST = 6
|
||||
}
|
||||
|
||||
// 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.
|
||||
public static var logLevel: LogLevel = LogLevel.MONITOR
|
||||
|
||||
// Used for OSLog
|
||||
private static let SUBSYSTEM: String = "com.SwiftAudioPlayer"
|
||||
|
||||
@@ -68,6 +69,11 @@ class Log {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "ERROR 🛑🛑🛑🛑")
|
||||
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, "\(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +92,7 @@ class Log {
|
||||
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: "MONITOR 🔥🔥🔥🔥")
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "ERROR 🔥🔥🔥🔥")
|
||||
os_log("%@:%@:%d:: %@", log: log, fileName, functionName, lineNumber, "\(logMessage)")
|
||||
}
|
||||
}
|
||||
@@ -110,6 +116,11 @@ class Log {
|
||||
let log = OSLog(subsystem: SUBSYSTEM, category: "WARN ⚠️⚠️⚠️⚠️")
|
||||
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, "\(logMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '2.7.0'
|
||||
s.version = '2.9.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