mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-09 05:18:40 +02:00
Added PlaybackSession
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// AudioBook.swift
|
||||
// App
|
||||
//
|
||||
// Created by Rasmus Krämer on 07.03.22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Audiobook {
|
||||
var streamId: String
|
||||
var audiobookId: String
|
||||
var playlistUrl: String
|
||||
|
||||
var startTime: Double = 0.0
|
||||
var duration: Double
|
||||
|
||||
var title: String
|
||||
var series: String?
|
||||
var author: String?
|
||||
var artworkUrl: String?
|
||||
|
||||
var token: String
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// PlaybackSession.swift
|
||||
// App
|
||||
//
|
||||
// Created by Rasmus Krämer on 12.04.22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PlaybackSession {
|
||||
var id: String
|
||||
var userId: String?
|
||||
var libraryItemId: String?
|
||||
var episodeId: String?
|
||||
var mediaType: String
|
||||
// var mediaMetadata: MediaTypeMetadata - It is not implemented in android?
|
||||
var chapters: [Chapter]
|
||||
var displayTitle: String?
|
||||
var displayAuthor: String?
|
||||
var coverPath: String?
|
||||
var duration: Double
|
||||
var playMethod: Int
|
||||
var startedAt: Double
|
||||
var updatedAt: Double
|
||||
var timeListening: Double
|
||||
var audioTracks: [AudioTrack]
|
||||
var currentTime: Double
|
||||
// var libraryItem: LibraryItem?
|
||||
// var localLibraryItem: LocalLibraryItem?
|
||||
var serverConnectionConfigId: String?
|
||||
var serverAddress: String?
|
||||
}
|
||||
struct Chapter {
|
||||
var id: Int
|
||||
var start: Double
|
||||
var end: Double
|
||||
var title: String?
|
||||
}
|
||||
struct AudioTrack {
|
||||
var index: Int
|
||||
var startOffset: Double
|
||||
var duration: Double
|
||||
var title: String
|
||||
var contentUrl: String
|
||||
var mimeType: String
|
||||
var metadata: FileMetadata?
|
||||
// var isLocal: Bool
|
||||
// var localFileId: String?
|
||||
// var audioProbeResult: AudioProbeResult? Need for local playback
|
||||
var serverIndex: Int?
|
||||
}
|
||||
struct FileMetadata {
|
||||
var filename: String
|
||||
var ext: String
|
||||
var path: String
|
||||
var relPath: String
|
||||
}
|
||||
@@ -24,21 +24,30 @@ class AudioPlayer: NSObject {
|
||||
private var playWhenReady: Bool
|
||||
|
||||
private var audioPlayer: AVPlayer
|
||||
public var audiobook: Audiobook
|
||||
private var playbackSession: PlaybackSession
|
||||
private var activeAudioTrack: AudioTrack
|
||||
|
||||
// MARK: - Constructor
|
||||
init(audiobook: Audiobook, playWhenReady: Bool = false) {
|
||||
init(playbackSession: PlaybackSession, playWhenReady: Bool = false) {
|
||||
self.playWhenReady = playWhenReady
|
||||
self.audiobook = audiobook
|
||||
self.audioPlayer = AVPlayer()
|
||||
self.playbackSession = playbackSession
|
||||
self.status = -1
|
||||
self.rate = 0.0
|
||||
|
||||
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: "", mimeType: "")
|
||||
|
||||
super.init()
|
||||
return
|
||||
}
|
||||
self.activeAudioTrack = playbackSession.audioTracks[0]
|
||||
|
||||
super.init()
|
||||
|
||||
initAudioSession()
|
||||
setupRemoteTransportControls()
|
||||
NowPlayingInfo.setAudiobook(audiobook: audiobook)
|
||||
|
||||
// Listen to player events
|
||||
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
|
||||
@@ -48,7 +57,7 @@ class AudioPlayer: NSObject {
|
||||
playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: &playerItemContext)
|
||||
|
||||
self.audioPlayer.replaceCurrentItem(with: playerItem)
|
||||
seek(self.audiobook.startTime)
|
||||
seek(self.playbackSession.currentTime)
|
||||
|
||||
NSLog("Audioplayer ready")
|
||||
}
|
||||
@@ -153,10 +162,10 @@ class AudioPlayer: NSObject {
|
||||
// MARK: - Private
|
||||
private func createAsset() -> AVAsset {
|
||||
let headers: [String: String] = [
|
||||
"Authorization": "Bearer \(audiobook.token)"
|
||||
"Authorization": "Bearer \(Store.serverConfig.token)"
|
||||
]
|
||||
|
||||
return AVURLAsset(url: URL(string: audiobook.playlistUrl)!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
|
||||
return AVURLAsset(url: URL(string: activeAudioTrack.contentUrl)!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
|
||||
}
|
||||
private func initAudioSession() {
|
||||
do {
|
||||
|
||||
@@ -9,9 +9,12 @@ import Foundation
|
||||
|
||||
class PlayerHandler {
|
||||
private static var player: AudioPlayer?
|
||||
// private static var item: any
|
||||
private static var session: PlaybackSession?
|
||||
|
||||
public static func setItem() {
|
||||
|
||||
public static func startPlayback(session: PlaybackSession) {
|
||||
if player != nil {
|
||||
player?.destroy()
|
||||
player = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,25 @@ func getData(from url: URL, completion: @escaping (UIImage?) -> Void) {
|
||||
}).resume()
|
||||
}
|
||||
|
||||
struct NowPlayingMetadata {
|
||||
var id: String
|
||||
var artworkUrl: String?
|
||||
var title: String
|
||||
var author: String?
|
||||
var series: String?
|
||||
}
|
||||
|
||||
class NowPlayingInfo {
|
||||
private static var nowPlayingInfo: [String: Any] = [:]
|
||||
private static var audiobook: Audiobook?
|
||||
|
||||
public static func setAudiobook(audiobook: Audiobook) {
|
||||
self.audiobook = audiobook
|
||||
setMetadata(nil)
|
||||
public static func setAudiobook(metadata: NowPlayingMetadata) {
|
||||
setMetadata(artwork: nil, metadata: nil)
|
||||
|
||||
if !shouldFetchCover() || audiobook.artworkUrl == nil {
|
||||
if !shouldFetchCover(id: metadata.id) || metadata.artworkUrl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = URL(string: audiobook.artworkUrl!) else { return }
|
||||
guard let url = URL(string: metadata.artworkUrl!) else { return }
|
||||
getData(from: url) { [self] image in
|
||||
guard let downloadedImage = image else {
|
||||
return
|
||||
@@ -37,7 +43,7 @@ class NowPlayingInfo {
|
||||
return downloadedImage
|
||||
})
|
||||
|
||||
self.setMetadata(artwork)
|
||||
self.setMetadata(artwork: artwork, metadata: metadata)
|
||||
}
|
||||
}
|
||||
public static func update(duration: Double, currentTime: Double, rate: Float) {
|
||||
@@ -49,31 +55,29 @@ class NowPlayingInfo {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||
}
|
||||
public static func reset() {
|
||||
audiobook = nil
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
||||
}
|
||||
|
||||
private static func setMetadata(_ artwork: MPMediaItemArtwork?) {
|
||||
if self.audiobook == nil {
|
||||
private static func setMetadata(artwork: MPMediaItemArtwork?, metadata: NowPlayingMetadata?) {
|
||||
if metadata == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if artwork != nil {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
||||
} else if shouldFetchCover() {
|
||||
} else if shouldFetchCover(id: metadata!.id) {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] = nil
|
||||
}
|
||||
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] = audiobook!.streamId
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyAssetURL] = URL(string: audiobook!.playlistUrl)
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] = metadata!.id
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = "hls"
|
||||
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = audiobook!.title
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = audiobook!.author ?? "unknown"
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = audiobook!.series
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = metadata!.title
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown"
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.series
|
||||
}
|
||||
private static func shouldFetchCover() -> Bool {
|
||||
audiobook != nil && (nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] as? String != audiobook!.streamId || nowPlayingInfo[MPMediaItemPropertyArtwork] == nil)
|
||||
private static func shouldFetchCover(id: String) -> Bool {
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] as? String != id || nowPlayingInfo[MPMediaItemPropertyArtwork] == nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,23 @@ import Foundation
|
||||
import RealmSwift
|
||||
|
||||
class Store {
|
||||
private static var _serverConfig: ServerConnectionConfig?
|
||||
// ONLY USE REALM IN Database.realmQueue OR ELSE THE APP WILL CRASH
|
||||
public static var serverConfig: ServerConnectionConfig {
|
||||
get {
|
||||
// TODO: change this when multiple configs are possible
|
||||
Database.getServerConnectionConfigs()[Database.getActiveServerConfigIndex()]
|
||||
if _serverConfig == nil {
|
||||
let index = Database.getActiveServerConfigIndex()
|
||||
// TODO: change this when multiple configs are possible
|
||||
_serverConfig = Database.getServerConnectionConfigs().first { config in
|
||||
return config.index == index
|
||||
}
|
||||
}
|
||||
|
||||
return _serverConfig ?? ServerConnectionConfig()
|
||||
}
|
||||
set(updated) {
|
||||
Database.setServerConnectionConfig(config: updated)
|
||||
_serverConfig = updated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user