Compare commits

...

5 Commits

Author SHA1 Message Date
tanhakabir 64677ad6ce Release 5.0.3 2021-04-28 15:33:46 -07:00
tanhakabir 3894309706 Merge pull request #102 from niczyja/session-fix
Fix for audio session setup on iOS 11 and up
2021-04-26 09:41:07 -07:00
Maciej Sienkiewicz e44f16258f Fix for audio session setup on iOS 11 and up 2021-04-26 14:43:13 +02:00
tanhakabir 1e3cf35b7b Release 5.0.2 2021-04-21 10:06:11 -07:00
tanhakabir 4bfb3f1774 fix packet parsing crash by putting audiopacket actions on a lock
close #94

Co-Authored-By: fayinsky <38639193+fayinsky@users.noreply.github.com>
2021-04-21 10:05:39 -07:00
4 changed files with 60 additions and 32 deletions
+55 -25
View File
@@ -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)
}
}
+2 -4
View File
@@ -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)")
+1 -1
View File
@@ -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.