Compare commits

...

4 Commits

Author SHA1 Message Date
Tanha 94c1a47641 Release 1.1.0 2019-11-20 16:36:58 -08:00
tanhakabir d0296ab012 Fix issue on streaming where it gets stuck in paused state and error of no more data to parse (#8)
* switch out audio clip for soundbite

* Fix being stuck in state of needing more data from the throttler
2019-11-20 16:35:31 -08:00
tanhakabir 2fd944d88e Fix play/pausing issue for saved audio (#7)
* update callback guards for updates for saved audio

* Fix play/pausing bug for saved audio
2019-11-20 13:38:52 -08:00
tanhakabir fc98c4c1c4 add separation of disk engine in SAPlayer, first iteration (#6) 2019-11-19 16:25:29 -08:00
9 changed files with 79 additions and 33 deletions
@@ -65,7 +65,7 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="joK-xi-MCo">
<rect key="frame" x="16" y="80" width="343" height="29"/>
<segments>
<segment title="20k Hertz"/>
<segment title="Soundbite"/>
<segment title="Acquired"/>
<segment title="Y Combinator"/>
</segments>
+18 -13
View File
@@ -10,32 +10,32 @@ import UIKit
import SwiftAudioPlayer
class ViewController: UIViewController {
struct AudioInfo {
struct AudioInfo: Hashable {
let index: Int
var url: URL {
switch index {
case 0:
return URL(string: "https://traffic.megaphone.fm/TTH7630150098.mp3")!
return URL(string: "https://cdn.fastlearner.media/bensound-rumble.mp3")!
case 1:
return URL(string: "https://chtbl.com/track/18338/traffic.libsyn.com/secure/acquired/acquired_-_armrev_2.mp3?dest-id=376122")!
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&amp;sd=1&amp;u=1549423185")!
default:
return URL(string: "https://traffic.megaphone.fm/TTH7630150098.mp3")!
return URL(string: "https://cdn.fastlearner.media/bensound-rumble.mp3")!
}
}
var title: String {
switch index {
case 0:
return "Twenty Thousand Hertz"
return "Soundbite"
case 1:
return "Acquired"
case 2:
return "Y Combinator"
default:
return "Twenty Thousand Hertz"
return "Soundbite"
}
}
@@ -43,6 +43,8 @@ class ViewController: UIViewController {
let releaseDate: Int = 1550790640
}
var savedUrls: [AudioInfo: URL] = [:]
var selectedAudio: AudioInfo = AudioInfo(index: 0) {
didSet {
if SAPlayer.Downloader.isDownloaded(withRemoteUrl: selectedAudio.url) {
@@ -104,14 +106,15 @@ class ViewController: UIViewController {
_ = SAPlayer.Updates.Duration.subscribe { [weak self] (url, duration) in
guard let self = self else { return }
guard url == self.selectedAudio.url else { return }
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
self.durationLabel.text = SAPlayer.prettifyTimestamp(duration)
self.duration = duration
}
_ = SAPlayer.Updates.ElapsedTime.subscribe { [weak self] (url, position) in
guard let self = self else { return }
guard url == self.selectedAudio.url else { return }
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
guard self.duration != 0 else { return }
@@ -134,7 +137,7 @@ class ViewController: UIViewController {
_ = SAPlayer.Updates.StreamingBuffer.subscribe{ [weak self] (url, buffer) in
guard let self = self else { return }
guard url == self.selectedAudio.url else { return }
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
if self.duration == 0.0 { return }
@@ -151,7 +154,7 @@ class ViewController: UIViewController {
_ = SAPlayer.Updates.PlayingStatus.subscribe { [weak self] (url, playing) in
guard let self = self else { return }
guard url == self.selectedAudio.url else { return }
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
if playing {
self.playPauseButton.setTitle("Pause", for: .normal)
@@ -172,6 +175,8 @@ class ViewController: UIViewController {
selectedAudio = AudioInfo(index: selected)
SAPlayer.shared.mediaInfo = SALockScreenInfo(title: selectedAudio.title, artist: selectedAudio.artist, artwork: UIImage(), releaseDate: selectedAudio.releaseDate)
// if let savedUrl = savedUrls[selectedAudio] {}
}
@IBAction func scrubberSeeked(_ sender: Any) {
@@ -194,12 +199,12 @@ class ViewController: UIViewController {
downloadButton.setTitle("Cancel 0%", for: .normal)
isDownloading = true
SAPlayer.Downloader.downloadAudio(withRemoteUrl: selectedAudio.url, completion: { [weak self] url in
guard let self = self else { return }
DispatchQueue.main.async {
self?.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
self.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
self.savedUrls[self.selectedAudio] = url
if let selectedUrl = self?.selectedAudio.url {
SAPlayer.shared.initializeAudio(withRemoteUrl: selectedUrl)
}
SAPlayer.shared.initializeSavedAudio(withSavedUrl: url)
}
})
streamButton.isEnabled = false
+33 -5
View File
@@ -47,6 +47,10 @@ class AudioThrottler: AudioThrottleable {
var alreadySent: Bool
var next: NetworkDataWrapper?
var byteCount: UInt {
return UInt(data.count)
}
var endOffset: UInt {
return startOffset + UInt(data.count) - 1
}
@@ -94,6 +98,16 @@ class AudioThrottler: AudioThrottleable {
var byteOffsetBecauseOfSeek: UInt = 0
var totalBytesExpected: Int64? //this got sent up twice. Once at beginning of stream and second from network seek. We honor the first send
var largestPollingOffsetDifference: UInt64 = 0
var lastOffsetPolled: UInt64 = 0 {
didSet {
let diff = lastOffsetPolled - oldValue
if diff > largestPollingOffsetDifference {
largestPollingOffsetDifference = diff
}
}
}
required init(withRemoteUrl url: AudioURL, withDelegate delegate: AudioThrottleDelegate) {
self.url = url
self.delegate = delegate
@@ -133,15 +147,29 @@ class AudioThrottler: AudioThrottleable {
func tellByteOffset(offset: UInt64) {
Log.debug("offset \(offset)")
lastOffsetPolled = offset
for wrappedNetworkData in networkData {
if wrappedNetworkData.containsOffset(UInt(offset)) {
Log.debug("offset within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
Log.debug("offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset) is next sent: \(wrappedNetworkData.isNextSent())")
if wrappedNetworkData.alreadySent {
if !wrappedNetworkData.isNextSent() {
if let next = wrappedNetworkData.next {
Log.debug("Sending next network packet with range: \(next.startOffset) to \(next.endOffset)")
next.alreadySent = true
delegate?.shouldProcess(networkData: next.data)
var bytesSent: UInt = 0
var current = wrappedNetworkData
// Sometimes the next data packet is smaller than a full audio chunk size, so we need to ensure we send up enough packets for the audio chunk. This prevented Issue #4 where tsreaming would randomly get stuck in a state needing more data up the chain.
// https://github.com/tanhakabir/SwiftAudioPlayer/issues/4
while bytesSent < largestPollingOffsetDifference {
if let next = current.next {
Log.debug("Sending next network packet with range: \(next.startOffset) to \(next.endOffset)")
next.alreadySent = true
delegate?.shouldProcess(networkData: next.data)
bytesSent += next.byteCount
current = next
} else {
return
}
}
}
return
+1 -1
View File
@@ -163,7 +163,7 @@ class AudioParser: AudioParsable {
if isParsingComplete {
throw ParserError.readerAskingBeyondEndOfFile
} else {
Log.debug("Tried to pull packet at index: \(packetIndex) when only have: \(audioPackets.count)")
Log.debug("Tried to pull packet at index: \(packetIndex) when only have: \(audioPackets.count), we predict \(totalPredictedPacketCount) in total")
throw ParserError.notEnoughDataForReader
}
}
@@ -105,7 +105,7 @@ class AudioDownloadWorker: NSObject, AudioDataDownloadable {
}
guard numberOfActive < MAX_CONCURRENT_DOWNLOADS else {
queuedDownloads.updatePreservingOldCompletionHandlers(withID: info.id, withRemoteUrl: info.remoteUrl)
_ = queuedDownloads.updatePreservingOldCompletionHandlers(withID: info.id, withRemoteUrl: info.remoteUrl)
return
}
+6 -1
View File
@@ -120,9 +120,14 @@ extension SAPlayer {
presenter.handleSeek(toNeedle: seconds)
}
public func initializeSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
presenter.handlePlaySavedAudio(withSavedUrl: url)
}
public func initializeAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
self.mediaInfo = mediaInfo
presenter.handlePlayAudio(withRemoteUrl: url)
presenter.handlePlayStreamedAudio(withRemoteUrl: url)
}
}
+12 -10
View File
@@ -60,21 +60,23 @@ class SAPlayerPresenter {
urlKeyMap[url.key] = url
}
func handlePlayAudio(withRemoteUrl url: URL) {
func handlePlaySavedAudio(withSavedUrl url: URL) {
attachForUpdates(url: url)
delegate?.startAudioDownloaded(withSavedUrl: url)
}
func handlePlayStreamedAudio(withRemoteUrl url: URL) {
attachForUpdates(url: url)
delegate?.startAudioStreamed(withRemoteUrl: url)
}
private func attachForUpdates(url: URL) {
AudioClockDirector.shared.detachFromChangesInDuration(withID: durationRef)
AudioClockDirector.shared.detachFromChangesInNeedle(withID: needleRef)
AudioClockDirector.shared.detachFromChangesInPlayingStatus(withID: playingStatusRef)
self.key = url.key
if let savedUrl = AudioDataManager.shared.getPersistedUrl(withRemoteURL: url) {
self.key = savedUrl.key
urlKeyMap[savedUrl.key] = url
delegate?.startAudioDownloaded(withSavedUrl: savedUrl)
} else {
urlKeyMap[url.key] = url
delegate?.startAudioStreamed(withRemoteUrl: url)
}
urlKeyMap[url.key] = url
durationRef = AudioClockDirector.shared.attachToChangesInDuration(closure: { [weak self] (key, duration) in
guard let self = self else { throw DirectorError.closureIsDead }
@@ -38,6 +38,12 @@ class DirectorThreadSafeClosures<P> {
private var closures: [UInt: TypeClosure] = [:]
private var cache: [Key: P] = [:]
var count: Int {
get {
return closures.count
}
}
func broadcast(key: Key, payload: P) {
queue.sync {
self.cache[key] = payload
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudioPlayer'
s.version = '1.0.3'
s.version = '1.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.