mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-04 01:54:33 +02:00
Merge branch 'master' of https://github.com/advplyr/audiobookshelf-app
This commit is contained in:
commit
152904b7c7
7 changed files with 73 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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..<self.allPlayerItems.count]
|
||||
|
||||
DispatchQueue.runOnMainQueue {
|
||||
|
@ -393,26 +390,33 @@ 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 and continuePlaying(\(continuePlaying)")
|
||||
self?.logger.log("SEEK: Completion handler called")
|
||||
guard completed else {
|
||||
self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)")
|
||||
return
|
||||
}
|
||||
guard let self = self else { return }
|
||||
|
||||
if continuePlaying {
|
||||
self.resumePlayback()
|
||||
}
|
||||
|
||||
self.updateNowPlaying()
|
||||
}
|
||||
}
|
||||
|
@ -463,9 +467,18 @@ class AudioPlayer: NSObject {
|
|||
}
|
||||
|
||||
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
|
||||
private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? {
|
||||
guard let playbackSession = self.getPlaybackSession() else { return nil }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue