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

286 lines
10 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
class AudioPlayer: NSObject {
// 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
private var lastPlayTime: Double = 0.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: AVPlayer
2022-04-12 14:28:47 +02:00
private var playbackSession: PlaybackSession
private var activeAudioTrack: AudioTrack
2022-03-07 20:46:59 +01:00
// MARK: - Constructor
init(playbackSession: PlaybackSession, playWhenReady: Bool = false, playbackRate: Float = 1) {
2022-03-07 20:46:59 +01:00
self.playWhenReady = playWhenReady
self.initialPlaybackRate = playbackRate
2022-03-07 20:46:59 +01:00
self.audioPlayer = AVPlayer()
2022-04-12 14:28:47 +02:00
self.playbackSession = playbackSession
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
2022-04-12 14:28:47 +02:00
if playbackSession.audioTracks.count != 1 || playbackSession.audioTracks[0].mimeType != "application/vnd.apple.mpegurl" {
NSLog("The player only support HLS streams right now")
self.activeAudioTrack = AudioTrack(index: 0, startOffset: -1, duration: -1, title: "", contentUrl: nil, mimeType: "", metadata: nil, serverIndex: 0)
2022-04-12 14:28:47 +02:00
super.init()
return
}
self.activeAudioTrack = playbackSession.audioTracks[0]
2022-03-07 20:46:59 +01:00
super.init()
initAudioSession()
setupRemoteTransportControls()
// 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)
let playerItem = AVPlayerItem(asset: createAsset())
playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: &playerItemContext)
self.audioPlayer.replaceCurrentItem(with: playerItem)
NSLog("Audioplayer ready")
}
deinit {
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-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)
}
// Throws error Possibly related to the error above
// DispatchQueue.main.sync {
// 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
}
// MARK: - Methods
public func play(allowSeekBack: Bool = false) {
if allowSeekBack {
2022-03-23 16:13:28 +01:00
let diffrence = Date.timeIntervalSinceReferenceDate - lastPlayTime
var time: Int?
if lastPlayTime == 0 {
time = 5
} else if diffrence < 6 {
time = 2
} else if diffrence < 12 {
time = 10
} else if diffrence < 30 {
time = 15
} else if diffrence < 180 {
time = 20
} else if diffrence < 3600 {
time = 25
} else {
time = 29
}
if time != nil {
seek(getCurrentTime() - Double(time!))
}
}
lastPlayTime = Date.timeIntervalSinceReferenceDate
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-03-07 20:46:59 +01:00
updateNowPlaying()
}
public func pause() {
self.audioPlayer.pause()
self.status = 0
self.rate = 0.0
updateNowPlaying()
lastPlayTime = Date.timeIntervalSinceReferenceDate
2022-03-07 20:46:59 +01:00
}
public func seek(_ to: Double) {
let continuePlaing = rate > 0.0
pause()
self.audioPlayer.seek(to: CMTime(seconds: to, preferredTimescale: 1000)) { completed in
if !completed {
NSLog("WARNING: seeking not completed (to \(to)")
}
if continuePlaing {
self.play()
}
self.updateNowPlaying()
}
}
2022-03-09 22:10:15 +01:00
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
if self.audioPlayer.rate != rate {
2022-03-07 20:46:59 +01:00
self.audioPlayer.rate = rate
}
2022-03-09 22:10:15 +01:00
if rate > 0.0 && !(observed && rate == 1) {
self.tmpRate = rate
}
2022-03-07 20:46:59 +01:00
self.rate = rate
self.updateNowPlaying()
}
public func getCurrentTime() -> Double {
self.audioPlayer.currentTime().seconds
}
public func getDuration() -> Double {
self.audioPlayer.currentItem?.duration.seconds ?? 0
}
// MARK: - Private
private func createAsset() -> AVAsset {
let headers: [String: String] = [
2022-04-15 10:16:11 +02:00
"Authorization": "Bearer \(Store.serverConfig!.token)"
2022-03-07 20:46:59 +01:00
]
return AVURLAsset(url: URL(string: "\(Store.serverConfig!.address)\(activeAudioTrack.contentUrl ?? "")")!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
2022-03-07 20:46:59 +01:00
}
private func initAudioSession() {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, options: [.allowAirPlay])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
NSLog("Failed to set AVAudioSession category")
print(error)
}
}
// MARK: - Now playing
private func setupRemoteTransportControls() {
2022-04-14 12:24:27 +02:00
// DispatchQueue.main.sync {
UIApplication.shared.beginReceivingRemoteControlEvents()
2022-04-14 12:24:27 +02:00
// }
2022-03-07 20:46:59 +01:00
let commandCenter = MPRemoteCommandCenter.shared()
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 = [30]
commandCenter.skipForwardCommand.addTarget { [unowned self] event in
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
seek(getCurrentTime() + command.preferredIntervals[0].doubleValue)
return .success
}
commandCenter.skipBackwardCommand.isEnabled = true
commandCenter.skipBackwardCommand.preferredIntervals = [30]
commandCenter.skipBackwardCommand.addTarget { [unowned self] event in
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
seek(getCurrentTime() - command.preferredIntervals[0].doubleValue)
return .success
}
commandCenter.changePlaybackPositionCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.addTarget { event in
guard let event = event as? MPChangePlaybackPositionCommandEvent else {
return .noSuchContent
}
self.seek(event.positionTime)
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-03-22 16:56:59 +01:00
NowPlayingInfo.update(duration: getDuration(), currentTime: getCurrentTime(), 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 == &playerItemContext {
if keyPath == #keyPath(AVPlayer.status) {
guard let playerStatus = AVPlayerItem.Status(rawValue: (change?[.newKey] as? Int ?? -1)) else { return }
if playerStatus == .readyToPlay {
self.updateNowPlaying()
2022-03-07 20:46:59 +01:00
let firstReady = self.status < 0
2022-03-07 20:46:59 +01:00
self.status = 0
if self.playWhenReady {
seek(playbackSession.currentTime)
2022-03-07 20:46:59 +01:00
self.playWhenReady = false
self.play()
} else if (firstReady) { // Only seek on first readyToPlay
seek(playbackSession.currentTime)
2022-03-07 20:46:59 +01:00
}
}
}
} else if context == &playerContext {
if keyPath == #keyPath(AVPlayer.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) {
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
}