Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94c1a47641 | |||
| d0296ab012 | |||
| 2fd944d88e | |||
| fc98c4c1c4 |
@@ -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>
|
||||
|
||||
@@ -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&sd=1&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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user