From cbd74d08e1e65d1754a5bc85b04182f0620d056f Mon Sep 17 00:00:00 2001 From: Joakim Ramer Date: Thu, 9 Feb 2023 02:38:56 +0100 Subject: [PATCH 1/5] fix: seek pauses playback --- components/app/AudioPlayer.vue | 2 - ios/App/App/plugins/AbsAudioPlayer.swift | 2 +- ios/App/Shared/models/PlaybackMetadata.swift | 8 +-- ios/App/Shared/models/PlayerState.swift | 10 ++-- ios/App/Shared/player/AudioPlayer.swift | 59 +++++++++++--------- ios/App/Shared/player/PlayerHandler.swift | 15 +++-- 6 files changed, 49 insertions(+), 47 deletions(-) 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 76f1e863..c9b05ba8 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) } } 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 da9c7ffc..4dec9851 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 @@ -59,7 +64,7 @@ class AudioPlayer: NSObject { self.audioPlayer = AVQueuePlayer() self.audioPlayer.automaticallyWaitsToMinimizeStalling = false 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? { @@ -254,7 +259,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 +272,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 +332,8 @@ class AudioPlayer: NSObject { self.audioPlayer.play() self.audioPlayer.rate = self.tmpRate } - self.status = 1 - + self.status = .playing + // Update the progress self.updateNowPlaying() @@ -351,20 +356,16 @@ 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] @@ -383,8 +384,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 { + // Other PlayerStates doesn't translate to player status + switch status { + case .uninitialized: + return PlayerState.buffering + default: + 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 723cfc3d..cb846fd8 100644 --- a/ios/App/Shared/player/PlayerHandler.swift +++ b/ios/App/Shared/player/PlayerHandler.swift @@ -118,16 +118,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 From ef46ad7a938325be0f43f7738dfb7a9b8944b411 Mon Sep 17 00:00:00 2001 From: Joakim Ramer Date: Thu, 9 Feb 2023 02:40:21 +0100 Subject: [PATCH 2/5] fix: removes some warnings --- ios/App/App/plugins/AbsAudioPlayer.swift | 6 +++--- ios/App/Shared/util/ApiClient.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/App/App/plugins/AbsAudioPlayer.swift b/ios/App/App/plugins/AbsAudioPlayer.swift index c9b05ba8..954af424 100644 --- a/ios/App/App/plugins/AbsAudioPlayer.swift +++ b/ios/App/App/plugins/AbsAudioPlayer.swift @@ -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/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 From b73763a9ec4feb947371fb0f1f2d7616bcc8cf38 Mon Sep 17 00:00:00 2001 From: Joakim Ramer Date: Thu, 16 Feb 2023 22:53:37 +0100 Subject: [PATCH 3/5] fix: Fixes streaming issues --- ios/App/Shared/player/AudioPlayer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 4dec9851..3d9de780 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -62,7 +62,7 @@ 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 = .uninitialized self.rate = 0.0 From f4b05a323633e990947413611a137cce112aee32 Mon Sep 17 00:00:00 2001 From: Joakim Ramer Date: Thu, 16 Feb 2023 23:00:33 +0100 Subject: [PATCH 4/5] Clarify state --- ios/App/Shared/player/AudioPlayer.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 3d9de780..a9bd98c5 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -462,11 +462,10 @@ class AudioPlayer: NSObject { } public func getPlayerState() -> PlayerState { - // Other PlayerStates doesn't translate to player status switch status { case .uninitialized: return PlayerState.buffering - default: + case .paused, .playing: return PlayerState.ready } } From 3318f3501c547e521cf47343384232a31ca2eef5 Mon Sep 17 00:00:00 2001 From: Joakim Ramer Date: Sun, 19 Feb 2023 17:42:45 +0100 Subject: [PATCH 5/5] fix: seek when changing track --- ios/App/Shared/player/AudioPlayer.swift | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index a9bd98c5..854b463a 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -161,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 @@ -367,12 +370,7 @@ class AudioPlayer: NSObject { 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)") @@ -392,13 +390,24 @@ class AudioPlayer: NSObject { self.audioPlayer.insert(item, after:self.audioPlayer.items().last) } } - + + seekInCurrentTrack(to: to, playbackSession: playbackSession) + setupQueueItemStatusObserver() } else { - logger.log("SEEK: Seeking in current item \(to)") - let currentTrackStartOffset = playbackSession.audioTracks[self.currentTrackIndex].startOffset ?? 0.0 - let seekTime = to - currentTrackStartOffset - + seekInCurrentTrack(to: to, playbackSession: playbackSession) + } + + // 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 { self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in self?.logger.log("SEEK: Completion handler called")