Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d96cc07b2 | |||
| 17e0ee5dd8 | |||
| 9344066468 | |||
| 0fee2b32e5 | |||
| 97909bacce | |||
| 30b0189f61 | |||
| 5bde849bf0 | |||
| b3b519ab4c | |||
| f3b62cc756 | |||
| a56d3314ad | |||
| f75d743cd9 | |||
| f8876d821e |
+4
@@ -17,6 +17,7 @@
|
||||
A40DBE292391D9CA00F86146 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40DBE282391D9C900F86146 /* Data.swift */; };
|
||||
A411CE4625F9609D0039E1CD /* SAPlayerFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */; };
|
||||
A41AA0D2238BB9B600A467E1 /* SAPlayingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = A41AA0D1238BB9B600A467E1 /* SAPlayingStatus.swift */; };
|
||||
A42157B22609BF8900253D01 /* SAPlayerNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42157B12609BF8900253D01 /* SAPlayerNetwork.swift */; };
|
||||
A4681FC6220113880018AB51 /* SAPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F8D2200E00E0018AB51 /* SAPlayer.swift */; };
|
||||
A4681FC72201138B0018AB51 /* SAPlayerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F912200E1950018AB51 /* SAPlayerDelegate.swift */; };
|
||||
A4681FC82201138E0018AB51 /* SAPlayerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F8F2200E1450018AB51 /* SAPlayerPresenter.swift */; };
|
||||
@@ -103,6 +104,7 @@
|
||||
A40DBE282391D9C900F86146 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.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>"; };
|
||||
A42157B12609BF8900253D01 /* SAPlayerNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerNetwork.swift; sourceTree = "<group>"; };
|
||||
A4523BC8220A0B3C0079C4BC /* Credited_LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = Credited_LICENSE; sourceTree = "<group>"; };
|
||||
A4681F802200D0500018AB51 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
||||
A4681F822200D9150018AB51 /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = "<group>"; };
|
||||
@@ -361,6 +363,7 @@
|
||||
A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */,
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */,
|
||||
A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */,
|
||||
A42157B12609BF8900253D01 /* SAPlayerNetwork.swift */,
|
||||
A4681F912200E1950018AB51 /* SAPlayerDelegate.swift */,
|
||||
A4681F8F2200E1450018AB51 /* SAPlayerPresenter.swift */,
|
||||
A4681FBE22010ECF0018AB51 /* LockScreenViewProtocol.swift */,
|
||||
@@ -548,6 +551,7 @@
|
||||
A4681FE1220113E70018AB51 /* Constants.swift in Sources */,
|
||||
A40DBE292391D9CA00F86146 /* Data.swift in Sources */,
|
||||
A4FBA6B5221B74C900D5A353 /* SALockScreenInfo.swift in Sources */,
|
||||
A42157B22609BF8900253D01 /* SAPlayerNetwork.swift in Sources */,
|
||||
A4681FC6220113880018AB51 /* SAPlayer.swift in Sources */,
|
||||
A4FBA6B7221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift in Sources */,
|
||||
A4681FC72201138B0018AB51 /* SAPlayerDelegate.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"/>
|
||||
|
||||
@@ -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&sd=1&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&sd=1&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"
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class ViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
SAPlayer.Downloader.allowUsingCellularData = true
|
||||
SAPlayer.Network.CellularData.allowUsage = true
|
||||
|
||||
// SAPlayer.shared.DEBUG_MODE = true
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,8 +29,6 @@ protocol AudioDataManagable {
|
||||
var numberOfQueued: Int { get }
|
||||
var numberOfActive: Int { get }
|
||||
|
||||
var allowCellular: Bool { get set }
|
||||
|
||||
func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ())
|
||||
func setAllowCellularDownloadPreference(_ preference: Bool)
|
||||
|
||||
@@ -53,6 +51,7 @@ protocol AudioDataManagable {
|
||||
|
||||
class AudioDataManager: AudioDataManagable {
|
||||
var allowCellular: Bool = true
|
||||
var userAgent: String?
|
||||
|
||||
static let shared: AudioDataManagable = AudioDataManager()
|
||||
|
||||
@@ -104,6 +103,10 @@ class AudioDataManager: AudioDataManagable {
|
||||
allowCellular = preference
|
||||
}
|
||||
|
||||
func setUserAgent(_ userAgent: String) {
|
||||
self.userAgent = userAgent
|
||||
}
|
||||
|
||||
func attach(callback: @escaping (_ id: ID, _ progress: Double)->()) {
|
||||
globalDownloadProgressCallback = callback
|
||||
}
|
||||
@@ -128,7 +131,7 @@ extension AudioDataManager {
|
||||
|
||||
downloadWorker.stop(withID: url.key) { [weak self] (fetchedData: Data?, totalBytesExpected: Int64?) in
|
||||
self?.downloadWorker.pauseAllActive()
|
||||
self?.streamWorker.start(withID: url.key, withRemoteURL: url, withInitialData: fetchedData, andTotalBytesExpectedPreviously: totalBytesExpected)
|
||||
self?.streamWorker.start(withID: url.key, withRemoteURL: url, withInitialData: fetchedData, andTotalBytesExpectedPreviously: totalBytesExpected, withUserAgent: userAgent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ import Foundation
|
||||
protocol AudioDataStreamable {
|
||||
//if user taps download then starts to stream
|
||||
init(progressCallback: @escaping (_ id: ID, _ dto: StreamProgressDTO) -> (), doneCallback: @escaping (_ id: ID, _ error: Error?)->Bool) //Bool is should save or not
|
||||
func start(withID id: ID, withRemoteURL url: URL, withInitialData data: Data?, andTotalBytesExpectedPreviously previousTotalBytesExpected: Int64?)
|
||||
func start(withID id: ID, withRemoteURL url: URL, withInitialData data: Data?, andTotalBytesExpectedPreviously previousTotalBytesExpected: Int64?, withUserAgent userAgent: String?)
|
||||
func pause(withId id: ID)
|
||||
func resume(withId id: ID)
|
||||
func stop(withId id: ID)//FIXME: with persistent play we should return a Data so that download can resume
|
||||
@@ -95,7 +95,7 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
self.session = URLSession(configuration: config, delegate: self, delegateQueue: nil) //TODO: should we use ephemeral
|
||||
}
|
||||
|
||||
func start(withID id: ID, withRemoteURL url: URL, withInitialData data: Data? = nil, andTotalBytesExpectedPreviously previousTotalBytesExpected: Int64? = nil) {
|
||||
func start(withID id: ID, withRemoteURL url: URL, withInitialData data: Data? = nil, andTotalBytesExpectedPreviously previousTotalBytesExpected: Int64? = nil, withUserAgent userAgent: String?) {
|
||||
Log.info("selfID: \(self.id ?? "none"), paramID: \(id) initialData: \(data?.count ?? 0)")
|
||||
|
||||
killPreviousTaskIfNeeded()
|
||||
|
||||
@@ -100,14 +100,5 @@ extension SAPlayer {
|
||||
public static func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ()) {
|
||||
AudioDataManager.shared.setBackgroundCompletionHandler(completionHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
Whether downloading audio on cellular data is allowed. By default this is set to `true`.
|
||||
*/
|
||||
public static var allowUsingCellularData = true {
|
||||
didSet {
|
||||
AudioDataManager.shared.setAllowCellularDownloadPreference(allowUsingCellularData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// SAPlayerNetwork.swift
|
||||
// SwiftAudioPlayer
|
||||
//
|
||||
// Created by Tanha Kabir on 3/22/21.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension SAPlayer {
|
||||
/**
|
||||
Actions relating to remote requests being made to download or stream audio data.
|
||||
*/
|
||||
public struct Network {
|
||||
|
||||
/**
|
||||
Handle permissions for downloading and/or streaming on cellular data.
|
||||
*/
|
||||
public struct CellularData {
|
||||
/**
|
||||
Whether downloading audio on cellular data is allowed. By default this is set to `true`.
|
||||
*/
|
||||
public static var allowUsage = true {
|
||||
didSet {
|
||||
AudioDataManager.shared.setAllowCellularDownloadPreference(allowUsage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Request {
|
||||
public func setUserAgent(with userAgent: String) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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