Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ebd7fa7fe | |||
| 5197a16023 | |||
| 159627c63e | |||
| 07230cce1a | |||
| a33aee80d1 | |||
| e1d3da1ddb | |||
| 8c2524d990 | |||
| be1b7aa05f |
@@ -117,7 +117,6 @@ class ViewController: UIViewController {
|
||||
|
||||
_ = SAPlayer.Updates.ElapsedTime.subscribe { [weak self] (url, position) in
|
||||
guard let self = self else { return }
|
||||
guard self.beingSeeked == false else { return }
|
||||
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
|
||||
|
||||
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
|
||||
@@ -174,6 +173,10 @@ class ViewController: UIViewController {
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Loading", for: .normal)
|
||||
return
|
||||
case .ended:
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Done", for: .normal)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ iOS 10.0 and higher.
|
||||
|
||||
1. Clone repo
|
||||
2. CD to directory
|
||||
3. run `pod install` in terminal
|
||||
4. Run
|
||||
3. Run `pod install` in terminal
|
||||
4. Build and run
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -252,7 +252,7 @@ Changes in the duration of the current initialized audio. Especially helpful for
|
||||
### PlayingStatus
|
||||
Payload = `SAPlayingStatus`
|
||||
|
||||
Changes in the playing status of the player. Can be one of the following 3: `playing`, `paused`, `buffering`.
|
||||
Changes in the playing status of the player. Can be one of the following 4: `playing`, `paused`, `buffering`, `ended` (audio ended).
|
||||
|
||||
### StreamingBuffer
|
||||
Payload = `SAAudioAvailabilityRange`
|
||||
|
||||
@@ -98,7 +98,7 @@ class AudioDiskEngine: AudioEngine {
|
||||
if state == .resumed {
|
||||
state = .suspended
|
||||
}
|
||||
delegate?.didEndPlaying()
|
||||
playingStatus = .ended
|
||||
}
|
||||
|
||||
guard audioSampleRate != 0 else {
|
||||
|
||||
@@ -27,6 +27,7 @@ import Foundation
|
||||
import AVFoundation
|
||||
|
||||
protocol AudioEngineProtocol {
|
||||
var engine: AVAudioEngine { get set }
|
||||
func play()
|
||||
func pause()
|
||||
func seek(toNeedle needle: Needle)
|
||||
@@ -42,7 +43,7 @@ class AudioEngine: AudioEngineProtocol {
|
||||
weak var delegate:AudioEngineDelegate?
|
||||
let key:Key
|
||||
|
||||
let engine = AVAudioEngine()
|
||||
var engine = AVAudioEngine()
|
||||
let playerNode = AVAudioPlayerNode()
|
||||
|
||||
var timer: Timer?
|
||||
@@ -77,6 +78,10 @@ class AudioEngine: AudioEngineProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
if status == .ended {
|
||||
delegate?.didEndPlaying()
|
||||
}
|
||||
|
||||
AudioClockDirector.shared.audioPlayingStatusWasChanged(key, status: status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
Log.info("reached end of audio")
|
||||
seek(toNeedle: 0)
|
||||
pause()
|
||||
delegate?.didEndPlaying()
|
||||
playingStatus = .ended
|
||||
}
|
||||
needle = (currentTime + currentTimeOffset)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,15 @@ class AudioThrottler: AudioThrottleable {
|
||||
private var networkData: [NetworkDataWrapper] = []
|
||||
var shouldThrottle = false
|
||||
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
|
||||
|
||||
//This will be sent once at beginning of stream and every network seek
|
||||
var totalBytesExpected: Int64? {
|
||||
didSet {
|
||||
if let bytes = totalBytesExpected {
|
||||
delegate?.didUpdate(totalBytesExpected: Int64(byteOffsetBecauseOfSeek) + bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var largestPollingOffsetDifference: UInt64 = 1
|
||||
|
||||
@@ -110,9 +118,8 @@ class AudioThrottler: AudioThrottleable {
|
||||
Log.debug("received stream data of size \(pto.getData().count) and progress: \(pto.getProgress())")
|
||||
self.delegate?.didUpdate(networkStreamProgress: pto.getProgress())
|
||||
|
||||
if self.totalBytesExpected == nil, let totalBytesExpected = pto.getTotalBytesExpected() {
|
||||
if let totalBytesExpected = pto.getTotalBytesExpected() {
|
||||
self.totalBytesExpected = totalBytesExpected
|
||||
self.delegate?.didUpdate(totalBytesExpected: totalBytesExpected)
|
||||
}
|
||||
|
||||
let lastItem = self.networkData.last
|
||||
@@ -152,30 +159,34 @@ class AudioThrottler: AudioThrottleable {
|
||||
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() {
|
||||
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)")
|
||||
Log.debug("already sent offset: \(offset) within network packet of range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
|
||||
|
||||
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 {
|
||||
if !next.alreadySent {
|
||||
Log.info("Sending next network packet with range: \(next.startOffset) to \(next.endOffset), have sent \(bytesSent) bytes so far from \(largestPollingOffsetDifference) bytes")
|
||||
next.alreadySent = true
|
||||
delegate?.shouldProcess(networkData: next.data)
|
||||
bytesSent += next.byteCount
|
||||
current = next
|
||||
} else {
|
||||
return
|
||||
}
|
||||
bytesSent += next.byteCount
|
||||
current = next
|
||||
} else {
|
||||
Log.debug("next package doesn't exist, bytes sent so far: \(bytesSent)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Log.debug("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
|
||||
delegate?.shouldProcess(networkData: wrappedNetworkData.data)
|
||||
Log.info("Found network packet to send with range: \(wrappedNetworkData.startOffset) to \(wrappedNetworkData.endOffset)")
|
||||
wrappedNetworkData.alreadySent = true
|
||||
delegate?.shouldProcess(networkData: wrappedNetworkData.data)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,9 @@ class AudioParser: AudioParsable {
|
||||
return max(AVAudioPacketCount(parsedAudioHeaderPacketCount), AVAudioPacketCount(audioPackets.count))
|
||||
}
|
||||
|
||||
guard let sizeOfFileInBytes = expectedFileSizeInBytes, let bytesPerPacket = averageBytesPerPacket else {
|
||||
let sizeOfFileInBytes: UInt64 = expectedFileSizeInBytes != nil ? expectedFileSizeInBytes! : 0
|
||||
|
||||
guard let bytesPerPacket = averageBytesPerPacket else {
|
||||
return AVAudioPacketCount(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,4 +30,5 @@ public enum SAPlayingStatus {
|
||||
case playing
|
||||
case paused
|
||||
case buffering
|
||||
case ended
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ protocol LockScreenViewProtocol {
|
||||
}
|
||||
|
||||
extension LockScreenViewProtocol {
|
||||
func clearLockScreenInfo() {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
func setLockScreenInfo(withMediaInfo info: SALockScreenInfo?, duration: Duration) {
|
||||
var nowPlayingInfo:[String : Any] = [:]
|
||||
|
||||
@@ -247,14 +247,20 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
guard let totalBytesExpected = totalBytesExpectedForCurrentStream, totalBytesExpected > 0 else {
|
||||
guard var totalBytesExpected = totalBytesExpectedForCurrentStream else {
|
||||
Log.monitor("should not be called 223r2")
|
||||
return
|
||||
}
|
||||
|
||||
if totalBytesExpected <= 0 {
|
||||
totalBytesExpected = totalBytesReceived
|
||||
}
|
||||
|
||||
totalBytesReceived = totalBytesReceived + Int64(data.count)
|
||||
let progress = Double(totalBytesReceived)/Double(totalBytesExpected)
|
||||
|
||||
Log.test(totalBytesExpected)
|
||||
|
||||
Log.debug("network streaming progress \(progress)")
|
||||
self.progressCallback(id, StreamProgressDTO(progress: progress, data: data, totalBytesExpected: totalBytesExpected))
|
||||
}
|
||||
|
||||
@@ -35,6 +35,33 @@ public class SAPlayer {
|
||||
private var presenter: SAPlayerPresenter!
|
||||
private var player: AudioEngine?
|
||||
|
||||
/**
|
||||
Access the engine of the player. Engine is nil if player has not been initialized with audio.
|
||||
|
||||
- Important: Changes to the engine are not safe guarded, thus unknown behaviour can arise from changing the engine. Just be wary and read [documentation of AVAudioEngine](https://developer.apple.com/documentation/avfoundation/avaudioengine) well when modifying,
|
||||
*/
|
||||
public var engine: AVAudioEngine? {
|
||||
get {
|
||||
return player?.engine
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Corresponding to the overall volume of the player. Volume's default value is 1.0 and the range of valid values is 0.0 to 1.0. Volume is nil if no audio has been initialized yet.
|
||||
*/
|
||||
public var volume: Float? {
|
||||
get {
|
||||
return player?.engine.mainMixerNode.volume
|
||||
}
|
||||
|
||||
set {
|
||||
guard let value = newValue else { return }
|
||||
guard value >= 0.0 && value <= 1.0 else { return }
|
||||
|
||||
player?.engine.mainMixerNode.volume = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Corresponding to the skipping forward button on the media player on the lockscreen. Default is set to 30 seconds.
|
||||
*/
|
||||
@@ -77,6 +104,8 @@ public class SAPlayer {
|
||||
|
||||
/**
|
||||
Total duration of current audio initialized. Returns nil if no audio is initialized in player.
|
||||
|
||||
- Note: If you are streaming from a source that does not have an expected size at the beginning of a stream, such as live streams, this value will be constantly updating to best known value at the time.
|
||||
*/
|
||||
public var duration: Double? {
|
||||
get {
|
||||
@@ -254,6 +283,14 @@ extension SAPlayer {
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlayStreamedAudio(withRemoteUrl: url)
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the player to the state before initializing audio and setting media info.
|
||||
*/
|
||||
public func clear() {
|
||||
player = nil
|
||||
presenter.handleClear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -60,6 +60,18 @@ class SAPlayerPresenter {
|
||||
urlKeyMap[url.key] = url
|
||||
}
|
||||
|
||||
func handleClear() {
|
||||
needle = nil
|
||||
duration = nil
|
||||
key = nil
|
||||
mediaInfo = nil
|
||||
delegate?.clearLockScreenInfo()
|
||||
|
||||
AudioClockDirector.shared.detachFromChangesInDuration(withID: durationRef)
|
||||
AudioClockDirector.shared.detachFromChangesInNeedle(withID: needleRef)
|
||||
AudioClockDirector.shared.detachFromChangesInPlayingStatus(withID: playingStatusRef)
|
||||
}
|
||||
|
||||
func handlePlaySavedAudio(withSavedUrl url: URL) {
|
||||
attachForUpdates(url: url)
|
||||
delegate?.startAudioDownloaded(withSavedUrl: url)
|
||||
|
||||
@@ -172,7 +172,7 @@ extension SAPlayer {
|
||||
|
||||
- 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.
|
||||
- Parameter progress: Value from 0.0 to 1.0 indicating progress of download.
|
||||
- Parameter progress: Value from 0.0 to 1.0 indicating progress of download. This is unreliable for live streams that don't have an expected audio length from the beginning of the stream; in that case it will always be 1.0.
|
||||
- Returns: the id for the subscription in the case you would like to unsubscribe to updates for the closure.
|
||||
*/
|
||||
public static func subscribe(_ closure: @escaping (_ url: URL, _ progress: Double) -> ()) -> UInt {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '2.2.0'
|
||||
s.version = '2.5.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