Compare commits

..

12 Commits

Author SHA1 Message Date
tanhakabir 52ea5a21b1 Merge branch 'issue-106' of https://github.com/tanhakabir/SwiftAudioPlayer into issue-106 2021-05-07 13:18:17 -07:00
tanhakabir a83c2f702f pass lockscreen info to player in example app (#107)
related to #106
2021-05-07 13:16:24 -07:00
tanhakabir c2cab7b272 pass lockscreen info to player in example app 2021-05-07 13:15:48 -07:00
tanhakabir 8644bf24fb Merge pull request #105 from Husseinhj/patch-1
Anchor link in SAPlayer.Updates fixed
2021-05-03 20:42:19 -07:00
tanhakabir 69a979cb98 Add twitter handle to podspec 2021-05-03 20:40:57 -07:00
Hussein Habibi Juybari 6ba43e70ea Rollback Contact header changes 2021-05-03 11:34:30 +04:30
Hussein Habibi Juybari 6f19009000 Anchor link in SAPlayer.Updates fixed 2021-05-03 11:32:30 +04:30
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
7 changed files with 73 additions and 37 deletions
+7
View File
@@ -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
+2 -1
View File
@@ -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
+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)")
+2 -2
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.
@@ -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'