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

672 lines
27 KiB
Swift
Raw Normal View History

2022-03-07 20:46:59 +01:00
//
// AudioPlayer.swift
// App
//
// Created by Rasmus Krämer on 07.03.22.
//
import Foundation
import AVFoundation
import UIKit
import MediaPlayer
enum PlayMethod:Int {
case directplay = 0
case directstream = 1
case transcode = 2
case local = 3
}
2022-03-07 20:46:59 +01:00
class AudioPlayer: NSObject {
private let queue = DispatchQueue(label: "ABSAudioPlayerQueue")
2022-03-07 20:46:59 +01:00
// enums and @objc are not compatible
@objc dynamic var status: Int
@objc dynamic var rate: Float
2022-03-09 22:10:15 +01:00
private var tmpRate: Float = 1.0
2022-03-07 20:46:59 +01:00
private var playerContext = 0
private var playerItemContext = 0
private var playWhenReady: Bool
private var initialPlaybackRate: Float
2022-03-07 20:46:59 +01:00
private var audioPlayer: AVQueuePlayer
2022-08-16 12:32:22 -04:00
private var sessionId: String
2022-08-21 12:36:29 -04:00
private var timeObserverToken: Any?
private var queueObserver:NSKeyValueObservation?
private var queueItemStatusObserver:NSKeyValueObservation?
2022-08-22 17:04:48 -04:00
private var sleepTimeStopAt: Double?
private var sleepTimeToken: Any?
private var currentTrackIndex = 0
private var allPlayerItems:[AVPlayerItem] = []
2022-03-07 20:46:59 +01:00
2022-08-22 18:00:37 -04:00
private var pausedTimer: Timer?
// MARK: - Constructor
2022-08-16 12:32:22 -04:00
init(sessionId: String, playWhenReady: Bool = false, playbackRate: Float = 1) {
2022-03-07 20:46:59 +01:00
self.playWhenReady = playWhenReady
self.initialPlaybackRate = playbackRate
self.audioPlayer = AVQueuePlayer()
self.audioPlayer.automaticallyWaitsToMinimizeStalling = false
2022-08-16 12:32:22 -04:00
self.sessionId = sessionId
2022-03-07 20:46:59 +01:00
self.status = -1
self.rate = 0.0
self.tmpRate = playbackRate
2022-03-07 20:46:59 +01:00
super.init()
initAudioSession()
setupRemoteTransportControls()
2022-08-30 17:05:06 -04:00
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
}
2022-08-16 12:32:22 -04:00
2022-03-07 20:46:59 +01:00
// Listen to player events
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
for track in playbackSession.audioTracks {
2022-08-30 17:05:06 -04:00
if let playerAsset = createAsset(itemId: playbackSession.libraryItemId!, track: track) {
let playerItem = AVPlayerItem(asset: playerAsset)
self.allPlayerItems.append(playerItem)
}
}
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
NSLog("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
2022-03-07 20:46:59 +01:00
let playerItems = self.allPlayerItems[self.currentTrackIndex..<self.allPlayerItems.count]
NSLog("Setting player items \(playerItems.count)")
2022-03-07 20:46:59 +01:00
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
}
2022-08-21 12:36:29 -04:00
setupTimeObserver()
setupQueueObserver()
setupQueueItemStatusObserver()
2022-03-07 20:46:59 +01:00
NSLog("Audioplayer ready")
}
deinit {
2022-08-22 18:00:37 -04:00
self.stopPausedTimer()
2022-08-22 17:04:48 -04:00
self.removeSleepTimer()
2022-08-21 12:36:29 -04:00
self.removeTimeObserver()
self.queueObserver?.invalidate()
self.queueItemStatusObserver?.invalidate()
2022-03-07 20:46:59 +01:00
destroy()
}
public func destroy() {
// Pause is not synchronous causing this error on below lines:
// AVAudioSession_iOS.mm:1206 Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session
2022-05-03 12:55:13 +02:00
// It is related to L79 `AVAudioSession.sharedInstance().setActive(false)`
2022-03-07 20:46:59 +01:00
pause()
audioPlayer.replaceCurrentItem(with: nil)
2022-03-07 20:46:59 +01:00
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch {
NSLog("Failed to set AVAudioSession inactive")
print(error)
}
2022-05-03 12:55:13 +02:00
DispatchQueue.runOnMainQueue {
UIApplication.shared.endReceivingRemoteControlEvents()
}
2022-04-14 14:39:09 +02:00
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
2022-03-07 20:46:59 +01:00
}
2022-08-30 17:05:06 -04:00
public func isInitialized() -> Bool {
2022-08-16 21:14:33 -04:00
return self.status != -1
}
2022-08-30 17:05:06 -04:00
public func getPlaybackSession() -> PlaybackSession? {
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 {
let startOffset = playbackSession.audioTracks[index].startOffset ?? 0.0
let duration = playbackSession.audioTracks[index].duration
let trackEnd = startOffset + duration
if (time < trackEnd.rounded(.down)) {
return index
}
}
return 0
}
2022-08-21 12:36:29 -04:00
private func setupTimeObserver() {
// Time observer should be configured on the main queue
DispatchQueue.runOnMainQueue {
self.removeTimeObserver()
2022-08-25 18:28:17 -04:00
let timeScale = CMTimeScale(NSEC_PER_SEC)
// Rate will be different depending on playback speed, aim for 2 observations/sec
let seconds = 0.5 * (self.rate > 0 ? self.rate : 1.0)
let time = CMTime(seconds: Double(seconds), preferredTimescale: timeScale)
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: self.queue) { [weak self] time in
2022-08-29 21:16:51 -04:00
guard let self = self else { return }
2022-08-30 17:05:06 -04:00
guard let currentTime = self.getCurrentTime() else { return }
2022-08-29 21:16:51 -04:00
let isPlaying = self.isPlaying()
Task {
// Let the player update the current playback positions
2022-08-29 21:16:51 -04:00
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: isPlaying, isStopping: false)
}
// Update the sleep time, if set
2022-08-29 21:16:51 -04:00
if self.sleepTimeStopAt != nil {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
}
2022-08-22 17:04:48 -04:00
}
2022-08-21 12:36:29 -04:00
}
}
private func removeTimeObserver() {
if let timeObserverToken = timeObserverToken {
self.audioPlayer.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
}
2022-08-30 17:05:06 -04:00
private func setupQueueObserver() {
self.queueObserver = self.audioPlayer.observe(\.currentItem, options: [.new]) {_,_ in
let prevTrackIndex = self.currentTrackIndex
self.audioPlayer.currentItem.map { item in
self.currentTrackIndex = self.allPlayerItems.firstIndex(of:item) ?? 0
if (self.currentTrackIndex != prevTrackIndex) {
NSLog("New Current track index \(self.currentTrackIndex)")
}
}
}
}
2022-08-30 17:05:06 -04:00
private func setupQueueItemStatusObserver() {
self.queueItemStatusObserver?.invalidate()
self.queueItemStatusObserver = self.audioPlayer.currentItem?.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in
2022-08-30 17:05:06 -04:00
guard let playbackSession = self.getPlaybackSession() else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
if (playerItem.status == .readyToPlay) {
NSLog("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
self.updateNowPlaying()
2022-08-19 21:20:53 -04:00
// Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session
let firstReady = self.status < 0
2022-08-19 21:20:53 -04:00
if firstReady || self.playWhenReady {
self.seek(playbackSession.currentTime, from: "queueItemStatusObserver")
}
// Mark the player as ready
self.status = 0
2022-08-19 21:20:53 -04:00
// Start the player, if requested
if self.playWhenReady {
self.playWhenReady = false
self.play()
}
} else if (playerItem.status == .failed) {
NSLog("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
}
})
}
2022-08-22 18:00:37 -04:00
private func startPausedTimer() {
guard self.pausedTimer == nil else { return }
self.queue.async {
2022-08-22 18:00:37 -04:00
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
NSLog("PAUSE TIMER: Syncing from server")
Task { await PlayerProgress.shared.syncFromServer() }
}
}
}
private func stopPausedTimer() {
self.pausedTimer?.invalidate()
self.pausedTimer = nil
}
2022-03-07 20:46:59 +01:00
// MARK: - Methods
public func play(allowSeekBack: Bool = false) {
guard self.isInitialized() else { return }
2022-08-25 17:39:06 -04:00
// Capture remaining sleep time before changing the track position
let sleepSecondsRemaining = PlayerHandler.remainingSleepTime
2022-08-30 17:05:06 -04:00
if allowSeekBack, let session = self.getPlaybackSession() {
let lastPlayed = (session.updatedAt ?? 0)/1000
let difference = Date.timeIntervalSinceReferenceDate - lastPlayed
2022-03-23 16:13:28 +01:00
var time: Int?
if lastPlayed == 0 {
2022-03-23 16:13:28 +01:00
time = 5
} else if difference < 6 {
2022-03-23 16:13:28 +01:00
time = 2
} else if difference < 12 {
2022-03-23 16:13:28 +01:00
time = 10
} else if difference < 30 {
2022-03-23 16:13:28 +01:00
time = 15
} else if difference < 180 {
2022-03-23 16:13:28 +01:00
time = 20
} else if difference < 3600 {
2022-03-23 16:13:28 +01:00
time = 25
} else {
time = 29
}
if time != nil {
2022-08-30 17:05:06 -04:00
guard let currentTime = self.getCurrentTime() else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
seek(currentTime - Double(time!), from: "play")
}
}
2022-08-22 17:04:48 -04:00
2022-08-22 18:00:37 -04:00
self.stopPausedTimer()
2022-08-22 17:04:48 -04:00
Task {
2022-08-30 17:05:06 -04:00
if let currentTime = self.getCurrentTime() {
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: false)
}
2022-08-22 17:04:48 -04:00
}
2022-03-07 20:46:59 +01:00
self.audioPlayer.play()
self.status = 1
2022-03-09 22:10:15 +01:00
self.rate = self.tmpRate
self.audioPlayer.rate = self.tmpRate
2022-08-25 17:39:06 -04:00
// If we have an active sleep timer, reschedule based on rate
2022-08-30 17:05:06 -04:00
if let currentTime = self.getCurrentTime() {
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
}
2022-08-25 17:39:06 -04:00
2022-03-07 20:46:59 +01:00
updateNowPlaying()
}
2022-03-07 20:46:59 +01:00
public func pause() {
guard self.isInitialized() else { return }
2022-03-07 20:46:59 +01:00
self.audioPlayer.pause()
2022-08-22 17:04:48 -04:00
Task {
2022-08-30 17:05:06 -04:00
if let currentTime = self.getCurrentTime() {
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: true)
}
2022-08-22 17:04:48 -04:00
}
2022-03-07 20:46:59 +01:00
self.status = 0
self.rate = 0.0
updateNowPlaying()
2022-08-22 18:00:37 -04:00
self.startPausedTimer()
2022-03-07 20:46:59 +01:00
}
2022-05-03 15:01:30 +02:00
public func seek(_ to: Double, from: String) {
let continuePlaying = rate > 0.0
2022-03-07 20:46:59 +01:00
pause()
NSLog("Seek to \(to) from \(from)")
2022-08-30 17:05:06 -04:00
guard let playbackSession = self.getPlaybackSession() else { return }
2022-08-16 12:32:22 -04:00
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
let ctso = currentTrack.startOffset ?? 0.0
let trackEnd = ctso + currentTrack.duration
NSLog("Seek current track END = \(trackEnd)")
2022-08-22 17:04:48 -04:00
// Capture remaining sleep time before changing the track position
let sleepSecondsRemaining = PlayerHandler.remainingSleepTime
let indexOfSeek = getItemIndexForTime(time: to)
NSLog("Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
// Reconstruct queue if seeking to a different track
if (self.currentTrackIndex != indexOfSeek) {
self.currentTrackIndex = indexOfSeek
2022-08-25 15:42:37 -04:00
try? playbackSession.update {
2022-08-16 12:32:22 -04:00
playbackSession.currentTime = to
}
self.playWhenReady = continuePlaying // Only playWhenReady if already playing
self.status = -1
let playerItems = self.allPlayerItems[indexOfSeek..<self.allPlayerItems.count]
self.audioPlayer.removeAllItems()
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
2022-03-07 20:46:59 +01:00
}
setupQueueItemStatusObserver()
} else {
NSLog("Seeking in current item \(to)")
2022-08-16 12:32:22 -04:00
let currentTrackStartOffset = playbackSession.audioTracks[self.currentTrackIndex].startOffset ?? 0.0
let seekTime = to - currentTrackStartOffset
2022-08-22 17:04:48 -04:00
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
if !completed {
NSLog("WARNING: seeking not completed (to \(seekTime)")
}
if continuePlaying {
2022-08-22 17:04:48 -04:00
self?.play()
}
self?.updateNowPlaying()
// If we have an active sleep timer, reschedule based on seek, since seek is fuzzy
2022-08-22 20:36:15 -04:00
// This needs to occur after play() to capture the correct playback rate
2022-08-22 17:04:48 -04:00
if let currentTime = self?.getCurrentTime() {
self?.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
}
2022-03-07 20:46:59 +01:00
}
}
}
2022-03-09 22:10:15 +01:00
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
2022-08-25 18:28:17 -04:00
// Capture remaining sleep time before changing the rate
let sleepSecondsRemaining = PlayerHandler.remainingSleepTime
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
2022-03-09 22:10:15 +01:00
if self.audioPlayer.rate != rate {
NSLog("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
2022-03-07 20:46:59 +01:00
self.audioPlayer.rate = rate
}
2022-03-09 22:10:15 +01:00
2022-03-07 20:46:59 +01:00
self.rate = rate
self.updateNowPlaying()
2022-08-22 17:04:48 -04:00
2022-08-25 18:28:17 -04:00
if playbackSpeedChanged {
self.tmpRate = rate
// If we have an active sleep timer, reschedule based on rate
2022-08-30 17:05:06 -04:00
if let currentTime = self.getCurrentTime() {
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
}
2022-08-25 18:28:17 -04:00
// Setup the time observer again at the new rate
self.setupTimeObserver()
}
2022-08-22 17:04:48 -04:00
}
public func getSleepStopAt() -> Double? {
return self.sleepTimeStopAt
}
// Let iOS handle the sleep timer logic by letting us know when it's time to stop
public func setSleepTime(stopAt: Double, scaleBasedOnSpeed: Bool = false) {
NSLog("SLEEP TIMER: Scheduling for \(stopAt)")
// Reset any previous sleep timer
self.removeSleepTimer()
2022-08-30 17:05:06 -04:00
guard let currentTime = getCurrentTime() else {
NSLog("Failed to get currenTime")
return
}
2022-08-22 17:04:48 -04:00
// Mark the time to stop playing
if scaleBasedOnSpeed {
// Consider paused as playing at 1x
let rate = Double(self.rate > 0 ? self.rate : 1)
// Calculate the scaled time to stop at
let timeUntilSleep = (stopAt - currentTime) * rate
self.sleepTimeStopAt = currentTime + timeUntilSleep
NSLog("SLEEP TIMER: Adjusted based on playback speed of \(rate) to \(self.sleepTimeStopAt!)")
} else {
self.sleepTimeStopAt = stopAt
}
guard let sleepTimeStopAt = self.sleepTimeStopAt else { return }
let sleepTime = CMTime(seconds: sleepTimeStopAt, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
// Schedule the observation time
var times = [NSValue]()
times.append(NSValue(time: sleepTime))
sleepTimeToken = self.audioPlayer.addBoundaryTimeObserver(forTimes: times, queue: queue) { [weak self] in
2022-08-22 17:04:48 -04:00
NSLog("SLEEP TIMER: Pausing audio")
self?.pause()
2022-08-25 19:03:05 -04:00
PlayerHandler.sleepTimerChapterStopTime = nil
2022-08-22 17:04:48 -04:00
self?.removeSleepTimer()
}
// Update the UI
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
}
private func rescheduleSleepTimerAtTime(time: Double, secondsRemaining: Int?) {
// Not a chapter sleep timer
let hadToCancelChapterSleepTimer = decideIfChapterSleepTimerNeedsToBeCanceled(time: time)
guard !hadToCancelChapterSleepTimer else { return }
2022-08-22 17:04:48 -04:00
guard PlayerHandler.sleepTimerChapterStopTime == nil else { return }
// Verify sleep timer is set
guard self.sleepTimeToken != nil else { return }
2022-08-22 17:04:48 -04:00
// Update the sleep timer
if let secondsRemaining = secondsRemaining {
let newSleepTimerPosition = time + Double(secondsRemaining)
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
}
}
private func decideIfChapterSleepTimerNeedsToBeCanceled(time: Double) -> Bool {
if let chapterSleepTime = PlayerHandler.sleepTimerChapterStopTime {
let sleepIsBeforeCurrentTime = Double(chapterSleepTime) <= time
if sleepIsBeforeCurrentTime {
PlayerHandler.sleepTimerChapterStopTime = nil
self.removeSleepTimer()
return true
}
}
return false
}
2022-08-22 17:04:48 -04:00
public func increaseSleepTime(extraTimeInSeconds: Double) {
2022-08-30 17:05:06 -04:00
if let sleepTime = PlayerHandler.remainingSleepTime, let currentTime = getCurrentTime() {
2022-08-22 17:04:48 -04:00
let newSleepTimerPosition = currentTime + Double(sleepTime) + extraTimeInSeconds
if newSleepTimerPosition > currentTime {
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
}
}
}
public func decreaseSleepTime(removeTimeInSeconds: Double) {
2022-08-30 17:05:06 -04:00
if let sleepTime = PlayerHandler.remainingSleepTime, let currentTime = getCurrentTime() {
2022-08-22 17:04:48 -04:00
let newSleepTimerPosition = currentTime + Double(sleepTime) - removeTimeInSeconds
guard newSleepTimerPosition > currentTime else { return }
if newSleepTimerPosition > currentTime {
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
}
}
}
public func removeSleepTimer() {
self.sleepTimeStopAt = nil
if let token = sleepTimeToken {
self.audioPlayer.removeTimeObserver(token)
sleepTimeToken = nil
}
// Update the UI
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: self)
2022-03-07 20:46:59 +01:00
}
2022-08-30 17:05:06 -04:00
public func getCurrentTime() -> Double? {
guard let playbackSession = self.getPlaybackSession() else { return nil }
let currentTrackTime = self.audioPlayer.currentTime().seconds
let audioTrack = playbackSession.audioTracks[currentTrackIndex]
let startOffset = audioTrack.startOffset ?? 0.0
return startOffset + currentTrackTime
2022-03-07 20:46:59 +01:00
}
2022-08-13 12:41:20 -04:00
2022-08-30 17:05:06 -04:00
public func getPlayMethod() -> Int? {
guard let playbackSession = self.getPlaybackSession() else { return nil }
2022-08-16 12:32:22 -04:00
return playbackSession.playMethod
}
2022-08-16 12:32:22 -04:00
public func getPlaybackSessionId() -> String {
return self.sessionId
}
2022-08-30 17:05:06 -04:00
public func getDuration() -> Double? {
guard let playbackSession = self.getPlaybackSession() else { return nil }
return playbackSession.duration
2022-03-07 20:46:59 +01:00
}
public func isPlaying() -> Bool {
return self.status > 0
}
2022-03-07 20:46:59 +01:00
// MARK: - Private
2022-08-30 17:05:06 -04:00
private func createAsset(itemId:String, track:AudioTrack) -> AVAsset? {
guard let playbackSession = self.getPlaybackSession() else { return nil }
if (playbackSession.playMethod == PlayMethod.directplay.rawValue) {
// The only reason this is separate is because the filename needs to be encoded
let filename = track.metadata?.filename ?? ""
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
let url = URL(string: urlstr)!
return AVURLAsset(url: url)
2022-08-11 18:29:55 -04:00
} else if (playbackSession.playMethod == PlayMethod.local.rawValue) {
guard let localFile = track.getLocalFile() else {
// Worst case we can stream the file
NSLog("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
let filename = track.metadata?.filename ?? ""
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
let url = URL(string: urlstr)!
return AVURLAsset(url: url)
}
return AVURLAsset(url: localFile.contentPath)
} else { // HLS Transcode
let headers: [String: String] = [
"Authorization": "Bearer \(Store.serverConfig!.token)"
]
return AVURLAsset(url: URL(string: "\(Store.serverConfig!.address)\(track.contentUrl ?? "")")!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
}
2022-03-07 20:46:59 +01:00
}
2022-03-07 20:46:59 +01:00
private func initAudioSession() {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio)
2022-03-07 20:46:59 +01:00
try AVAudioSession.sharedInstance().setActive(true)
} catch {
NSLog("Failed to set AVAudioSession category")
print(error)
}
}
// MARK: - Now playing
private func setupRemoteTransportControls() {
2022-05-03 12:55:13 +02:00
DispatchQueue.runOnMainQueue {
UIApplication.shared.beginReceivingRemoteControlEvents()
2022-05-03 12:55:13 +02:00
}
2022-03-07 20:46:59 +01:00
let commandCenter = MPRemoteCommandCenter.shared()
let deviceSettings = Database.shared.getDeviceSettings()
2022-03-07 20:46:59 +01:00
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
play(allowSeekBack: true)
2022-03-07 20:46:59 +01:00
return .success
}
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [unowned self] event in
pause()
return .success
}
commandCenter.skipForwardCommand.isEnabled = true
commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: deviceSettings.jumpForwardTime)]
2022-03-07 20:46:59 +01:00
commandCenter.skipForwardCommand.addTarget { [unowned self] event in
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
2022-08-30 17:05:06 -04:00
guard let currentTime = self.getCurrentTime() else {
return .commandFailed
}
seek(currentTime + command.preferredIntervals[0].doubleValue, from: "remote")
2022-03-07 20:46:59 +01:00
return .success
}
commandCenter.skipBackwardCommand.isEnabled = true
commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: deviceSettings.jumpBackwardsTime)]
2022-03-07 20:46:59 +01:00
commandCenter.skipBackwardCommand.addTarget { [unowned self] event in
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
2022-08-30 17:05:06 -04:00
guard let currentTime = self.getCurrentTime() else {
return .commandFailed
}
seek(currentTime - command.preferredIntervals[0].doubleValue, from: "remote")
2022-03-07 20:46:59 +01:00
return .success
}
commandCenter.changePlaybackPositionCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.addTarget { event in
guard let event = event as? MPChangePlaybackPositionCommandEvent else {
return .noSuchContent
}
self.seek(event.positionTime, from: "remote")
2022-03-07 20:46:59 +01:00
return .success
}
commandCenter.changePlaybackRateCommand.isEnabled = true
2022-03-22 16:56:59 +01:00
commandCenter.changePlaybackRateCommand.supportedPlaybackRates = [0.5, 0.75, 1.0, 1.25, 1.5, 2]
commandCenter.changePlaybackRateCommand.addTarget { event in
guard let event = event as? MPChangePlaybackRateCommandEvent else {
return .noSuchContent
}
self.setPlaybackRate(event.playbackRate)
return .success
}
2022-03-07 20:46:59 +01:00
}
private func updateNowPlaying() {
2022-04-14 14:39:09 +02:00
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
2022-08-30 17:05:06 -04:00
if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate)
}
2022-03-07 20:46:59 +01:00
}
// MARK: - Observer
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &playerContext {
2022-03-07 20:46:59 +01:00
if keyPath == #keyPath(AVPlayer.rate) {
NSLog("playerContext observer player rate")
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
2022-03-07 20:46:59 +01:00
} else if keyPath == #keyPath(AVPlayer.currentItem) {
2022-04-30 10:58:08 +02:00
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
2022-03-07 20:46:59 +01:00
NSLog("WARNING: Item ended")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
}
public static var instance: AudioPlayer?
2022-03-07 20:46:59 +01:00
}