Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e66b4b4d4 | |||
| 58bbc97a1b | |||
| 8d9e9d92f4 | |||
| 03392c21e0 | |||
| 924170d159 | |||
| b355eb4e09 | |||
| 1373a816a6 | |||
| 196b04a703 |
@@ -145,11 +145,9 @@ class ViewController: UIViewController {
|
||||
|
||||
if self.duration == 0.0 { return }
|
||||
|
||||
let progress = Float((buffer.totalDurationBuffered + buffer.startingBufferTimePositon) / self.duration)
|
||||
self.bufferProgress.progress = Float(buffer.bufferingProgress)
|
||||
|
||||
self.bufferProgress.progress = progress
|
||||
|
||||
if progress >= 0.99 {
|
||||
if buffer.bufferingProgress >= 0.99 {
|
||||
self.streamButton.isEnabled = false
|
||||
}
|
||||
|
||||
@@ -244,7 +242,7 @@ class ViewController: UIViewController {
|
||||
self.currentUrlLocationLabel.text = "saved to: \(url.lastPathComponent)"
|
||||
self.savedUrls[self.selectedAudio] = url
|
||||
|
||||
SAPlayer.shared.initializeSavedAudio(withSavedUrl: url)
|
||||
SAPlayer.shared.startSavedAudio(withSavedUrl: url)
|
||||
}
|
||||
})
|
||||
streamButton.isEnabled = false
|
||||
@@ -259,7 +257,7 @@ class ViewController: UIViewController {
|
||||
|
||||
@IBAction func streamTouched(_ sender: Any) {
|
||||
if !isStreaming {
|
||||
SAPlayer.shared.initializeRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
streamButton.setTitle("Cancel streaming", for: .normal)
|
||||
downloadButton.isEnabled = false
|
||||
} else {
|
||||
|
||||
@@ -39,7 +39,7 @@ pod 'SwiftAudioPlayer'
|
||||
To play remote audio:
|
||||
```swift
|
||||
let url = URL(string: "https://randomwebsite.com/audio.mp3")!
|
||||
SAPlayer.shared.initializeAudio(withRemoteUrl: url)
|
||||
SAPlayer.shared.startRemoteAudio(withRemoteUrl: url)
|
||||
SAPlayer.shared.play()
|
||||
```
|
||||
|
||||
@@ -121,8 +121,8 @@ Access the player and all of its fields and functions through `SAPlayer.shared`.
|
||||
### Playing Audio (Basic Commands)
|
||||
|
||||
To set up player with audio to play, use either:
|
||||
* `initializeSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo?)` to play audio that is saved on the device.
|
||||
* `initializeRemoteAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo?)` to play audio streamed from a remote location.
|
||||
* `startSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo?)` to play audio that is saved on the device.
|
||||
* `startRemoteAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo?)` to play audio streamed from a remote location.
|
||||
|
||||
Both of these expect a URL of the location of the audio and an optional media information to display on the lockscreen.
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class AudioDiskEngine: AudioEngine {
|
||||
audioSampleRate = Float(audioFormat?.sampleRate ?? 44100)
|
||||
audioLengthSeconds = Float(audioLengthSamples) / audioSampleRate
|
||||
duration = Duration(audioLengthSeconds)
|
||||
bufferedSeconds = SAAudioAvailabilityRange(startingNeedle: 0, durationLoadedByNetwork: duration, isPlayable: true)
|
||||
bufferedSeconds = SAAudioAvailabilityRange(startingNeedle: 0, durationLoadedByNetwork: duration, predictedDurationToLoad: duration, isPlayable: true)
|
||||
} else {
|
||||
Log.monitor("Could not load downloaded file with url: \(url)")
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ class AudioEngine: AudioEngineProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
var bufferedSecondsDebouncer: SAAudioAvailabilityRange = SAAudioAvailabilityRange(startingNeedle: 0.0, durationLoadedByNetwork: 0.0, isPlayable: false)
|
||||
var bufferedSeconds: SAAudioAvailabilityRange = SAAudioAvailabilityRange(startingNeedle: 0.0, durationLoadedByNetwork: 0.0, isPlayable: false) {
|
||||
var bufferedSecondsDebouncer: SAAudioAvailabilityRange = SAAudioAvailabilityRange(startingNeedle: 0.0, durationLoadedByNetwork: 0.0, predictedDurationToLoad: Double.greatestFiniteMagnitude, isPlayable: false)
|
||||
var bufferedSeconds: SAAudioAvailabilityRange = SAAudioAvailabilityRange(startingNeedle: 0.0, durationLoadedByNetwork: 0.0, predictedDurationToLoad: Double.greatestFiniteMagnitude, isPlayable: false) {
|
||||
didSet {
|
||||
if bufferedSeconds.startingNeedle == 0.0 && bufferedSeconds.durationLoadedByNetwork == 0.0 {
|
||||
bufferedSecondsDebouncer = bufferedSeconds
|
||||
@@ -154,7 +154,11 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
func updateIsPlaying() {
|
||||
if !bufferedSeconds.isPlayable {
|
||||
playingStatus = .buffering
|
||||
if bufferedSeconds.bufferingProgress > 0.999 {
|
||||
playingStatus = .ended
|
||||
} else {
|
||||
playingStatus = .buffering
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
let range = converter.pollNetworkAudioAvailabilityRange()
|
||||
isPlayable = (numberOfBuffersScheduledInTotal >= MIN_BUFFERS_TO_BE_PLAYABLE && range.1 > 0) && predictedStreamDuration > 0
|
||||
Log.debug("loaded \(range), numberOfBuffersScheduledInTotal: \(numberOfBuffersScheduledInTotal), isPlayable: \(isPlayable)")
|
||||
bufferedSeconds = SAAudioAvailabilityRange(startingNeedle: range.0, durationLoadedByNetwork: range.1, isPlayable: isPlayable)
|
||||
bufferedSeconds = SAAudioAvailabilityRange(startingNeedle: range.0, durationLoadedByNetwork: range.1, predictedDurationToLoad: predictedStreamDuration, isPlayable: isPlayable)
|
||||
}
|
||||
|
||||
private func updateNeedle() {
|
||||
@@ -236,12 +236,6 @@ class AudioStreamEngine: AudioEngine {
|
||||
var currentTime = TimeInterval(playerTime.sampleTime) / playerTime.sampleRate
|
||||
currentTime = currentTime > 0 ? currentTime : 0
|
||||
|
||||
if currentTime > predictedStreamDuration {
|
||||
Log.info("reached end of audio")
|
||||
seek(toNeedle: 0)
|
||||
pause()
|
||||
playingStatus = .ended
|
||||
}
|
||||
needle = (currentTime + currentTimeOffset)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,15 @@ import Foundation
|
||||
public struct SAAudioAvailabilityRange {
|
||||
let startingNeedle: Needle
|
||||
let durationLoadedByNetwork: Duration
|
||||
let predictedDurationToLoad: Duration
|
||||
let isPlayable: Bool
|
||||
|
||||
public var bufferingProgress: Double {
|
||||
get {
|
||||
return (startingNeedle + durationLoadedByNetwork) / predictedDurationToLoad
|
||||
}
|
||||
}
|
||||
|
||||
public var startingBufferTimePositon: Double {
|
||||
get {
|
||||
return startingNeedle
|
||||
@@ -52,4 +59,8 @@ public struct SAAudioAvailabilityRange {
|
||||
public func contains(_ needle: Double) -> Bool {
|
||||
return needle >= startingNeedle && (needle - startingNeedle) < durationLoadedByNetwork
|
||||
}
|
||||
|
||||
public func isCompletelyBuffered() -> Bool {
|
||||
return startingNeedle + durationLoadedByNetwork >= predictedDurationToLoad
|
||||
}
|
||||
}
|
||||
|
||||
+22
-4
@@ -198,21 +198,27 @@ public class SAPlayer {
|
||||
//MARK: - External Player Controls
|
||||
extension SAPlayer {
|
||||
/**
|
||||
Toggles between the play and pause state of the player if the player is not buffering (thus is playable).
|
||||
Toggles between the play and pause state of the player. If nothing is playable (aka still in buffering state or no audio is initialized) no action will be taken. Please call `startSavedAudio` or `startRemoteAudio` to set up the player with audio before this.
|
||||
|
||||
- Note: If you are streaming, wait till the status from `SAPlayer.Updates.PlayingStatus` is not `.buffering`.
|
||||
*/
|
||||
public func togglePlayAndPause() {
|
||||
presenter.handleTogglePlayingAndPausing()
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to play the player even if nothing playable is loaded (aka still in buffering state or no audio is initialized).
|
||||
Attempts to play the player. If nothing is playable (aka still in buffering state or no audio is initialized) no action will be taken. Please call `startSavedAudio` or `startRemoteAudio` to set up the player with audio before this.
|
||||
|
||||
- Note: If you are streaming, wait till the status from `SAPlayer.Updates.PlayingStatus` is not `.buffering`.
|
||||
*/
|
||||
public func play() {
|
||||
presenter.handlePlay()
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to pause the player even if nothing playable is loaded (aka still in buffering state or no audio is initialized).
|
||||
Attempts to pause the player. If nothing is playable (aka still in buffering state or no audio is initialized) no action will be taken. Please call `startSavedAudio` or `startRemoteAudio` to set up the player with audio before this.
|
||||
|
||||
- Note:If you are streaming, wait till the status from `SAPlayer.Updates.PlayingStatus` is not `.buffering`.
|
||||
*/
|
||||
public func pause() {
|
||||
presenter.handlePause()
|
||||
@@ -264,13 +270,19 @@ extension SAPlayer {
|
||||
- Parameter withSavedUrl: The URL of the audio saved on the device.
|
||||
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
|
||||
*/
|
||||
public func startSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlaySavedAudio(withSavedUrl: url)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "startSavedAudio")
|
||||
public func initializeSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlaySavedAudio(withSavedUrl: url)
|
||||
}
|
||||
|
||||
/**
|
||||
Sets up player to play audio that will be streamed from a remote location.
|
||||
Sets up player to play audio that will be streamed from a remote location. After this is called, it will connect to the server and start to receive and process data. The player is not playable the SAAudioAvailabilityRange notifies that player is ready for playing (you can subscribe to these updates through `SAPlayer.Updates.StreamingBuffer`). You can alternatively see when the player is available to play by subscribing to `SAPlayer.Updates.PlayingStatus` and waiting for a status that isn't `.buffering`.
|
||||
|
||||
- Important: If intending to use [AVAudioUnit](https://developer.apple.com/documentation/avfoundation/audio_track_engineering/audio_engine_building_blocks/audio_enhancements) audio modifiers during playback, the list of audio modifiers under `SAPlayer.shared.audioModifiers` must be finalized before calling this function. After all realtime audio manipulations within the this will be effective.
|
||||
|
||||
@@ -279,6 +291,12 @@ extension SAPlayer {
|
||||
- Parameter withRemoteUrl: The URL of the remote audio.
|
||||
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
|
||||
*/
|
||||
public func startRemoteAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlayStreamedAudio(withRemoteUrl: url)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "startRemoteAudio")
|
||||
public func initializeRemoteAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo? = nil) {
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlayStreamedAudio(withRemoteUrl: url)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '2.5.2'
|
||||
s.version = '2.8.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.
|
||||
|
||||
Reference in New Issue
Block a user