Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bf6cbb56e | |||
| b97f97ca5e | |||
| 0c7bcdcf90 | |||
| 840122e603 | |||
| 8518d10c6d | |||
| f214be28a9 | |||
| f219d9d1a0 | |||
| 8797c0d917 | |||
| 0121d05dff | |||
| 26faf62657 | |||
| 61e79d067a | |||
| 103838d1b8 | |||
| 47de2a5251 | |||
| d4d8f767e3 | |||
| c75da619cf | |||
| aea6f5efaa | |||
| 2625b8f4db | |||
| e6460513ea | |||
| a2504f2726 | |||
| 23f445ce4d | |||
| 61fe0c6ebb | |||
| 72c4335386 | |||
| 640f0b92f0 | |||
| c0f8db29c0 | |||
| 285cd92514 | |||
| a5293a5b39 | |||
| 8430a7e8ce | |||
| 34e430713b | |||
| d23a5f8d62 | |||
| 9f89944bc5 |
+4
@@ -42,6 +42,7 @@
|
||||
A4681FDF220113E20018AB51 /* DirectorThreadSafeClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F872200DAD50018AB51 /* DirectorThreadSafeClosures.swift */; };
|
||||
A4681FE0220113E40018AB51 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F802200D0500018AB51 /* Log.swift */; };
|
||||
A4681FE1220113E70018AB51 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4681F8B2200DDD50018AB51 /* Constants.swift */; };
|
||||
A4B4CC122223ED2A0045554B /* SAPlayerDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */; };
|
||||
A4FBA6B2221B538E00D5A353 /* DownloadProgressDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A49B78C3221A78DE00BBA862 /* DownloadProgressDirector.swift */; };
|
||||
A4FBA6B5221B74C900D5A353 /* SALockScreenInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */; };
|
||||
A4FBA6B7221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */; };
|
||||
@@ -124,6 +125,7 @@
|
||||
A4681FBC220100AB0018AB51 /* AudioStreamEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioStreamEngine.swift; sourceTree = "<group>"; };
|
||||
A4681FBE22010ECF0018AB51 /* LockScreenViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenViewProtocol.swift; sourceTree = "<group>"; };
|
||||
A49B78C3221A78DE00BBA862 /* DownloadProgressDirector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadProgressDirector.swift; sourceTree = "<group>"; };
|
||||
A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerDownloader.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SALockScreenInfo.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerUpdateSubscription.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B8221BAF8700D5A353 /* SAAudioAvailabilityRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAAudioAvailabilityRange.swift; sourceTree = "<group>"; };
|
||||
@@ -344,6 +346,7 @@
|
||||
A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */,
|
||||
A4681F8D2200E00E0018AB51 /* SAPlayer.swift */,
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */,
|
||||
A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */,
|
||||
A4681F912200E1950018AB51 /* SAPlayerDelegate.swift */,
|
||||
A4681F8F2200E1450018AB51 /* SAPlayerPresenter.swift */,
|
||||
A4681FBE22010ECF0018AB51 /* LockScreenViewProtocol.swift */,
|
||||
@@ -530,6 +533,7 @@
|
||||
A4681FC82201138E0018AB51 /* SAPlayerPresenter.swift in Sources */,
|
||||
A4681FD3220113B60018AB51 /* AudioParserPropertyListener.swift in Sources */,
|
||||
A4681FCA220113940018AB51 /* AudioClockDirector.swift in Sources */,
|
||||
A4B4CC122223ED2A0045554B /* SAPlayerDownloader.swift in Sources */,
|
||||
A4681FD0220113A70018AB51 /* AudioConverterErrors.swift in Sources */,
|
||||
A4FBA6B2221B538E00D5A353 /* DownloadProgressDirector.swift in Sources */,
|
||||
A4681FD7220113C30018AB51 /* StreamProgressPTO.swift in Sources */,
|
||||
|
||||
@@ -105,6 +105,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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
@@ -117,6 +123,7 @@
|
||||
<constraint firstItem="joK-xi-MCo" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="AH1-Uu-eLB"/>
|
||||
<constraint firstItem="joK-xi-MCo" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" constant="60" id="Ba7-nd-oCD"/>
|
||||
<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="vfk-OJ-S3T" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="NOY-IO-NIJ"/>
|
||||
@@ -126,12 +133,14 @@
|
||||
<constraint firstItem="lTK-Hd-Tl2" firstAttribute="top" secondItem="j3w-gr-HzF" secondAttribute="bottom" constant="8" id="Wwx-Uo-yIC"/>
|
||||
<constraint firstItem="yUQ-mI-ozK" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="a66-h4-WVf"/>
|
||||
<constraint firstItem="Urj-Dv-41y" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="aKt-EV-Bwd"/>
|
||||
<constraint firstItem="tFH-sY-Xu9" firstAttribute="top" secondItem="1IX-z5-wWx" secondAttribute="bottom" constant="27" id="bIq-V0-Sac"/>
|
||||
<constraint firstItem="tFH-sY-Xu9" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="62.5" id="cH6-q6-Lel"/>
|
||||
<constraint firstItem="jUc-tP-CC5" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="cgM-Nj-yit"/>
|
||||
<constraint firstItem="KDu-ea-kF8" firstAttribute="top" secondItem="joK-xi-MCo" secondAttribute="bottom" constant="32" id="dLw-rF-Pfb"/>
|
||||
<constraint firstItem="w2a-RA-zmI" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="daz-b0-eCC"/>
|
||||
<constraint firstItem="jUc-tP-CC5" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="tFH-sY-Xu9" secondAttribute="trailing" constant="8" symbolic="YES" id="fS9-Ce-4ph"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" constant="16" id="gdg-7Y-7la"/>
|
||||
<constraint firstAttribute="trailing" secondItem="1IX-z5-wWx" secondAttribute="trailing" constant="16" id="hHM-jO-RZd"/>
|
||||
<constraint firstItem="6d9-Bc-hIz" firstAttribute="top" secondItem="joK-xi-MCo" secondAttribute="bottom" constant="32" id="m9s-An-IWV"/>
|
||||
<constraint firstItem="vfk-OJ-S3T" firstAttribute="top" secondItem="yUQ-mI-ozK" secondAttribute="bottom" constant="8" id="oaW-rr-UVN"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0QE-3F-a4G" secondAttribute="trailing" constant="62.5" id="tg1-gr-hdd"/>
|
||||
@@ -145,6 +154,7 @@
|
||||
<outlet property="audioSelector" destination="joK-xi-MCo" id="GmY-Xg-be0"/>
|
||||
<outlet property="bufferProgress" destination="lTK-Hd-Tl2" id="54k-by-qb2"/>
|
||||
<outlet property="currentTimestampLabel" destination="j3w-gr-HzF" id="5Lh-aS-pat"/>
|
||||
<outlet property="currentUrlLocationLabel" destination="1IX-z5-wWx" id="MuO-fF-ZxL"/>
|
||||
<outlet property="downloadButton" destination="KDu-ea-kF8" id="5o4-1h-y06"/>
|
||||
<outlet property="durationLabel" destination="Urj-Dv-41y" id="mIq-eh-int"/>
|
||||
<outlet property="playPauseButton" destination="jUc-tP-CC5" id="e9C-zV-A1B"/>
|
||||
|
||||
@@ -48,10 +48,16 @@ class ViewController: UIViewController {
|
||||
if SAPlayer.Downloader.isDownloaded(withRemoteUrl: selectedAudio.url) {
|
||||
downloadButton.setTitle("Delete downloaded", for: .normal)
|
||||
streamButton.isEnabled = false
|
||||
} else {
|
||||
downloadButton.setTitle("Download", for: .normal)
|
||||
streamButton.isEnabled = true
|
||||
}
|
||||
|
||||
self.currentUrlLocationLabel.text = "remote url: \(selectedAudio.url.absoluteString)"
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var currentUrlLocationLabel: UILabel!
|
||||
@IBOutlet weak var bufferProgress: UIProgressView!
|
||||
@IBOutlet weak var scrubberSlider: UISlider!
|
||||
|
||||
@@ -94,6 +100,7 @@ class ViewController: UIViewController {
|
||||
adjustSpeed()
|
||||
|
||||
isPlayable = false
|
||||
selectedAudio = AudioInfo(index: 0)
|
||||
|
||||
_ = SAPlayer.Updates.Duration.subscribe { [weak self] (url, duration) in
|
||||
guard let self = self else { return }
|
||||
@@ -113,12 +120,15 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
_ = SAPlayer.Updates.AudioDownloading.subscribe { [weak self] (url, progress) in
|
||||
print(progress)
|
||||
guard let self = self else { return }
|
||||
guard url == self.selectedAudio.url else { return }
|
||||
|
||||
if self.isDownloading {
|
||||
self.downloadButton.setTitle("Cancel \(String(format: "%02d", (progress * 100)))%", for: .normal)
|
||||
DispatchQueue.main.async {
|
||||
UIView.performWithoutAnimation {
|
||||
self.downloadButton.setTitle("Cancel \(String(format: "%.2f", (progress * 100)))%", for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,15 +185,23 @@ class ViewController: UIViewController {
|
||||
|
||||
@IBAction func downloadTouched(_ sender: Any) {
|
||||
if !isDownloading {
|
||||
if SAPlayer.Downloader.isDownloaded(withRemoteUrl: selectedAudio.url) {
|
||||
SAPlayer.Downloader.deleteDownload(withRemoteUrl: selectedAudio.url)
|
||||
if let savedUrl = SAPlayer.Downloader.getSavedUrl(forRemoteUrl: selectedAudio.url) {
|
||||
SAPlayer.Downloader.deleteDownloaded(withSavedUrl: savedUrl)
|
||||
downloadButton.setTitle("Download", for: .normal)
|
||||
streamButton.isEnabled = true
|
||||
isDownloading = false
|
||||
} else {
|
||||
downloadButton.setTitle("Cancel 0%", for: .normal)
|
||||
isDownloading = true
|
||||
SAPlayer.Downloader.downloadAudio(withRemoteUrl: selectedAudio.url)
|
||||
SAPlayer.Downloader.downloadAudio(withRemoteUrl: selectedAudio.url, completion: { [weak self] url in
|
||||
DispatchQueue.main.async {
|
||||
self?.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
|
||||
|
||||
if let selectedUrl = self?.selectedAudio.url {
|
||||
SAPlayer.shared.initializeAudio(withRemoteUrl: selectedUrl)
|
||||
}
|
||||
}
|
||||
})
|
||||
streamButton.isEnabled = false
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# SwiftAudioPlayer
|
||||
|
||||
[](https://travis-ci.org/tanhakabir/SwiftAudioPlayer)
|
||||
[](https://cocoapods.org/pods/SwiftAudioPlayer)
|
||||
[](https://cocoapods.org/pods/SwiftAudioPlayer)
|
||||
[](https://cocoapods.org/pods/SwiftAudioPlayer)
|
||||
@@ -33,20 +32,20 @@ pod 'SwiftAudioPlayer'
|
||||
### Usage
|
||||
|
||||
To play remote audio:
|
||||
```
|
||||
```swift
|
||||
let url = URL(string: "https://randomwebsite.com/audio.mp3")!
|
||||
SAPlayer.shared.initializeAudio(withRemoteUrl: url)
|
||||
SAPlayer.shared.play()
|
||||
```
|
||||
|
||||
To set the display information for the lockscreen:
|
||||
```
|
||||
```swift
|
||||
let info = SALockScreenInfo(title: "Random audio", artist: "Foo", artwork: UIImage(), releaseDate: 123456789)
|
||||
SAPlayer.shared.mediaInfo = info
|
||||
```
|
||||
|
||||
To receive streaming progress:
|
||||
```
|
||||
```swift
|
||||
@IBOutlet weak var bufferProgress: UIProgressView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -64,7 +63,11 @@ override func viewDidLoad() {
|
||||
}
|
||||
}
|
||||
```
|
||||
Look at the [Updates](#SAPlayer.Updates) section to see usage details and other updates to follow.
|
||||
|
||||
**Important:** For app in background downloading please refer to [note](#important-step-for-background-downloads).
|
||||
|
||||
For more details and specifics look at the [API documentation](#api-in-detail) below.
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -79,6 +82,114 @@ Feel free to reach out to either of us:
|
||||
[tanhakabir](https://github.com/tanhakabir), tanhakabir.ca@gmail.com
|
||||
[JonMercer](https://github.com/JonMercer), mercer.jon@gmail.com
|
||||
|
||||
## License
|
||||
### License
|
||||
|
||||
SwiftAudioPlayer is available under the MIT license. See the LICENSE file for more info.
|
||||
|
||||
---
|
||||
|
||||
# API in detail
|
||||
|
||||
## SAPlayer.Downloader
|
||||
|
||||
Use functionaity from Downloader to save audio files from remote locations for future offline playback.
|
||||
|
||||
Audio files are saved under custom naming scheme on device and are recoverable with original remote URL for file.
|
||||
|
||||
#### Important step for background downloads
|
||||
|
||||
To ensure that your app will keep downloading audio in the background be sure to add the following to `AppDelegate.swift`:
|
||||
|
||||
```swift
|
||||
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
|
||||
SAPlayer.Downloader.setBackgroundCompletionHandler(completionHandler)
|
||||
}
|
||||
```
|
||||
|
||||
### Downloading
|
||||
|
||||
Downloads will be held on pause when active stream is started, and will resume downloads when streaming is done.
|
||||
|
||||
Use the following to start downloading audio in the background:
|
||||
|
||||
```swift
|
||||
func downloadAudio(withRemoteUrl url: URL, completion: @escaping (_ savedUrl: URL) -> ())
|
||||
```
|
||||
|
||||
It will call the completion handler you pass after successful download with the location of the downloaded file on the device.
|
||||
|
||||
And use the following to stop any active or prevent future downloads of the corresponding remote URL:
|
||||
|
||||
```swift
|
||||
func cancelDownload(withRemoteUrl url: URL)
|
||||
```
|
||||
|
||||
### Manage Downloaded
|
||||
|
||||
Use the following to manage downloaded audio files.
|
||||
|
||||
Checks if downloaded already:
|
||||
```swift
|
||||
func isDownloaded(withRemoteUrl url: URL) -> Bool
|
||||
```
|
||||
|
||||
Get URL of audio file saved on device corresponding to remote location:
|
||||
```swift
|
||||
func getSavedUrl(forRemoteUrl url: URL) -> URL?
|
||||
```
|
||||
|
||||
Delete downloaded audio if it exists:
|
||||
```swift
|
||||
func deleteDownloaded(withSavedUrl url: URL)
|
||||
```
|
||||
|
||||
## SAPlayer.Updates
|
||||
|
||||
Receive updates for changing values from the player, such as the duration, elapsed time of playing audio, download progress, and etc.
|
||||
|
||||
All subscription functions for updates take the form of:
|
||||
```swift
|
||||
func subscribe(_ closure: @escaping (_ url: URL, _ payload: <Payload>) -> ()) -> UInt
|
||||
```
|
||||
|
||||
- `closure`: The closure that will receive the updates. It's recommended to have a weak reference to a class that uses these functions.
|
||||
- `url`: The corresponding remote URL for the update. In the case there might be multiple files observed, such as downloading many files at once or switching over from playing one audio to another and the updates corresponding to the previous aren't silenced on switch-over.
|
||||
- `payload`: The updated value.
|
||||
- Returns: the id for the subscription in the case you would like to unsubscribe to updates for the closure.
|
||||
|
||||
Similarily unsubscribe takes the form of:
|
||||
```swift
|
||||
func unsubscribe(_ id: UInt)
|
||||
```
|
||||
|
||||
- `id`: The closure with this id will stop receiving updates.
|
||||
|
||||
|
||||
### ElapsedTime
|
||||
Payload = `Double`
|
||||
|
||||
Changes in the timestamp/elapsed time of the current initialized audio. Aka, where the scrubber's pointer of the audio should be at.
|
||||
|
||||
Subscribe to this to update views on changes in position of which part of audio is being played.
|
||||
|
||||
### Duration
|
||||
Payload = `Double`
|
||||
|
||||
Changes in the duration of the current initialized audio. Especially helpful for audio that is being streamed and can change with more data.
|
||||
|
||||
### PlayingStatus
|
||||
Payload = `Bool`
|
||||
|
||||
Changes in the playing/paused status of the player.
|
||||
|
||||
### StreamingBuffer
|
||||
Payload = `SAAudioAvailabilityRange`
|
||||
|
||||
Changes in the progress of downloading audio for streaming. Information about range of audio available and if the audio is playable. Look at SAAudioAvailabilityRange for more information.
|
||||
|
||||
For progress of downloading audio that saves to the phone for playback later, look at AudioDownloading instead.
|
||||
|
||||
### AudioDownloading
|
||||
Payload = `Double`
|
||||
|
||||
Changes in the progress of downloading audio in the background. This does not correspond to progress in streaming downloads, look at StreamingBuffer for streaming progress.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// This file was modified and adapted from https://github.com/syedhali/AudioStreamer
|
||||
// which was released under Apache License 2.0. Apache License 2.0 requires explicit
|
||||
// documentation of modified files from source and a copy of the Apache License 2.0
|
||||
// in the project which he have under the name Credited_LICENSE.
|
||||
// in the project which is under the name Credited_LICENSE.
|
||||
//
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -56,12 +56,22 @@ extension LockScreenViewProtocol {
|
||||
nowPlayingInfo[MPMediaItemPropertyPodcastTitle] = title
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 0.0 //because default is 1.0. If we pause audio then it keeps ticking
|
||||
nowPlayingInfo[MPMediaItemPropertyReleaseDate] = Date(timeIntervalSince1970: TimeInterval(releaseDate))
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] =
|
||||
MPMediaItemArtwork(boundsSize: info.artwork.size) { size in
|
||||
return info.artwork
|
||||
|
||||
if let artwork = info.artwork {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] =
|
||||
MPMediaItemArtwork(boundsSize: artwork.size) { size in
|
||||
return artwork
|
||||
}
|
||||
} else {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: UIImage().size) { size in
|
||||
return UIImage()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,9 @@ protocol AudioDataManagable {
|
||||
func deleteStream(withRemoteURL url: AudioURL)
|
||||
|
||||
func getPersistedUrl(withRemoteURL url: AudioURL) -> URL?
|
||||
func startDownload(withRemoteURL url: AudioURL)
|
||||
func deleteDownload(withRemoteURL url: AudioURL)
|
||||
func startDownload(withRemoteURL url: AudioURL, completion: @escaping (URL) -> ())
|
||||
func cancelDownload(withRemoteURL url: AudioURL)
|
||||
func deleteDownload(withLocalURL url: URL)
|
||||
}
|
||||
|
||||
class AudioDataManager: AudioDataManagable {
|
||||
@@ -152,11 +153,12 @@ extension AudioDataManager {
|
||||
return FileStorage.Audio.locate(url.key)
|
||||
}
|
||||
|
||||
func startDownload(withRemoteURL url: AudioURL) {
|
||||
func startDownload(withRemoteURL url: AudioURL, completion: @escaping (URL) -> ()) {
|
||||
let key = url.key
|
||||
|
||||
if FileStorage.Audio.isStored(key) {
|
||||
if let savedUrl = FileStorage.Audio.locate(key), FileStorage.Audio.isStored(key) {
|
||||
globalDownloadProgressCallback(key, 1.0)
|
||||
completion(savedUrl)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,13 +173,17 @@ extension AudioDataManager {
|
||||
return
|
||||
}
|
||||
|
||||
downloadWorker.start(withID: key, withRemoteUrl: url, withResumeData: nil)
|
||||
downloadWorker.start(withID: key, withRemoteUrl: url, completion: completion)
|
||||
}
|
||||
|
||||
func deleteDownload(withRemoteURL url: AudioURL) {
|
||||
func cancelDownload(withRemoteURL url: AudioURL) {
|
||||
downloadWorker.stop(withID: url.key, callback: nil)
|
||||
FileStorage.Audio.delete(url.key)
|
||||
}
|
||||
|
||||
func deleteDownload(withLocalURL url: URL) {
|
||||
FileStorage.delete(url)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- Listeners
|
||||
|
||||
@@ -33,7 +33,7 @@ protocol AudioDataDownloadable: AnyObject {
|
||||
|
||||
func getProgressOfDownload(withID id: ID) -> Double?
|
||||
|
||||
func start(withID id: ID, withRemoteUrl remoteUrl: URL, withResumeData data: Data?)
|
||||
func start(withID id: ID, withRemoteUrl remoteUrl: URL, completion: @escaping (URL) -> ())
|
||||
func stop(withID id: ID, callback: ((_ dataSoFar: Data?, _ totalBytesExpected: Int64?) -> ())?)
|
||||
func pauseAllActive() //Because of streaming
|
||||
func resumeAllActive() //Because of streaming
|
||||
@@ -85,31 +85,36 @@ class AudioDownloadWorker: NSObject, AudioDataDownloadable {
|
||||
return activeDownloads.filter { $0.info.id == id }.first?.progress
|
||||
}
|
||||
|
||||
func start(withID id: ID, withRemoteUrl remoteUrl: URL, withResumeData data: Data? = nil) {
|
||||
Log.info("paramID: \(id) activeDownloadIDs: \((activeDownloads.map { $0.info.id } ).toLog)")
|
||||
func start(withID id: ID, withRemoteUrl remoteUrl: URL, completion: @escaping (URL) -> ()) {
|
||||
Log.info("startExternal paramID: \(id) activeDownloadIDs: \((activeDownloads.map { $0.info.id } ).toLog)")
|
||||
let temp = activeDownloads.filter { $0.info.id == id }.count
|
||||
guard temp == 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let rank = Date.getUTC()
|
||||
let info = queuedDownloads.updatePreservingOldCompletionHandlers(withID: id, withRemoteUrl: remoteUrl, completion: completion)
|
||||
|
||||
guard numberOfActive < MAX_CONCURRENT_DOWNLOADS else {
|
||||
queuedDownloads.update(with: DownloadInfo(id: id, remoteUrl: remoteUrl, rank: rank))
|
||||
start(withInfo: info)
|
||||
}
|
||||
|
||||
fileprivate func start(withInfo info: DownloadInfo) {
|
||||
Log.info("paramID: \(info.id) activeDownloadIDs: \((activeDownloads.map { $0.info.id } ).toLog)")
|
||||
let temp = activeDownloads.filter { $0.info.id == info.id }.count
|
||||
guard temp == 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var task: URLSessionDownloadTask
|
||||
|
||||
if let resumeData = data {
|
||||
task = session.downloadTask(withResumeData: resumeData)
|
||||
} else {
|
||||
task = session.downloadTask(with: remoteUrl)
|
||||
guard numberOfActive < MAX_CONCURRENT_DOWNLOADS else {
|
||||
queuedDownloads.updatePreservingOldCompletionHandlers(withID: info.id, withRemoteUrl: info.remoteUrl)
|
||||
return
|
||||
}
|
||||
|
||||
task.taskDescription = id
|
||||
queuedDownloads.remove(info)
|
||||
|
||||
let activeTask = ActiveDownload(info: DownloadInfo(id: id, remoteUrl: remoteUrl, rank: rank), task: task)
|
||||
let task: URLSessionDownloadTask = session.downloadTask(with: info.remoteUrl)
|
||||
task.taskDescription = info.id
|
||||
|
||||
let activeTask = ActiveDownload(info: info, task: task)
|
||||
|
||||
activeDownloads.append(activeTask)
|
||||
activeTask.task.resume()
|
||||
@@ -145,6 +150,7 @@ class AudioDownloadWorker: NSObject, AudioDataDownloadable {
|
||||
}
|
||||
}
|
||||
|
||||
queuedDownloads.remove(withMatchingId: id)
|
||||
callback?(nil, nil)
|
||||
}
|
||||
}
|
||||
@@ -189,10 +195,15 @@ extension AudioDownloadWorker: URLSessionDownloadDelegate {
|
||||
}
|
||||
|
||||
completionHandler(task.info.id, nil)
|
||||
|
||||
for handler in task.info.completionHandlers {
|
||||
handler(destinationUrl)
|
||||
}
|
||||
|
||||
activeDownloads = activeDownloads.filter { $0 != task }
|
||||
|
||||
if let queued = queuedDownloads.popHighestRanked() {
|
||||
start(withID: queued.id, withRemoteUrl: queued.remoteUrl)
|
||||
start(withInfo: queued)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,9 +267,18 @@ extension AudioDownloadWorker {
|
||||
// MARK:- Helper Classes
|
||||
extension AudioDownloadWorker {
|
||||
fileprivate struct DownloadInfo: Hashable {
|
||||
static func == (lhs: AudioDownloadWorker.DownloadInfo, rhs: AudioDownloadWorker.DownloadInfo) -> Bool {
|
||||
return lhs.id == rhs.id && lhs.remoteUrl == rhs.remoteUrl
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
return id.hashValue ^ remoteUrl.hashValue
|
||||
}
|
||||
|
||||
let id: ID
|
||||
let remoteUrl: URL
|
||||
let rank: Int
|
||||
var completionHandlers: [(URL) -> ()]
|
||||
}
|
||||
|
||||
private class ActiveDownload: Hashable {
|
||||
@@ -298,6 +318,47 @@ extension Set where Element == AudioDownloadWorker.DownloadInfo {
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
mutating func updatePreservingOldCompletionHandlers(withID id: ID, withRemoteUrl remoteUrl: URL, completion: ((URL) -> ())? = nil) -> AudioDownloadWorker.DownloadInfo {
|
||||
|
||||
let rank = Date.getUTC()
|
||||
|
||||
let tempHandlers: [(URL) -> ()] = completion != nil ? [completion!] : []
|
||||
|
||||
var newInfo = AudioDownloadWorker.DownloadInfo.init(id: id, remoteUrl: remoteUrl, rank: rank, completionHandlers: tempHandlers)
|
||||
|
||||
if let previous = self.update(with: newInfo) {
|
||||
let prevHandlers = previous.completionHandlers
|
||||
let newHandlers = prevHandlers + tempHandlers
|
||||
|
||||
newInfo = AudioDownloadWorker.DownloadInfo.init(id: id, remoteUrl: remoteUrl, rank: rank, completionHandlers: newHandlers)
|
||||
|
||||
self.update(with: newInfo)
|
||||
}
|
||||
|
||||
return newInfo
|
||||
}
|
||||
|
||||
mutating func remove(withMatchingId id: ID) {
|
||||
var toRemove: AudioDownloadWorker.DownloadInfo? = nil
|
||||
var matchCount = 0
|
||||
|
||||
for item in self.enumerated() {
|
||||
if item.element.id == id {
|
||||
toRemove = item.element
|
||||
matchCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
guard matchCount <= 1 else {
|
||||
Log.error("Found \(matchCount) matches of queued info with the same id of: \(id), this should have never happened.")
|
||||
return
|
||||
}
|
||||
|
||||
if let removeInfo = toRemove {
|
||||
self.remove(removeInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
@@ -36,19 +36,19 @@ struct FileStorage {
|
||||
|
||||
Note: It is not guaranteed that the file actually exists.
|
||||
*/
|
||||
private static func getUrl(givenAName name: NameFile, inDirectory dir: FileManager.SearchPathDirectory) -> URL {
|
||||
static func getUrl(givenAName name: NameFile, inDirectory dir: FileManager.SearchPathDirectory) -> URL {
|
||||
let directoryPath = NSSearchPathForDirectoriesInDomains(dir, .userDomainMask, true)[0] as String
|
||||
let url = URL(fileURLWithPath: directoryPath)
|
||||
return url.appendingPathComponent(name)
|
||||
}
|
||||
|
||||
private static func isStored(_ url: URL) -> Bool{
|
||||
static func isStored(_ url: URL) -> Bool{
|
||||
// https://stackoverflow.com/questions/42897844/swift-3-0-filemanager-fileexistsatpath-always-return-false
|
||||
// When determining if a file exists, we must use .path not .absolute string!
|
||||
return FileManager.default.fileExists(atPath: url.path)
|
||||
}
|
||||
|
||||
private static func delete(_ url: URL) {
|
||||
static func delete(_ url: URL) {
|
||||
if !isStored(url) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,15 +26,21 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/**
|
||||
UTC corresponds to epoch time (number of seconds that have elapsed since January 1, 1970, midnight UTC/GMT). https://www.epochconverter.com/ is a useful site to convert to human readable format.
|
||||
*/
|
||||
public typealias UTC = Int
|
||||
|
||||
/**
|
||||
Use to set what will be displayed in the lockscreen.
|
||||
*/
|
||||
public struct SALockScreenInfo {
|
||||
var title: String
|
||||
var artist: String
|
||||
var artwork: UIImage
|
||||
var artwork: UIImage?
|
||||
var releaseDate: UTC
|
||||
|
||||
public init(title: String, artist: String, artwork: UIImage, releaseDate: UTC) {
|
||||
public init(title: String, artist: String, artwork: UIImage?, releaseDate: UTC) {
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.artwork = artwork
|
||||
|
||||
+2
-25
@@ -94,7 +94,7 @@ public class SAPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Player Controls
|
||||
//MARK: - External Player Controls
|
||||
extension SAPlayer {
|
||||
public func togglePlayAndPause() {
|
||||
presenter.handleTogglePlayingAndPausing()
|
||||
@@ -126,31 +126,8 @@ extension SAPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
extension SAPlayer {
|
||||
public struct Downloader {
|
||||
public static func downloadAudio(withRemoteUrl url: URL) {
|
||||
SAPlayer.shared.addUrlToMapping(url: url)
|
||||
AudioDataManager.shared.startDownload(withRemoteURL: url)
|
||||
}
|
||||
|
||||
public static func cancelDownload(withRemoteUrl url: URL) {
|
||||
AudioDataManager.shared.deleteDownload(withRemoteURL: url)
|
||||
}
|
||||
|
||||
public static func deleteDownload(withRemoteUrl url: URL) {
|
||||
AudioDataManager.shared.deleteDownload(withRemoteURL: url)
|
||||
}
|
||||
|
||||
public static func isDownloaded(withRemoteUrl url: URL) -> Bool {
|
||||
return AudioDataManager.shared.getPersistedUrl(withRemoteURL: url) != nil
|
||||
}
|
||||
|
||||
public static func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ()) {
|
||||
AudioDataManager.shared.setBackgroundCompletionHandler(completionHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Internal implementation of delegate
|
||||
extension SAPlayer: SAPlayerDelegate {
|
||||
func startAudioDownloaded(withSavedUrl url: AudioURL) {
|
||||
player?.pause()
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// SAPlayerDownloader.swift
|
||||
// SwiftAudioPlayer
|
||||
//
|
||||
// Created by Tanha Kabir on 2019-02-25.
|
||||
// Copyright © 2019 Tanha Kabir, Jon Mercer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension SAPlayer {
|
||||
/**
|
||||
Actions relating to downloading remote audio to the device for offline playback.
|
||||
|
||||
- Note: All saved urls generated from downloaded audio corresponds to a specific remote url. Thus, can be queryed if original remote url is known.
|
||||
|
||||
- Important: Please ensure that you have passed in the background download completion handler in the AppDelegate with `setBackgroundCompletionHandler` to allow for downloading audio while app is in the background.
|
||||
*/
|
||||
public struct Downloader {
|
||||
/**
|
||||
Download audio from a remote url. Will save the audio on the device for playback later.
|
||||
|
||||
Save the saved url of the downloaded audio for future playback or query for the saved url with the same remote url in the future.
|
||||
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter url: The remote url to download audio from.
|
||||
- Parameter completion: Completion handler that will return once the download is successful and complete.
|
||||
- Parameter savedUrl: The url of where the audio was saved locally on the device. Will receive once download has completed.
|
||||
*/
|
||||
public static func downloadAudio(withRemoteUrl url: URL, completion: @escaping (_ savedUrl: URL) -> ()) {
|
||||
SAPlayer.shared.addUrlToMapping(url: url)
|
||||
AudioDataManager.shared.startDownload(withRemoteURL: url, completion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Cancel downloading audio from a specific remote url if actively downloading. If download has not started yet, it will remove from the list of future downloads queued.
|
||||
|
||||
- Parameter url: The remote url corresponding to the active download you want to cancel.
|
||||
*/
|
||||
public static func cancelDownload(withRemoteUrl url: URL) {
|
||||
AudioDataManager.shared.cancelDownload(withRemoteURL: url)
|
||||
}
|
||||
|
||||
/**
|
||||
Delete downloaded audio file from device at url.
|
||||
|
||||
- Note: This will delete any file saved on device at the local url. This, however, is intended to use for audio files.
|
||||
|
||||
- Parameter url: The url of the audio to delete from the device.
|
||||
*/
|
||||
public static func deleteDownloaded(withSavedUrl url: URL) {
|
||||
AudioDataManager.shared.deleteDownload(withLocalURL: url)
|
||||
}
|
||||
|
||||
/**
|
||||
Check if audio at remote url is downloaded on device.
|
||||
|
||||
- Parameter url: The remote url corresponding to the audio file you want to see if downloaded.
|
||||
- Returns: Whether of not file at remote url is downloaded on device.
|
||||
*/
|
||||
public static func isDownloaded(withRemoteUrl url: URL) -> Bool {
|
||||
return AudioDataManager.shared.getPersistedUrl(withRemoteURL: url) != nil
|
||||
}
|
||||
|
||||
/**
|
||||
Get url of audio file downloaded from remote url onto on device if it exists.
|
||||
|
||||
- Parameter url: The remote url corresponding to the audio file you want the device url of.
|
||||
- Returns: Url of audio file on device if it exists.
|
||||
*/
|
||||
public static func getSavedUrl(forRemoteUrl url: URL) -> URL? {
|
||||
return AudioDataManager.shared.getPersistedUrl(withRemoteURL: url)
|
||||
}
|
||||
|
||||
/**
|
||||
Pass along the completion handler from `AppDelegate` to ensure downloading continues while app is in background.
|
||||
|
||||
- Parameter completionHandler: The completion hander from `AppDelegate` to use for app in the background downloads.
|
||||
*/
|
||||
public static func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ()) {
|
||||
AudioDataManager.shared.setBackgroundCompletionHandler(completionHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Subscribe to updates in elapsed time of the playing audio. Aka, the current timestamp of the audio.
|
||||
|
||||
Note: It's recommended to have a weak reference to a class that uses this fuction
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter closure: The closure that will receive the updates of the changes in time.
|
||||
- Parameter url: The corresponding remote URL for the updated playing time.
|
||||
@@ -72,7 +72,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Subscribe to updates to changes in duration of the current audio initialized.
|
||||
|
||||
Note: It's recommended to have a weak reference to a class that uses this fuction
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter closure: The closure that will receive the updates of the changes in duration.
|
||||
- Parameter url: The corresponding remote URL for the updated duration.
|
||||
@@ -104,7 +104,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Subscribe to updates to changes in the playing/paused status of audio.
|
||||
|
||||
Note: It's recommended to have a weak reference to a class that uses this fuction
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter closure: The closure that will receive the updates of the changes in duration.
|
||||
- Parameter url: The corresponding remote URL for the updated duration.
|
||||
@@ -129,14 +129,14 @@ extension SAPlayer {
|
||||
}
|
||||
|
||||
/**
|
||||
Updates to changes in the progress of downloading audio for streaming. Information about range of audio available and if the audio is playable. Look at SAAudioAvailabilityRange for more information.
|
||||
Updates to changes in the progress of downloading audio for streaming. Information about range of audio available and if the audio is playable. Look at `SAAudioAvailabilityRange` for more information.
|
||||
*/
|
||||
public struct StreamingBuffer {
|
||||
|
||||
/**
|
||||
Subscribe to updates to changes in the progress of downloading audio for streaming. Information about range of audio available and if the audio is playable. Look at SAAudioAvailabilityRange for more information. For progress of downloading audio that saves to the phone for playback later, look at AudioDownloading instead.
|
||||
|
||||
Note: It's recommended to have a weak reference to a class that uses this fuction
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter closure: The closure that will receive the updates of the changes in duration.
|
||||
- Parameter url: The corresponding remote URL for the updated streaming progress.
|
||||
@@ -168,7 +168,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Subscribe to updates to changes in the progress of downloading audio. This does not correspond to progress in streaming downloads, look at StreamingBuffer for streaming progress.
|
||||
|
||||
Note: It's recommended to have a weak reference to a class that uses this fuction
|
||||
- Note: It's recommended to have a weak reference to a class that uses this function
|
||||
|
||||
- Parameter closure: The closure that will receive the updates of the changes in duration.
|
||||
- Parameter url: The corresponding remote URL for the updated download progress.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '0.1.0'
|
||||
s.version = '1.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.
|
||||
|
||||
Reference in New Issue
Block a user