Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52ea5a21b1 | |||
| a83c2f702f | |||
| c2cab7b272 | |||
| 8644bf24fb | |||
| 69a979cb98 | |||
| 6ba43e70ea | |||
| 6f19009000 | |||
| 64677ad6ce | |||
| 3894309706 | |||
| e44f16258f | |||
| 1e3cf35b7b | |||
| 4bfb3f1774 |
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftAudioPlayer
|
||||
|
||||
struct AudioInfo: Hashable {
|
||||
var index: Int = 0
|
||||
@@ -44,6 +45,12 @@ struct AudioInfo: Hashable {
|
||||
let artist: String = "SwiftAudioPlayer Sample App"
|
||||
let releaseDate: Int = 1550790640
|
||||
|
||||
var lockscreenInfo: SALockScreenInfo {
|
||||
get {
|
||||
return SALockScreenInfo(title: self.title, artist: self.artist, artwork: nil, releaseDate: self.releaseDate)
|
||||
}
|
||||
}
|
||||
|
||||
var savedUrl: URL? {
|
||||
get {
|
||||
return savedUrls[index]
|
||||
|
||||
@@ -295,7 +295,7 @@ class ViewController: UIViewController {
|
||||
self.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
|
||||
self.selectedAudio.addSavedUrl(url)
|
||||
|
||||
SAPlayer.shared.startSavedAudio(withSavedUrl: url)
|
||||
SAPlayer.shared.startSavedAudio(withSavedUrl: url, mediaInfo: self.selectedAudio.lockscreenInfo)
|
||||
self.lastPlayedAudioIndex = self.selectedAudio.index
|
||||
}
|
||||
})
|
||||
@@ -312,9 +312,9 @@ class ViewController: UIViewController {
|
||||
@IBAction func streamTouched(_ sender: Any) {
|
||||
if !isStreaming {
|
||||
if selectedAudio.index == 2 { // radio
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, bitrate: .low)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, bitrate: .low, mediaInfo: selectedAudio.lockscreenInfo)
|
||||
} else {
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url, mediaInfo: selectedAudio.lockscreenInfo)
|
||||
}
|
||||
|
||||
lastPlayedAudioIndex = selectedAudio.index
|
||||
|
||||
@@ -88,7 +88,7 @@ override func viewDidLoad() {
|
||||
}
|
||||
}
|
||||
```
|
||||
Look at the [Updates](#SAPlayer.Updates) section to see usage details and other updates to follow.
|
||||
Look at the [Updates](#saplayerupdates) section to see usage details and other updates to follow.
|
||||
|
||||
|
||||
For realtime audio manipulations, [AVAudioUnit](https://developer.apple.com/documentation/avfoundation/avaudiounit) nodes are used. For example to adjust the reverb through a slider in the UI:
|
||||
@@ -113,6 +113,7 @@ For a more detailed explanation on usage, look at the [Realtime Audio Manipulati
|
||||
|
||||
For more details and specifics look at the [API documentation](#api-in-detail) below.
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
### Issues
|
||||
|
||||
@@ -105,7 +105,7 @@ class AudioParser: AudioParsable {
|
||||
|
||||
var sumOfParsedAudioBytes:UInt32 = 0
|
||||
var numberOfPacketsParsed:UInt32 = 0
|
||||
var audioPackets: [(AudioStreamPacketDescription?,Data)] = [] {
|
||||
var audioPackets: [(AudioStreamPacketDescription?,Data)] = [] {
|
||||
didSet {
|
||||
if let audioPacketByteSize = audioPackets.last?.0?.mDataByteSize {
|
||||
sumOfParsedAudioBytes += audioPacketByteSize
|
||||
@@ -118,6 +118,7 @@ class AudioParser: AudioParsable {
|
||||
//TODO: duration will not be accurate with WAV or AIFF
|
||||
}
|
||||
}
|
||||
private let lockQueue = DispatchQueue(label: "SwiftAudioPlayer.Parser.packets.lock")
|
||||
var lastSentAudioPacketIndex = -1
|
||||
|
||||
/**
|
||||
@@ -152,21 +153,23 @@ class AudioParser: AudioParsable {
|
||||
self.framesPerBuffer = bufferSize
|
||||
self.parsedFileAudioFormatCallback = parsedFileAudioFormatCallback
|
||||
|
||||
self.throttler = AudioThrottler(withRemoteUrl: url, withDelegate: self)
|
||||
|
||||
streamChangeListenerId = StreamingDownloadDirector.shared.attach { [weak self] (key, progress) in
|
||||
guard let self = self else { return }
|
||||
guard key == url.key else { return }
|
||||
self.networkProgress = progress
|
||||
|
||||
// initially parse a bunch of packets
|
||||
if self.fileAudioFormat == nil {
|
||||
self.processNextDataPacket()
|
||||
} else if self.audioPackets.count - self.lastSentAudioPacketIndex < self.MIN_PACKETS_TO_HAVE_AVAILABLE_BEFORE_THROTTLING_PARSING {
|
||||
self.processNextDataPacket()
|
||||
self.lockQueue.sync {
|
||||
if self.fileAudioFormat == nil {
|
||||
self.processNextDataPacket()
|
||||
} else if self.audioPackets.count - self.lastSentAudioPacketIndex < self.MIN_PACKETS_TO_HAVE_AVAILABLE_BEFORE_THROTTLING_PARSING {
|
||||
self.processNextDataPacket()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.throttler = AudioThrottler(withRemoteUrl: url, withDelegate: self)
|
||||
|
||||
let context = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
|
||||
//Open the stream and when we call parse data is fed into this stream
|
||||
guard AudioFileStreamOpen(context, ParserPropertyListener, ParserPacketListener, kAudioFileMP3Type, &streamID) == noErr else {
|
||||
@@ -187,31 +190,48 @@ class AudioParser: AudioParsable {
|
||||
// 1. We've reached the end of the packet data and the file has been completely parsed
|
||||
// 2. We've reached the end of the data we currently have downloaded, but not the file
|
||||
let packetIndex = index - indexSeekOffset
|
||||
let isEndOfData = packetIndex >= audioPackets.count
|
||||
if isEndOfData {
|
||||
if isParsingComplete {
|
||||
throw ParserError.readerAskingBeyondEndOfFile
|
||||
} else {
|
||||
Log.debug("Tried to pull packet at index: \(packetIndex) when only have: \(audioPackets.count), we predict \(totalPredictedPacketCount) in total")
|
||||
throw ParserError.notEnoughDataForReader
|
||||
}
|
||||
}
|
||||
|
||||
lastSentAudioPacketIndex = Int(packetIndex)
|
||||
return audioPackets[Int(packetIndex)]
|
||||
var exception: ParserError? = nil
|
||||
var packet: (AudioStreamPacketDescription?, Data) = (nil, Data())
|
||||
lockQueue.sync {
|
||||
if packetIndex >= self.audioPackets.count {
|
||||
if isParsingComplete {
|
||||
exception = ParserError.readerAskingBeyondEndOfFile
|
||||
return
|
||||
} else {
|
||||
Log.debug("Tried to pull packet at index: \(packetIndex) when only have: \(self.audioPackets.count), we predict \(self.totalPredictedPacketCount) in total")
|
||||
exception = ParserError.notEnoughDataForReader
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lastSentAudioPacketIndex = Int(packetIndex)
|
||||
packet = audioPackets[Int(packetIndex)]
|
||||
}
|
||||
if let exception = exception {
|
||||
throw exception
|
||||
} else {
|
||||
return packet
|
||||
}
|
||||
}
|
||||
|
||||
private func determineIfMoreDataNeedsToBeParsed(index: AVAudioPacketCount) {
|
||||
if index > audioPackets.count - MIN_PACKETS_TO_HAVE_AVAILABLE_BEFORE_THROTTLING_PARSING {
|
||||
processNextDataPacket()
|
||||
lockQueue.sync {
|
||||
if index > self.audioPackets.count - self.MIN_PACKETS_TO_HAVE_AVAILABLE_BEFORE_THROTTLING_PARSING {
|
||||
self.processNextDataPacket()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tellSeek(toIndex index: AVAudioPacketCount) {
|
||||
//Already within the processed audio packets. Ignore
|
||||
if indexSeekOffset <= index && index < audioPackets.count + Int(indexSeekOffset) {
|
||||
return
|
||||
var isIndexValid: Bool = true
|
||||
lockQueue.sync {
|
||||
if self.indexSeekOffset <= index && index < self.audioPackets.count + Int(self.indexSeekOffset) {
|
||||
isIndexValid = false
|
||||
}
|
||||
}
|
||||
guard isIndexValid else { return }
|
||||
|
||||
guard let byteOffset = getOffset(fromPacketIndex: index) else {
|
||||
return
|
||||
@@ -223,10 +243,12 @@ class AudioParser: AudioParsable {
|
||||
// NOTE: Order matters. Need to prevent appending to the array before we clean it. Just in case
|
||||
// then we tell the throttler to send us appropriate packet
|
||||
shouldPreventPacketFromFillingUp = true
|
||||
audioPackets = []
|
||||
lockQueue.sync {
|
||||
self.audioPackets = []
|
||||
}
|
||||
|
||||
throttler.tellSeek(offset: byteOffset)
|
||||
processNextDataPacket()
|
||||
self.processNextDataPacket()
|
||||
}
|
||||
|
||||
private func getOffset(fromPacketIndex index: AVAudioPacketCount) -> UInt64? {
|
||||
@@ -279,6 +301,12 @@ class AudioParser: AudioParsable {
|
||||
return Needle(TimeInterval(frame)/TimeInterval(frameCount)*duration)
|
||||
}
|
||||
|
||||
func append(description: AudioStreamPacketDescription?, data: Data) {
|
||||
lockQueue.sync {
|
||||
self.audioPackets.append((description, data))
|
||||
}
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
throttler.invalidate()
|
||||
|
||||
@@ -311,7 +339,9 @@ class AudioParser: AudioParsable {
|
||||
guard let self = self else { return }
|
||||
guard let data = d else { return }
|
||||
|
||||
Log.debug("processing data count: \(data.count) :: already had \(self.audioPackets.count) audio packets")
|
||||
self.lockQueue.sync {
|
||||
Log.debug("processing data count: \(data.count) :: already had \(self.audioPackets.count) audio packets")
|
||||
}
|
||||
self.shouldPreventPacketFromFillingUp = false
|
||||
do {
|
||||
let sID = self.streamID!
|
||||
|
||||
@@ -65,7 +65,7 @@ func parserPacket(_ context: UnsafeMutableRawPointer, _ byteCount: UInt32, _ pac
|
||||
let audioPacketStart = Int(audioPacketDescription.mStartOffset)
|
||||
let audioPacketSize = Int(audioPacketDescription.mDataByteSize)
|
||||
let audioPacketData = Data(bytes: streamData.advanced(by: audioPacketStart), count: audioPacketSize)
|
||||
selfAudioParser.audioPackets.append((audioPacketDescription,audioPacketData))
|
||||
selfAudioParser.append(description: audioPacketDescription, data: audioPacketData)
|
||||
}
|
||||
} else { // not compressed audio (.wav)
|
||||
Log.debug("uncompressed audio")
|
||||
@@ -75,7 +75,7 @@ func parserPacket(_ context: UnsafeMutableRawPointer, _ byteCount: UInt32, _ pac
|
||||
let audioPacketStart = i * bytesPerAudioPacket
|
||||
let audioPacketSize = bytesPerAudioPacket
|
||||
let audioPacketData = Data(bytes: streamData.advanced(by: audioPacketStart), count: audioPacketSize)
|
||||
selfAudioParser.audioPackets.append((nil, audioPacketData))
|
||||
selfAudioParser.append(description: nil, data: audioPacketData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -523,12 +523,10 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
private func becomeDeviceAudioPlayer() {
|
||||
do {
|
||||
if #available(iOS 11.0, *) {
|
||||
// try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, policy: .longForm, options: [])
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, policy: .longFormAudio, options: [])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode(rawValue: convertFromAVAudioSessionMode(AVAudioSession.Mode.default)), options: .allowAirPlay)
|
||||
}
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode(rawValue: convertFromAVAudioSessionMode(AVAudioSession.Mode.default)), options: .allowAirPlay)
|
||||
|
||||
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
|
||||
} catch {
|
||||
Log.monitor("Problem setting up AVAudioSession to play in:: \(error.localizedDescription)")
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '5.0.1'
|
||||
s.version = '5.0.3'
|
||||
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.
|
||||
@@ -26,7 +26,7 @@ SwiftAudioPlayer is a Swift based audio player that can handle streaming from a
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'tanhakabir' => 'tanhakabir.ca@gmail.com', 'JonMercer' => 'mercer.jon@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/tanhakabir/SwiftAudioPlayer.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
s.social_media_url = 'https://twitter.com/_tanhakabir'
|
||||
|
||||
s.ios.deployment_target = '10.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user