Compare commits

...

11 Commits

Author SHA1 Message Date
Tanha 876d517f3d Release 1.1.1 2019-11-20 22:15:51 -08:00
Tanha 0a12c68274 Fix fatal error when seeking on streamed audio 2019-11-20 22:15:32 -08:00
cendolinside123 873e537301 fix seek bar on example app's player (#9) 2019-11-20 22:09:11 -08:00
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
Tanha 8bf6cbb56e Release 1.0.3 2019-11-18 13:46:50 -08:00
Tanha b97f97ca5e Fix fatal error on iOS 10.0
close #3
2019-11-18 13:39:06 -08:00
Tanha 0c7bcdcf90 Fix issue on example app that prevented downloaded audio being playable 2019-11-18 11:42:02 -08:00
Tanha 840122e603 remove build badge from README 2019-04-27 23:00:46 -07:00
11 changed files with 128 additions and 59 deletions
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -22,50 +20,52 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lTK-Hd-Tl2">
<rect key="frame" x="16" y="320" width="343" height="2"/>
<rect key="frame" x="16" y="303" width="343" height="2"/>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="progressTintColor" red="0.46202266219999999" green="0.83828371759999998" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="trackTintColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</progressView>
<slider opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="749" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="w2a-RA-zmI">
<rect key="frame" x="14" y="305" width="347" height="31"/>
<rect key="frame" x="14" y="288" width="347" height="31"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="maximumTrackTintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="valueChanged" id="jDA-wR-wxk"/>
<action selector="beingScrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchDown" id="UXg-Wf-fKv"/>
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="hTi-fq-lrl"/>
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchUpOutside" id="mFP-SW-38w"/>
</connections>
</slider>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jUc-tP-CC5">
<rect key="frame" x="172.5" y="250" width="30" height="30"/>
<rect key="frame" x="172.5" y="233" width="30" height="30"/>
<state key="normal" title="play"/>
<connections>
<action selector="playPauseTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="Avk-K3-EZ7"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tFH-sY-Xu9">
<rect key="frame" x="62.5" y="250" width="30" height="30"/>
<rect key="frame" x="62.5" y="233" width="30" height="30"/>
<state key="normal" title="-15"/>
<connections>
<action selector="skipBackwardTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="PCT-BE-udf"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0QE-3F-a4G">
<rect key="frame" x="282.5" y="250" width="30" height="30"/>
<rect key="frame" x="282.5" y="233" width="30" height="30"/>
<state key="normal" title="+30"/>
<connections>
<action selector="skipForwardTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="uXv-bz-tnt"/>
</connections>
</button>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.10000000000000001" maxValue="32" translatesAutoresizingMaskIntoConstraints="NO" id="vfk-OJ-S3T">
<rect key="frame" x="14" y="564" width="347" height="31"/>
<rect key="frame" x="14" y="447" width="347" height="31"/>
<connections>
<action selector="rateChanged:" destination="vXZ-lx-hvc" eventType="valueChanged" id="FDJ-jA-bm8"/>
</connections>
</slider>
<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"/>
<rect key="frame" x="16" y="60" width="343" height="32"/>
<segments>
<segment title="20k Hertz"/>
<segment title="Soundbite"/>
<segment title="Acquired"/>
<segment title="Y Combinator"/>
</segments>
@@ -74,39 +74,39 @@
</connections>
</segmentedControl>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KDu-ea-kF8">
<rect key="frame" x="78" y="140" width="69" height="30"/>
<rect key="frame" x="78" y="123" width="69" height="30"/>
<state key="normal" title="Download"/>
<connections>
<action selector="downloadTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="8Jg-1C-0Ms"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6d9-Bc-hIz">
<rect key="frame" x="244" y="140" width="49" height="30"/>
<rect key="frame" x="244" y="123" width="49" height="30"/>
<state key="normal" title="Stream"/>
<connections>
<action selector="streamTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="AXY-N7-87Y"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="rate: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yUQ-mI-ozK">
<rect key="frame" x="157" y="535" width="61" height="21"/>
<rect key="frame" x="157" y="418" width="61" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j3w-gr-HzF">
<rect key="frame" x="16" y="297" width="27" height="15"/>
<rect key="frame" x="16" y="280" width="27" height="15"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="100:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Urj-Dv-41y">
<rect key="frame" x="319" y="297" width="40" height="15"/>
<rect key="frame" x="319" y="280" width="40" height="15"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="remote url: " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1IX-z5-wWx">
<rect key="frame" x="16" y="207" width="343" height="16"/>
<rect key="frame" x="16" y="190" width="343" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@@ -125,7 +125,7 @@
<constraint firstItem="Urj-Dv-41y" firstAttribute="centerY" secondItem="j3w-gr-HzF" secondAttribute="centerY" id="Fvd-7V-Rr8"/>
<constraint firstItem="1IX-z5-wWx" firstAttribute="leading" secondItem="joK-xi-MCo" secondAttribute="leading" id="GeX-7f-jzu"/>
<constraint firstItem="0QE-3F-a4G" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="jUc-tP-CC5" secondAttribute="trailing" constant="8" symbolic="YES" id="JP5-yW-eVB"/>
<constraint firstItem="yUQ-mI-ozK" firstAttribute="top" secondItem="w2a-RA-zmI" secondAttribute="bottom" constant="200" id="K1K-8N-SpD"/>
<constraint firstItem="yUQ-mI-ozK" firstAttribute="top" secondItem="w2a-RA-zmI" secondAttribute="bottom" constant="100" id="K1K-8N-SpD"/>
<constraint firstItem="vfk-OJ-S3T" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="NOY-IO-NIJ"/>
<constraint firstItem="tFH-sY-Xu9" firstAttribute="centerY" secondItem="jUc-tP-CC5" secondAttribute="centerY" id="Rre-EY-kVY"/>
<constraint firstItem="KDu-ea-kF8" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="78" id="SRU-sX-z5b"/>
+34 -16
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) {
@@ -77,6 +79,7 @@ class ViewController: UIViewController {
var isDownloading: Bool = false
var isStreaming: Bool = false
var beingSeeked: Bool = false
var duration: Double = 0.0
@@ -104,19 +107,22 @@ 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 }
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
guard self.duration != 0 else { return }
self.scrubberSlider.value = Float(position/self.duration)
if self.beingSeeked == false{
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
guard self.duration != 0 else { return }
self.scrubberSlider.value = Float(position/self.duration)
}
}
_ = SAPlayer.Updates.AudioDownloading.subscribe { [weak self] (url, progress) in
@@ -134,7 +140,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 +157,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,10 +178,18 @@ 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 beingScrubberSeeked(_ sender: UISlider) {
beingSeeked = true
}
@IBAction func scrubberSeeked(_ sender: Any) {
SAPlayer.shared.seekTo(seconds: Double(scrubberSlider.value))
let value = Double(scrubberSlider.value) * duration
SAPlayer.shared.seekTo(seconds: value)
beingSeeked = false
SAPlayer.shared.play()
}
@@ -194,8 +208,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
SAPlayer.shared.initializeSavedAudio(withSavedUrl: url)
}
})
streamButton.isEnabled = false
-1
View File
@@ -1,6 +1,5 @@
# SwiftAudioPlayer
[![CI Status](https://img.shields.io/travis/tanhakabir/SwiftAudioPlayer.svg?style=flat)](https://travis-ci.org/tanhakabir/SwiftAudioPlayer)
[![Version](https://img.shields.io/cocoapods/v/SwiftAudioPlayer.svg?style=flat)](https://cocoapods.org/pods/SwiftAudioPlayer)
[![License](https://img.shields.io/cocoapods/l/SwiftAudioPlayer.svg?style=flat)](https://cocoapods.org/pods/SwiftAudioPlayer)
[![Platform](https://img.shields.io/cocoapods/p/SwiftAudioPlayer.svg?style=flat)](https://cocoapods.org/pods/SwiftAudioPlayer)
+31 -5
View File
@@ -36,6 +36,7 @@ protocol AudioThrottleable {
func tellAudioFormatFound()
func tellByteOffset(offset: UInt64)
func tellSeek(offset: UInt64)
func tellBytesPerAudioPacket(count: UInt64)
func pollRangeOfBytesAvailable() -> (UInt64, UInt64)
func invalidate()
}
@@ -47,6 +48,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 +99,8 @@ 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 = 1
required init(withRemoteUrl url: AudioURL, withDelegate delegate: AudioThrottleDelegate) {
self.url = url
self.delegate = delegate
@@ -131,17 +138,36 @@ class AudioThrottler: AudioThrottleable {
shouldThrottle = true //the above layer has enough info that we can throttle
}
func tellBytesPerAudioPacket(count: UInt64) {
if count > largestPollingOffsetDifference {
largestPollingOffsetDifference = count
}
}
func tellByteOffset(offset: UInt64) {
Log.debug("offset \(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
+9 -2
View File
@@ -98,7 +98,14 @@ class AudioParser: AudioParsable {
return predictedCount
}
var sumOfParsedAudioBytes:UInt32 = 0
var sumOfParsedAudioBytes:UInt32 = 0 {
didSet {
if let byteCount = averageBytesPerPacket {
throttler.tellBytesPerAudioPacket(count: UInt64(byteCount))
}
}
}
var numberOfPacketsParsed:UInt32 = 0
var audioPackets: [(AudioStreamPacketDescription?,Data)] = [] {
didSet {
@@ -163,7 +170,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 }
+7 -1
View File
@@ -33,7 +33,13 @@ extension Date {
*/
static func getUTC64() -> UInt {
//"On 32-bit platforms, UInt is the same size as UInt32, and on 64-bit platforms, UInt is the same size as UInt64."
return UInt(Date().timeIntervalSince1970.bitPattern)
if #available(iOS 11.0, *) {
return UInt(Date().timeIntervalSince1970.bitPattern)
} else {
let time = Date().timeIntervalSince1970.bitPattern & 0xFFFFFFFF;
return UInt(time)
}
}
/**
@@ -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.2'
s.version = '1.1.1'
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.