mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 14:47:21 +02:00
Merge pull request #340 from ronaldheft/ios-crashes
fix: iOS app crashes when playback session not loaded from Realm
This commit is contained in:
@@ -63,15 +63,22 @@ class AudioPlayer: NSObject {
|
|||||||
initAudioSession()
|
initAudioSession()
|
||||||
setupRemoteTransportControls()
|
setupRemoteTransportControls()
|
||||||
|
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
let playbackSession = self.getPlaybackSession()
|
||||||
|
guard let playbackSession = playbackSession else {
|
||||||
|
NSLog("Failed to fetch playback session. Player will not initialize")
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Listen to player events
|
// Listen to player events
|
||||||
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
|
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
|
||||||
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
|
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
|
||||||
|
|
||||||
for track in playbackSession.audioTracks {
|
for track in playbackSession.audioTracks {
|
||||||
let playerItem = AVPlayerItem(asset: createAsset(itemId: playbackSession.libraryItemId!, track: track))
|
if let playerAsset = createAsset(itemId: playbackSession.libraryItemId!, track: track) {
|
||||||
self.allPlayerItems.append(playerItem)
|
let playerItem = AVPlayerItem(asset: playerAsset)
|
||||||
|
self.allPlayerItems.append(playerItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
|
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
|
||||||
@@ -118,12 +125,16 @@ class AudioPlayer: NSObject {
|
|||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInitialized() -> Bool {
|
public func isInitialized() -> Bool {
|
||||||
return self.status != -1
|
return self.status != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func getItemIndexForTime(time:Double) -> Int {
|
public func getPlaybackSession() -> PlaybackSession? {
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
return Database.shared.getPlaybackSession(id: self.sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getItemIndexForTime(time:Double) -> Int {
|
||||||
|
guard let playbackSession = self.getPlaybackSession() else { return 0 }
|
||||||
for index in 0..<self.allPlayerItems.count {
|
for index in 0..<self.allPlayerItems.count {
|
||||||
let startOffset = playbackSession.audioTracks[index].startOffset ?? 0.0
|
let startOffset = playbackSession.audioTracks[index].startOffset ?? 0.0
|
||||||
let duration = playbackSession.audioTracks[index].duration
|
let duration = playbackSession.audioTracks[index].duration
|
||||||
@@ -147,7 +158,7 @@ class AudioPlayer: NSObject {
|
|||||||
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: self.queue) { [weak self] time in
|
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: self.queue) { [weak self] time in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
let currentTime = self.getCurrentTime()
|
guard let currentTime = self.getCurrentTime() else { return }
|
||||||
let isPlaying = self.isPlaying()
|
let isPlaying = self.isPlaying()
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
@@ -170,7 +181,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupQueueObserver() {
|
private func setupQueueObserver() {
|
||||||
self.queueObserver = self.audioPlayer.observe(\.currentItem, options: [.new]) {_,_ in
|
self.queueObserver = self.audioPlayer.observe(\.currentItem, options: [.new]) {_,_ in
|
||||||
let prevTrackIndex = self.currentTrackIndex
|
let prevTrackIndex = self.currentTrackIndex
|
||||||
self.audioPlayer.currentItem.map { item in
|
self.audioPlayer.currentItem.map { item in
|
||||||
@@ -182,10 +193,13 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupQueueItemStatusObserver() {
|
private func setupQueueItemStatusObserver() {
|
||||||
self.queueItemStatusObserver?.invalidate()
|
self.queueItemStatusObserver?.invalidate()
|
||||||
self.queueItemStatusObserver = self.audioPlayer.currentItem?.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in
|
self.queueItemStatusObserver = self.audioPlayer.currentItem?.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (playerItem.status == .readyToPlay) {
|
if (playerItem.status == .readyToPlay) {
|
||||||
NSLog("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
|
NSLog("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
|
||||||
self.updateNowPlaying()
|
self.updateNowPlaying()
|
||||||
@@ -233,7 +247,7 @@ class AudioPlayer: NSObject {
|
|||||||
// Capture remaining sleep time before changing the track position
|
// Capture remaining sleep time before changing the track position
|
||||||
let sleepSecondsRemaining = PlayerHandler.remainingSleepTime
|
let sleepSecondsRemaining = PlayerHandler.remainingSleepTime
|
||||||
|
|
||||||
if allowSeekBack, let session = Database.shared.getPlaybackSession(id: self.sessionId) {
|
if allowSeekBack, let session = self.getPlaybackSession() {
|
||||||
let lastPlayed = (session.updatedAt ?? 0)/1000
|
let lastPlayed = (session.updatedAt ?? 0)/1000
|
||||||
let difference = Date.timeIntervalSinceReferenceDate - lastPlayed
|
let difference = Date.timeIntervalSinceReferenceDate - lastPlayed
|
||||||
var time: Int?
|
var time: Int?
|
||||||
@@ -255,14 +269,20 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if time != nil {
|
if time != nil {
|
||||||
seek(getCurrentTime() - Double(time!), from: "play")
|
guard let currentTime = self.getCurrentTime() else {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seek(currentTime - Double(time!), from: "play")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stopPausedTimer()
|
self.stopPausedTimer()
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await PlayerProgress.shared.syncFromPlayer(currentTime: self.getCurrentTime(), includesPlayProgress: self.isPlaying(), isStopping: false)
|
if let currentTime = self.getCurrentTime() {
|
||||||
|
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.audioPlayer.play()
|
self.audioPlayer.play()
|
||||||
@@ -271,7 +291,9 @@ class AudioPlayer: NSObject {
|
|||||||
self.audioPlayer.rate = self.tmpRate
|
self.audioPlayer.rate = self.tmpRate
|
||||||
|
|
||||||
// If we have an active sleep timer, reschedule based on rate
|
// If we have an active sleep timer, reschedule based on rate
|
||||||
self.rescheduleSleepTimerAtTime(time: self.getCurrentTime(), secondsRemaining: sleepSecondsRemaining)
|
if let currentTime = self.getCurrentTime() {
|
||||||
|
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
|
||||||
|
}
|
||||||
|
|
||||||
updateNowPlaying()
|
updateNowPlaying()
|
||||||
}
|
}
|
||||||
@@ -282,7 +304,9 @@ class AudioPlayer: NSObject {
|
|||||||
self.audioPlayer.pause()
|
self.audioPlayer.pause()
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await PlayerProgress.shared.syncFromPlayer(currentTime: self.getCurrentTime(), includesPlayProgress: self.isPlaying(), isStopping: true)
|
if let currentTime = self.getCurrentTime() {
|
||||||
|
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status = 0
|
self.status = 0
|
||||||
@@ -300,7 +324,7 @@ class AudioPlayer: NSObject {
|
|||||||
|
|
||||||
NSLog("Seek to \(to) from \(from)")
|
NSLog("Seek to \(to) from \(from)")
|
||||||
|
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else { return }
|
||||||
|
|
||||||
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
|
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
|
||||||
let ctso = currentTrack.startOffset ?? 0.0
|
let ctso = currentTrack.startOffset ?? 0.0
|
||||||
@@ -372,7 +396,9 @@ class AudioPlayer: NSObject {
|
|||||||
self.tmpRate = rate
|
self.tmpRate = rate
|
||||||
|
|
||||||
// If we have an active sleep timer, reschedule based on rate
|
// If we have an active sleep timer, reschedule based on rate
|
||||||
self.rescheduleSleepTimerAtTime(time: self.getCurrentTime(), secondsRemaining: sleepSecondsRemaining)
|
if let currentTime = self.getCurrentTime() {
|
||||||
|
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the time observer again at the new rate
|
// Setup the time observer again at the new rate
|
||||||
self.setupTimeObserver()
|
self.setupTimeObserver()
|
||||||
@@ -390,7 +416,10 @@ class AudioPlayer: NSObject {
|
|||||||
// Reset any previous sleep timer
|
// Reset any previous sleep timer
|
||||||
self.removeSleepTimer()
|
self.removeSleepTimer()
|
||||||
|
|
||||||
let currentTime = getCurrentTime()
|
guard let currentTime = getCurrentTime() else {
|
||||||
|
NSLog("Failed to get currenTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Mark the time to stop playing
|
// Mark the time to stop playing
|
||||||
if scaleBasedOnSpeed {
|
if scaleBasedOnSpeed {
|
||||||
@@ -454,8 +483,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func increaseSleepTime(extraTimeInSeconds: Double) {
|
public func increaseSleepTime(extraTimeInSeconds: Double) {
|
||||||
if let sleepTime = PlayerHandler.remainingSleepTime {
|
if let sleepTime = PlayerHandler.remainingSleepTime, let currentTime = getCurrentTime() {
|
||||||
let currentTime = getCurrentTime()
|
|
||||||
let newSleepTimerPosition = currentTime + Double(sleepTime) + extraTimeInSeconds
|
let newSleepTimerPosition = currentTime + Double(sleepTime) + extraTimeInSeconds
|
||||||
if newSleepTimerPosition > currentTime {
|
if newSleepTimerPosition > currentTime {
|
||||||
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
|
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
|
||||||
@@ -464,8 +492,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func decreaseSleepTime(removeTimeInSeconds: Double) {
|
public func decreaseSleepTime(removeTimeInSeconds: Double) {
|
||||||
if let sleepTime = PlayerHandler.remainingSleepTime {
|
if let sleepTime = PlayerHandler.remainingSleepTime, let currentTime = getCurrentTime() {
|
||||||
let currentTime = getCurrentTime()
|
|
||||||
let newSleepTimerPosition = currentTime + Double(sleepTime) - removeTimeInSeconds
|
let newSleepTimerPosition = currentTime + Double(sleepTime) - removeTimeInSeconds
|
||||||
guard newSleepTimerPosition > currentTime else { return }
|
guard newSleepTimerPosition > currentTime else { return }
|
||||||
if newSleepTimerPosition > currentTime {
|
if newSleepTimerPosition > currentTime {
|
||||||
@@ -486,16 +513,16 @@ class AudioPlayer: NSObject {
|
|||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: self)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getCurrentTime() -> Double {
|
public func getCurrentTime() -> Double? {
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else { return nil }
|
||||||
let currentTrackTime = self.audioPlayer.currentTime().seconds
|
let currentTrackTime = self.audioPlayer.currentTime().seconds
|
||||||
let audioTrack = playbackSession.audioTracks[currentTrackIndex]
|
let audioTrack = playbackSession.audioTracks[currentTrackIndex]
|
||||||
let startOffset = audioTrack.startOffset ?? 0.0
|
let startOffset = audioTrack.startOffset ?? 0.0
|
||||||
return startOffset + currentTrackTime
|
return startOffset + currentTrackTime
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getPlayMethod() -> Int {
|
public func getPlayMethod() -> Int? {
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else { return nil }
|
||||||
return playbackSession.playMethod
|
return playbackSession.playMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,8 +530,8 @@ class AudioPlayer: NSObject {
|
|||||||
return self.sessionId
|
return self.sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDuration() -> Double {
|
public func getDuration() -> Double? {
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else { return nil }
|
||||||
return playbackSession.duration
|
return playbackSession.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,8 +540,9 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
private func createAsset(itemId:String, track:AudioTrack) -> AVAsset {
|
private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? {
|
||||||
let playbackSession = Database.shared.getPlaybackSession(id: self.sessionId)!
|
guard let playbackSession = self.getPlaybackSession() else { return nil }
|
||||||
|
|
||||||
if (playbackSession.playMethod == PlayMethod.directplay.rawValue) {
|
if (playbackSession.playMethod == PlayMethod.directplay.rawValue) {
|
||||||
// The only reason this is separate is because the filename needs to be encoded
|
// The only reason this is separate is because the filename needs to be encoded
|
||||||
let filename = track.metadata?.filename ?? ""
|
let filename = track.metadata?.filename ?? ""
|
||||||
@@ -576,8 +604,10 @@ class AudioPlayer: NSObject {
|
|||||||
guard let command = event.command as? MPSkipIntervalCommand else {
|
guard let command = event.command as? MPSkipIntervalCommand else {
|
||||||
return .noSuchContent
|
return .noSuchContent
|
||||||
}
|
}
|
||||||
|
guard let currentTime = self.getCurrentTime() else {
|
||||||
seek(getCurrentTime() + command.preferredIntervals[0].doubleValue, from: "remote")
|
return .commandFailed
|
||||||
|
}
|
||||||
|
seek(currentTime + command.preferredIntervals[0].doubleValue, from: "remote")
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
commandCenter.skipBackwardCommand.isEnabled = true
|
commandCenter.skipBackwardCommand.isEnabled = true
|
||||||
@@ -586,8 +616,10 @@ class AudioPlayer: NSObject {
|
|||||||
guard let command = event.command as? MPSkipIntervalCommand else {
|
guard let command = event.command as? MPSkipIntervalCommand else {
|
||||||
return .noSuchContent
|
return .noSuchContent
|
||||||
}
|
}
|
||||||
|
guard let currentTime = self.getCurrentTime() else {
|
||||||
seek(getCurrentTime() - command.preferredIntervals[0].doubleValue, from: "remote")
|
return .commandFailed
|
||||||
|
}
|
||||||
|
seek(currentTime - command.preferredIntervals[0].doubleValue, from: "remote")
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +646,9 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
private func updateNowPlaying() {
|
private func updateNowPlaying() {
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
NowPlayingInfo.shared.update(duration: getDuration(), currentTime: getCurrentTime(), rate: rate)
|
if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
|
||||||
|
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|||||||
@@ -62,13 +62,14 @@ class PlayerHandler {
|
|||||||
public static var remainingSleepTime: Int? {
|
public static var remainingSleepTime: Int? {
|
||||||
get {
|
get {
|
||||||
guard let player = player else { return nil }
|
guard let player = player else { return nil }
|
||||||
|
guard let currentTime = player.getCurrentTime() else { return nil }
|
||||||
|
|
||||||
// Return the player time until sleep
|
// Return the player time until sleep
|
||||||
var timeUntilSleep: Double? = nil
|
var timeUntilSleep: Double? = nil
|
||||||
if let sleepTimerChapterStopTime = sleepTimerChapterStopTime {
|
if let sleepTimerChapterStopTime = sleepTimerChapterStopTime {
|
||||||
timeUntilSleep = Double(sleepTimerChapterStopTime) - player.getCurrentTime()
|
timeUntilSleep = Double(sleepTimerChapterStopTime) - currentTime
|
||||||
} else if let stopAt = player.getSleepStopAt() {
|
} else if let stopAt = player.getSleepStopAt() {
|
||||||
timeUntilSleep = stopAt - player.getCurrentTime()
|
timeUntilSleep = stopAt - currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the time until sleep based on the playback rate
|
// Scale the time until sleep based on the playback rate
|
||||||
@@ -96,7 +97,8 @@ class PlayerHandler {
|
|||||||
|
|
||||||
public static func setSleepTime(secondsUntilSleep: Double) {
|
public static func setSleepTime(secondsUntilSleep: Double) {
|
||||||
guard let player = player else { return }
|
guard let player = player else { return }
|
||||||
let stopAt = secondsUntilSleep + player.getCurrentTime()
|
guard let currentTime = player.getCurrentTime() else { return }
|
||||||
|
let stopAt = secondsUntilSleep + currentTime
|
||||||
player.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: true)
|
player.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +136,17 @@ class PlayerHandler {
|
|||||||
|
|
||||||
public static func seekForward(amount: Double) {
|
public static func seekForward(amount: Double) {
|
||||||
guard let player = player else { return }
|
guard let player = player else { return }
|
||||||
|
guard let currentTime = player.getCurrentTime() else { return }
|
||||||
|
|
||||||
let destinationTime = player.getCurrentTime() + amount
|
let destinationTime = currentTime + amount
|
||||||
player.seek(destinationTime, from: "handler")
|
player.seek(destinationTime, from: "handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func seekBackward(amount: Double) {
|
public static func seekBackward(amount: Double) {
|
||||||
guard let player = player else { return }
|
guard let player = player else { return }
|
||||||
|
guard let currentTime = player.getCurrentTime() else { return }
|
||||||
|
|
||||||
let destinationTime = player.getCurrentTime() - amount
|
let destinationTime = currentTime - amount
|
||||||
player.seek(destinationTime, from: "handler")
|
player.seek(destinationTime, from: "handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user