This commit is contained in:
advplyr 2023-02-19 14:51:41 -06:00
commit 152904b7c7
7 changed files with 73 additions and 63 deletions

View file

@ -783,8 +783,6 @@ export default {
console.log('received metadata update', data) console.log('received metadata update', data)
if (data.currentRate && data.currentRate > 0) this.playbackSpeed = data.currentRate
this.timeupdate() this.timeupdate()
}, },
// When a playback session is started the native android/ios will send the session // When a playback session is started the native android/ios will send the session

View file

@ -159,7 +159,7 @@ public class AbsAudioPlayer: CAPPlugin {
@objc func sendMetadata() { @objc func sendMetadata() {
self.notifyListeners("onPlayingUpdate", data: [ "value": !PlayerHandler.paused ]) self.notifyListeners("onPlayingUpdate", data: [ "value": !PlayerHandler.paused ])
if let metadata = PlayerHandler.getMetdata() { if let metadata = try? PlayerHandler.getMetdata()?.asDictionary() {
self.notifyListeners("onMetadata", data: metadata) self.notifyListeners("onMetadata", data: metadata)
} }
} }
@ -211,19 +211,19 @@ public class AbsAudioPlayer: CAPPlugin {
@objc func getSleepTimerTime(_ call: CAPPluginCall) { @objc func getSleepTimerTime(_ call: CAPPluginCall) {
call.resolve([ call.resolve([
"value": PlayerHandler.getSleepTimeRemaining() "value": PlayerHandler.getSleepTimeRemaining() ?? 0
]) ])
} }
@objc func sendSleepTimerEnded() { @objc func sendSleepTimerEnded() {
self.notifyListeners("onSleepTimerEnded", data: [ self.notifyListeners("onSleepTimerEnded", data: [
"value": PlayerHandler.getCurrentTime() "value": PlayerHandler.getCurrentTime() ?? 0
]) ])
} }
@objc func sendSleepTimerSet() { @objc func sendSleepTimerSet() {
self.notifyListeners("onSleepTimerSet", data: [ self.notifyListeners("onSleepTimerSet", data: [
"value": PlayerHandler.getSleepTimeRemaining() "value": PlayerHandler.getSleepTimeRemaining() ?? 0
]) ])
} }

View file

@ -7,8 +7,8 @@
import Foundation import Foundation
class PlaybackMetadata: Codable { struct PlaybackMetadata: Codable {
var duration: Double = 0 let duration: Double
var currentTime: Double = 0 let currentTime: Double
var playerState: PlayerState = PlayerState.IDLE let playerState: PlayerState
} }

View file

@ -7,9 +7,9 @@
import Foundation import Foundation
enum PlayerState: Codable { enum PlayerState: String, Codable {
case IDLE case idle = "IDLE"
case BUFFERING case buffering = "BUFFERING"
case READY case ready = "READY"
case ENDED case ended = "ENDED"
} }

View file

@ -10,21 +10,26 @@ import AVFoundation
import UIKit import UIKit
import MediaPlayer import MediaPlayer
enum PlayMethod:Int { enum PlayMethod: Int {
case directplay = 0 case directplay = 0
case directstream = 1 case directstream = 1
case transcode = 2 case transcode = 2
case local = 3 case local = 3
} }
enum PlayerStatus: Int {
case uninitialized = -1
case paused = 0
case playing = 1
}
class AudioPlayer: NSObject { class AudioPlayer: NSObject {
internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue") internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue")
internal let logger = AppLogger(category: "AudioPlayer") internal let logger = AppLogger(category: "AudioPlayer")
// enums and @objc are not compatible private var status: PlayerStatus
@objc dynamic var status: Int internal var rate: Float
@objc dynamic var rate: Float
private var tmpRate: Float = 1.0 private var tmpRate: Float = 1.0
private var playerContext = 0 private var playerContext = 0
@ -57,9 +62,9 @@ class AudioPlayer: NSObject {
self.playWhenReady = playWhenReady self.playWhenReady = playWhenReady
self.initialPlaybackRate = playbackRate self.initialPlaybackRate = playbackRate
self.audioPlayer = AVQueuePlayer() self.audioPlayer = AVQueuePlayer()
self.audioPlayer.automaticallyWaitsToMinimizeStalling = false self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
self.sessionId = sessionId self.sessionId = sessionId
self.status = -1 self.status = .uninitialized
self.rate = 0.0 self.rate = 0.0
self.tmpRate = playbackRate self.tmpRate = playbackRate
@ -141,7 +146,7 @@ class AudioPlayer: NSObject {
} }
public func isInitialized() -> Bool { public func isInitialized() -> Bool {
return self.status != -1 return self.status != .uninitialized
} }
public func getPlaybackSession() -> PlaybackSession? { public func getPlaybackSession() -> PlaybackSession? {
@ -156,6 +161,9 @@ class AudioPlayer: NSObject {
let trackEnd = startOffset + duration let trackEnd = startOffset + duration
if (time < trackEnd.rounded(.down)) { if (time < trackEnd.rounded(.down)) {
return index return index
} else if (index == self.allPlayerItems.count - 1) {
// Seeking past end of last item
return index
} }
} }
return 0 return 0
@ -254,7 +262,7 @@ class AudioPlayer: NSObject {
logger.log("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)") logger.log("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
// Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session // Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session
let firstReady = self.status < 0 let firstReady = self.status == .uninitialized
if firstReady && !self.playWhenReady { if firstReady && !self.playWhenReady {
// Seek is async, and if we call this when also pressing play, we will get weird jumps in the scrub bar depending on timing // Seek is async, and if we call this when also pressing play, we will get weird jumps in the scrub bar depending on timing
// Seeking to the correct position happens during play() // Seeking to the correct position happens during play()
@ -267,7 +275,7 @@ class AudioPlayer: NSObject {
self.play(allowSeekBack: false, isInitializing: true) self.play(allowSeekBack: false, isInitializing: true)
} else { } else {
// Mark the player as ready // Mark the player as ready
self.status = 0 self.status = .paused
} }
} else if (playerItem.status == .failed) { } else if (playerItem.status == .failed) {
logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")") logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
@ -327,8 +335,8 @@ class AudioPlayer: NSObject {
self.audioPlayer.play() self.audioPlayer.play()
self.audioPlayer.rate = self.tmpRate self.audioPlayer.rate = self.tmpRate
} }
self.status = 1 self.status = .playing
// Update the progress // Update the progress
self.updateNowPlaying() self.updateNowPlaying()
@ -351,27 +359,18 @@ class AudioPlayer: NSObject {
} }
} }
self.status = 0
self.status = .paused
updateNowPlaying() updateNowPlaying()
self.startPausedTimer() self.startPausedTimer()
} }
public func seek(_ to: Double, from: String) { public func seek(_ to: Double, from: String) {
let continuePlaying = rate > 0.0 logger.log("SEEK: Seek to \(to) from \(from)")
self.pause()
logger.log("SEEK: Seek to \(to) from \(from) and continuePlaying(\(continuePlaying)")
guard let playbackSession = self.getPlaybackSession() else { return } guard let playbackSession = self.getPlaybackSession() else { return }
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
let ctso = currentTrack.startOffset ?? 0.0
let trackEnd = ctso + currentTrack.duration
logger.log("SEEK: Seek current track END = \(trackEnd)")
let indexOfSeek = getItemIndexForTime(time: to) let indexOfSeek = getItemIndexForTime(time: to)
logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)") logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
@ -383,8 +382,6 @@ class AudioPlayer: NSObject {
playbackSession.currentTime = to playbackSession.currentTime = to
} }
self.playWhenReady = continuePlaying // Only playWhenReady if already playing
self.status = -1
let playerItems = self.allPlayerItems[indexOfSeek..<self.allPlayerItems.count] let playerItems = self.allPlayerItems[indexOfSeek..<self.allPlayerItems.count]
DispatchQueue.runOnMainQueue { DispatchQueue.runOnMainQueue {
@ -393,26 +390,33 @@ class AudioPlayer: NSObject {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last) self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
} }
} }
seekInCurrentTrack(to: to, playbackSession: playbackSession)
setupQueueItemStatusObserver() setupQueueItemStatusObserver()
} else { } else {
logger.log("SEEK: Seeking in current item \(to)") seekInCurrentTrack(to: to, playbackSession: playbackSession)
let currentTrackStartOffset = playbackSession.audioTracks[self.currentTrackIndex].startOffset ?? 0.0 }
let seekTime = to - currentTrackStartOffset
// Only for use in here where we handle track selection
func seekInCurrentTrack(to: Double, playbackSession: PlaybackSession) {
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
let ctso = currentTrack.startOffset ?? 0.0
let trackEnd = ctso + currentTrack.duration
logger.log("SEEK: Seeking in current item \(to) (track START = \(ctso) END = \(trackEnd))")
let boundedTime = min(max(to, ctso), trackEnd)
let seekTime = boundedTime - ctso
DispatchQueue.runOnMainQueue { DispatchQueue.runOnMainQueue {
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
self?.logger.log("SEEK: Completion handler called and continuePlaying(\(continuePlaying)") self?.logger.log("SEEK: Completion handler called")
guard completed else { guard completed else {
self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)") self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)")
return return
} }
guard let self = self else { return } guard let self = self else { return }
if continuePlaying {
self.resumePlayback()
}
self.updateNowPlaying() self.updateNowPlaying()
} }
} }
@ -463,9 +467,18 @@ class AudioPlayer: NSObject {
} }
public func isPlaying() -> Bool { public func isPlaying() -> Bool {
return self.status > 0 return self.status == .playing
} }
public func getPlayerState() -> PlayerState {
switch status {
case .uninitialized:
return PlayerState.buffering
case .paused, .playing:
return PlayerState.ready
}
}
// MARK: - Private // MARK: - Private
private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? { private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? {
guard let playbackSession = self.getPlaybackSession() else { return nil } guard let playbackSession = self.getPlaybackSession() else { return nil }

View file

@ -121,16 +121,15 @@ class PlayerHandler {
player.seek(amount, from: "handler") player.seek(amount, from: "handler")
} }
public static func getMetdata() -> [String: Any]? { public static func getMetdata() -> PlaybackMetadata? {
guard let player = player else { return nil } guard let player = player else { return nil }
guard player.isInitialized() else { return nil } guard player.isInitialized() else { return nil }
return [ return PlaybackMetadata(
"duration": player.getDuration(), duration: player.getDuration() ?? 0,
"currentTime": player.getCurrentTime(), currentTime: player.getCurrentTime() ?? 0,
"playerState": !paused, playerState: player.getPlayerState()
"currentRate": player.rate, )
]
} }
// MARK: - Helper logic // MARK: - Helper logic

View file

@ -171,7 +171,7 @@ class ApiClient {
] ]
] ]
ApiClient.postResource(endpoint: endpoint, parameters: parameters, decodable: PlaybackSession.self) { obj in ApiClient.postResource(endpoint: endpoint, parameters: parameters, decodable: PlaybackSession.self) { obj in
var session = obj let session = obj
session.serverConnectionConfigId = Store.serverConfig!.id session.serverConnectionConfigId = Store.serverConfig!.id
session.serverAddress = Store.serverConfig!.address session.serverAddress = Store.serverConfig!.address