advplyr.audiobookshelf-app/ios/App/Shared/player/PlayerHandler.swift

272 lines
9.2 KiB
Swift
Raw Normal View History

2022-04-11 16:29:19 +02:00
//
// PlayerHandler.swift
// App
//
// Created by Rasmus Krämer on 11.04.22.
//
import Foundation
2022-08-16 12:32:22 -04:00
import RealmSwift
2022-04-11 16:29:19 +02:00
class PlayerHandler {
private static var player: AudioPlayer?
2022-04-15 12:21:46 +02:00
private static var timer: Timer?
private static var lastSyncTime: Double = 0.0
2022-04-15 12:21:46 +02:00
2022-08-11 18:49:04 -05:00
public static var sleepTimerChapterStopTime: Int? = nil
2022-05-03 14:32:46 +02:00
private static var _remainingSleepTime: Int? = nil
public static var remainingSleepTime: Int? {
get {
return _remainingSleepTime
}
set(time) {
if time != nil && time! < 0 {
_remainingSleepTime = nil
} else {
_remainingSleepTime = time
}
if _remainingSleepTime == nil {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: _remainingSleepTime)
} else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: _remainingSleepTime)
}
}
}
private static var listeningTimePassedSinceLastSync: Double = 0.0
public static var paused: Bool {
get {
2022-05-03 15:01:30 +02:00
guard let player = player else {
return true
}
return player.rate == 0.0
2022-05-03 14:32:46 +02:00
}
set(paused) {
if paused {
self.player?.pause()
} else {
self.player?.play()
}
}
}
2022-04-11 16:29:19 +02:00
public static func startTickTimer() {
DispatchQueue.runOnMainQueue {
NSLog("Starting the tick timer")
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.tick()
}
}
}
public static func stopTickTimer() {
NSLog("Stopping the tick timer")
timer?.invalidate()
timer = nil
}
2022-08-16 20:45:29 -04:00
private static func cleanupOldSessions(currentSessionId: String?) {
if let currentSessionId = currentSessionId {
let realm = try! Realm()
let oldSessions = realm.objects(PlaybackSession.self)
.where({ $0.isActiveSession == true && $0.id != currentSessionId })
try! realm.write {
for s in oldSessions {
s.isActiveSession = false
}
}
}
}
2022-08-16 12:32:22 -04:00
public static func startPlayback(sessionId: String, playWhenReady: Bool, playbackRate: Float) {
guard let session = Database.shared.getPlaybackSession(id: sessionId) else { return }
2022-08-16 20:45:29 -04:00
// Clean up the existing player
2022-04-12 14:28:47 +02:00
if player != nil {
player?.destroy()
player = nil
}
2022-08-16 20:45:29 -04:00
// Cleanup old sessions
cleanupOldSessions(currentSessionId: sessionId)
// Set now playing info
2022-05-03 12:55:13 +02:00
NowPlayingInfo.shared.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
2022-04-15 12:21:46 +02:00
2022-08-16 20:45:29 -04:00
// Create the audio player
2022-08-16 12:32:22 -04:00
player = AudioPlayer(sessionId: sessionId, playWhenReady: playWhenReady, playbackRate: playbackRate)
2022-04-15 12:21:46 +02:00
startTickTimer()
2022-04-11 16:29:19 +02:00
}
2022-04-14 14:39:09 +02:00
public static func stopPlayback() {
2022-08-13 12:41:20 -04:00
// Pause playback first, so we can sync our current progress
player?.pause()
2022-04-14 14:39:09 +02:00
2022-08-13 12:41:20 -04:00
// Stop updating progress before we destory the player, so we don't receive bad data
stopTickTimer()
2022-04-15 12:21:46 +02:00
2022-08-13 12:41:20 -04:00
player?.destroy()
player = nil
2022-08-16 20:45:29 -04:00
cleanupOldSessions(currentSessionId: nil)
2022-05-03 12:55:13 +02:00
NowPlayingInfo.shared.reset()
2022-04-14 14:39:09 +02:00
}
public static func getCurrentTime() -> Double? {
self.player?.getCurrentTime()
}
2022-04-14 14:39:09 +02:00
public static func setPlaybackSpeed(speed: Float) {
self.player?.setPlaybackRate(speed)
}
public static func getPlayMethod() -> Int? {
self.player?.getPlayMethod()
}
public static func getPlaybackSession() -> PlaybackSession? {
2022-08-16 12:32:22 -04:00
guard let player = player else { return nil }
guard let session = Database.shared.getPlaybackSession(id: player.getPlaybackSessionId()) else { return nil }
return session
}
2022-04-14 14:39:09 +02:00
public static func seekForward(amount: Double) {
2022-05-03 12:55:13 +02:00
guard let player = player else {
2022-04-14 14:39:09 +02:00
return
}
2022-05-03 12:55:13 +02:00
let destinationTime = player.getCurrentTime() + amount
player.seek(destinationTime, from: "handler")
2022-04-14 14:39:09 +02:00
}
2022-04-14 14:39:09 +02:00
public static func seekBackward(amount: Double) {
2022-05-03 12:55:13 +02:00
guard let player = player else {
2022-04-14 14:39:09 +02:00
return
}
2022-05-03 12:55:13 +02:00
let destinationTime = player.getCurrentTime() - amount
player.seek(destinationTime, from: "handler")
2022-04-14 14:39:09 +02:00
}
2022-04-14 14:39:09 +02:00
public static func seek(amount: Double) {
player?.seek(amount, from: "handler")
2022-04-14 14:39:09 +02:00
}
2022-04-14 14:39:09 +02:00
public static func getMetdata() -> [String: Any] {
2022-04-15 12:21:46 +02:00
DispatchQueue.main.async {
syncProgress()
}
2022-04-14 14:39:09 +02:00
return [
"duration": player?.getDuration() ?? 0,
"currentTime": player?.getCurrentTime() ?? 0,
2022-05-03 14:32:46 +02:00
"playerState": !paused,
2022-04-14 14:39:09 +02:00
"currentRate": player?.rate ?? 0,
]
}
2022-04-15 12:21:46 +02:00
private static func tick() {
2022-05-03 14:32:46 +02:00
if !paused {
listeningTimePassedSinceLastSync += 1
2022-08-11 18:49:04 -05:00
if remainingSleepTime != nil {
if sleepTimerChapterStopTime != nil {
let timeUntilChapterEnd = Double(sleepTimerChapterStopTime ?? 0) - (getCurrentTime() ?? 0)
if timeUntilChapterEnd <= 0 {
paused = true
remainingSleepTime = nil
} else {
remainingSleepTime = Int(timeUntilChapterEnd.rounded())
}
} else {
if remainingSleepTime! <= 0 {
paused = true
}
remainingSleepTime! -= 1
}
}
2022-04-15 12:21:46 +02:00
}
if listeningTimePassedSinceLastSync >= 5 {
2022-04-15 12:21:46 +02:00
syncProgress()
}
}
2022-04-15 12:21:46 +02:00
public static func syncProgress() {
2022-05-03 14:32:46 +02:00
guard let player = player else { return }
2022-08-16 12:32:22 -04:00
guard let session = getPlaybackSession() else { return }
2022-04-15 12:21:46 +02:00
2022-08-16 20:45:29 -04:00
// Get current time
let playerCurrentTime = player.getCurrentTime()
// Prevent multiple sync requests
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
if (lastSyncTime > 0 && timeSinceLastSync < 1) {
NSLog("syncProgress last sync time was < 1 second so not syncing")
return
}
2022-08-13 12:41:20 -04:00
// Prevent a sync if we got junk data from the player (occurs when exiting out of memory
guard !playerCurrentTime.isNaN else { return }
lastSyncTime = Date().timeIntervalSince1970 // seconds
2022-04-15 12:21:46 +02:00
2022-08-16 12:32:22 -04:00
session.update {
session.currentTime = playerCurrentTime
2022-08-16 20:45:29 -04:00
session.timeListening += listeningTimePassedSinceLastSync
2022-08-16 12:32:22 -04:00
}
listeningTimePassedSinceLastSync = 0
2022-04-15 12:21:46 +02:00
2022-08-16 20:45:29 -04:00
// Persist items in the database and sync to the server
if session.isLocal {
_ = syncLocalProgress()
}
2022-08-16 20:45:29 -04:00
syncPlaybackSessionsToServer()
}
private static func syncLocalProgress() -> LocalMediaProgress? {
2022-08-16 12:32:22 -04:00
guard let session = getPlaybackSession() else { return nil }
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
2022-08-16 12:32:22 -04:00
guard let localMediaProgress = localMediaProgress else {
// Local media progress should have been created
// If we're here, it means a library id is invalid
return nil
}
localMediaProgress.updateFromPlaybackSession(session)
Database.shared.saveLocalMediaProgress(localMediaProgress)
2022-08-13 10:28:28 -04:00
NSLog("Local progress saved to the database")
// Send the local progress back to front-end
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
return localMediaProgress
2022-04-15 12:21:46 +02:00
}
2022-08-16 20:45:29 -04:00
private static func syncPlaybackSessionsToServer() {
guard Connectivity.isConnectedToInternet else { return }
DispatchQueue.global(qos: .utility).async {
let realm = try! Realm()
for session in realm.objects(PlaybackSession.self) {
NSLog("Sending sessionId(\(session.id)) to server")
let sessionRef = ThreadSafeReference(to: session)
ApiClient.reportLocalPlaybackProgress(session.freeze()) { success in
// Remove old sessions after they synced with the server
let session = try! Realm().resolve(sessionRef)
if success && !(session?.isActiveSession ?? false) {
NSLog("Deleting sessionId(\(session!.id)) as is no longer active")
session?.delete()
}
}
}
}
}
2022-04-11 16:29:19 +02:00
}