diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue index 76a1f1a4..1564ba08 100644 --- a/components/app/AudioPlayer.vue +++ b/components/app/AudioPlayer.vue @@ -783,8 +783,6 @@ export default { console.log('received metadata update', data) - if (data.currentRate && data.currentRate > 0) this.playbackSpeed = data.currentRate - this.timeupdate() }, // When a playback session is started the native android/ios will send the session diff --git a/ios/App/App/plugins/AbsAudioPlayer.swift b/ios/App/App/plugins/AbsAudioPlayer.swift index 537a4524..78ceb12a 100644 --- a/ios/App/App/plugins/AbsAudioPlayer.swift +++ b/ios/App/App/plugins/AbsAudioPlayer.swift @@ -159,7 +159,7 @@ public class AbsAudioPlayer: CAPPlugin { @objc func sendMetadata() { self.notifyListeners("onPlayingUpdate", data: [ "value": !PlayerHandler.paused ]) - if let metadata = PlayerHandler.getMetdata() { + if let metadata = try? PlayerHandler.getMetdata()?.asDictionary() { self.notifyListeners("onMetadata", data: metadata) } } @@ -211,19 +211,19 @@ public class AbsAudioPlayer: CAPPlugin { @objc func getSleepTimerTime(_ call: CAPPluginCall) { call.resolve([ - "value": PlayerHandler.getSleepTimeRemaining() + "value": PlayerHandler.getSleepTimeRemaining() ?? 0 ]) } @objc func sendSleepTimerEnded() { self.notifyListeners("onSleepTimerEnded", data: [ - "value": PlayerHandler.getCurrentTime() + "value": PlayerHandler.getCurrentTime() ?? 0 ]) } @objc func sendSleepTimerSet() { self.notifyListeners("onSleepTimerSet", data: [ - "value": PlayerHandler.getSleepTimeRemaining() + "value": PlayerHandler.getSleepTimeRemaining() ?? 0 ]) } diff --git a/ios/App/Shared/models/PlaybackMetadata.swift b/ios/App/Shared/models/PlaybackMetadata.swift index 65c41560..d5ad08fe 100644 --- a/ios/App/Shared/models/PlaybackMetadata.swift +++ b/ios/App/Shared/models/PlaybackMetadata.swift @@ -7,8 +7,8 @@ import Foundation -class PlaybackMetadata: Codable { - var duration: Double = 0 - var currentTime: Double = 0 - var playerState: PlayerState = PlayerState.IDLE +struct PlaybackMetadata: Codable { + let duration: Double + let currentTime: Double + let playerState: PlayerState } diff --git a/ios/App/Shared/models/PlayerState.swift b/ios/App/Shared/models/PlayerState.swift index 02599f17..89ca9a58 100644 --- a/ios/App/Shared/models/PlayerState.swift +++ b/ios/App/Shared/models/PlayerState.swift @@ -7,9 +7,9 @@ import Foundation -enum PlayerState: Codable { - case IDLE - case BUFFERING - case READY - case ENDED +enum PlayerState: String, Codable { + case idle = "IDLE" + case buffering = "BUFFERING" + case ready = "READY" + case ended = "ENDED" } diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 46fafaaf..302d05a9 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -10,21 +10,26 @@ import AVFoundation import UIKit import MediaPlayer -enum PlayMethod:Int { +enum PlayMethod: Int { case directplay = 0 case directstream = 1 case transcode = 2 case local = 3 } +enum PlayerStatus: Int { + case uninitialized = -1 + case paused = 0 + case playing = 1 +} + class AudioPlayer: NSObject { internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue") internal let logger = AppLogger(category: "AudioPlayer") - - // enums and @objc are not compatible - @objc dynamic var status: Int - @objc dynamic var rate: Float - + + private var status: PlayerStatus + internal var rate: Float + private var tmpRate: Float = 1.0 private var playerContext = 0 @@ -57,9 +62,9 @@ class AudioPlayer: NSObject { self.playWhenReady = playWhenReady self.initialPlaybackRate = playbackRate self.audioPlayer = AVQueuePlayer() - self.audioPlayer.automaticallyWaitsToMinimizeStalling = false + self.audioPlayer.automaticallyWaitsToMinimizeStalling = true self.sessionId = sessionId - self.status = -1 + self.status = .uninitialized self.rate = 0.0 self.tmpRate = playbackRate @@ -141,7 +146,7 @@ class AudioPlayer: NSObject { } public func isInitialized() -> Bool { - return self.status != -1 + return self.status != .uninitialized } public func getPlaybackSession() -> PlaybackSession? { @@ -156,6 +161,9 @@ class AudioPlayer: NSObject { let trackEnd = startOffset + duration if (time < trackEnd.rounded(.down)) { return index + } else if (index == self.allPlayerItems.count - 1) { + // Seeking past end of last item + return index } } return 0 @@ -254,7 +262,7 @@ class AudioPlayer: NSObject { 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 - let firstReady = self.status < 0 + let firstReady = self.status == .uninitialized 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 // Seeking to the correct position happens during play() @@ -267,7 +275,7 @@ class AudioPlayer: NSObject { self.play(allowSeekBack: false, isInitializing: true) } else { // Mark the player as ready - self.status = 0 + self.status = .paused } } else if (playerItem.status == .failed) { logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")") @@ -327,8 +335,8 @@ class AudioPlayer: NSObject { self.audioPlayer.play() self.audioPlayer.rate = self.tmpRate } - self.status = 1 - + self.status = .playing + // Update the progress self.updateNowPlaying() @@ -351,27 +359,18 @@ class AudioPlayer: NSObject { } } - self.status = 0 + self.status = .paused updateNowPlaying() self.startPausedTimer() } public func seek(_ to: Double, from: String) { - let continuePlaying = rate > 0.0 - - self.pause() - - logger.log("SEEK: Seek to \(to) from \(from) and continuePlaying(\(continuePlaying)") - + logger.log("SEEK: Seek to \(to) from \(from)") + 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) logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)") @@ -383,8 +382,6 @@ class AudioPlayer: NSObject { playbackSession.currentTime = to } - self.playWhenReady = continuePlaying // Only playWhenReady if already playing - self.status = -1 let playerItems = self.allPlayerItems[indexOfSeek.. 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 private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? { guard let playbackSession = self.getPlaybackSession() else { return nil } diff --git a/ios/App/Shared/player/PlayerHandler.swift b/ios/App/Shared/player/PlayerHandler.swift index dbcca12a..aa402489 100644 --- a/ios/App/Shared/player/PlayerHandler.swift +++ b/ios/App/Shared/player/PlayerHandler.swift @@ -121,16 +121,15 @@ class PlayerHandler { player.seek(amount, from: "handler") } - public static func getMetdata() -> [String: Any]? { + public static func getMetdata() -> PlaybackMetadata? { guard let player = player else { return nil } guard player.isInitialized() else { return nil } - - return [ - "duration": player.getDuration(), - "currentTime": player.getCurrentTime(), - "playerState": !paused, - "currentRate": player.rate, - ] + + return PlaybackMetadata( + duration: player.getDuration() ?? 0, + currentTime: player.getCurrentTime() ?? 0, + playerState: player.getPlayerState() + ) } // MARK: - Helper logic diff --git a/ios/App/Shared/util/ApiClient.swift b/ios/App/Shared/util/ApiClient.swift index c193f913..740e0eba 100644 --- a/ios/App/Shared/util/ApiClient.swift +++ b/ios/App/Shared/util/ApiClient.swift @@ -171,7 +171,7 @@ class ApiClient { ] ] ApiClient.postResource(endpoint: endpoint, parameters: parameters, decodable: PlaybackSession.self) { obj in - var session = obj + let session = obj session.serverConnectionConfigId = Store.serverConfig!.id session.serverAddress = Store.serverConfig!.address