mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-21 19:25:00 +02:00
Working local file playback
This commit is contained in:
parent
65fcc45bc6
commit
9477860bca
6 changed files with 74 additions and 24 deletions
|
@ -45,11 +45,19 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
let isLocalItem = libraryItemId?.starts(with: "local_") ?? false
|
let isLocalItem = libraryItemId?.starts(with: "local_") ?? false
|
||||||
if (isLocalItem) {
|
if (isLocalItem) {
|
||||||
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: libraryItemId!)
|
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: libraryItemId!)
|
||||||
// TODO: Logic required for podcasts here
|
let episode = item?.getPodcastEpisode(episodeId: episodeId)
|
||||||
let playbackSession = item?.getPlaybackSession(episode: nil)
|
let playbackSession = item?.getPlaybackSession(episode: episode)
|
||||||
|
sendPrepareMetadataEvent(itemId: libraryItemId!, playWhenReady: playWhenReady)
|
||||||
|
do {
|
||||||
|
self.sendPlaybackSession(session: try playbackSession.asDictionary())
|
||||||
|
call.resolve(try playbackSession.asDictionary())
|
||||||
|
} catch(let exception) {
|
||||||
|
NSLog("failed to convert session to json")
|
||||||
|
debugPrint(exception)
|
||||||
|
call.resolve([:])
|
||||||
|
}
|
||||||
PlayerHandler.startPlayback(session: playbackSession!, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
PlayerHandler.startPlayback(session: playbackSession!, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
self.sendMetadata()
|
self.sendMetadata()
|
||||||
call.resolve()
|
|
||||||
} else { // Playing from the server
|
} else { // Playing from the server
|
||||||
sendPrepareMetadataEvent(itemId: libraryItemId!, playWhenReady: playWhenReady)
|
sendPrepareMetadataEvent(itemId: libraryItemId!, playWhenReady: playWhenReady)
|
||||||
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { session in
|
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { session in
|
||||||
|
@ -62,12 +70,12 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
call.resolve([:])
|
call.resolve([:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PlayerHandler.startPlayback(session: session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
PlayerHandler.startPlayback(session: session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
self.sendMetadata()
|
self.sendMetadata()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func closePlayback(_ call: CAPPluginCall) {
|
@objc func closePlayback(_ call: CAPPluginCall) {
|
||||||
NSLog("Close playback")
|
NSLog("Close playback")
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,8 @@ struct Metadata: Realmable, Codable {
|
||||||
var seriesName: String?
|
var seriesName: String?
|
||||||
var feedUrl: String?
|
var feedUrl: String?
|
||||||
|
|
||||||
|
var authorDisplayName: String { self.authorName ?? "Unknown" }
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
title = "Unknown"
|
title = "Unknown"
|
||||||
genres = []
|
genres = []
|
||||||
|
@ -251,6 +253,11 @@ struct AudioTrack: Realmable, Codable {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLocalFile() -> LocalFile? {
|
||||||
|
guard let localFileId = self.localFileId else { return nil }
|
||||||
|
return Database.shared.getLocalFile(localFileId: localFileId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileMetadata: Realmable, Codable {
|
struct FileMetadata: Realmable, Codable {
|
||||||
|
|
|
@ -59,6 +59,12 @@ extension LocalLibraryItem {
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPodcastEpisode(episodeId: String?) -> PodcastEpisode? {
|
||||||
|
guard self.isPodcast else { return nil }
|
||||||
|
guard let episodes = self.media?.episodes else { return nil }
|
||||||
|
return episodes.first(where: { $0.id == episodeId })
|
||||||
|
}
|
||||||
|
|
||||||
func getPlaybackSession(episode: PodcastEpisode?) -> PlaybackSession {
|
func getPlaybackSession(episode: PodcastEpisode?) -> PlaybackSession {
|
||||||
let localEpisodeId = episode?.id
|
let localEpisodeId = episode?.id
|
||||||
let sessionId = "play_local_\(UUID().uuidString)"
|
let sessionId = "play_local_\(UUID().uuidString)"
|
||||||
|
@ -67,13 +73,13 @@ extension LocalLibraryItem {
|
||||||
let mediaProgressId = (localEpisodeId != nil) ? "\(self.id)-\(localEpisodeId!)" : self.id
|
let mediaProgressId = (localEpisodeId != nil) ? "\(self.id)-\(localEpisodeId!)" : self.id
|
||||||
let mediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: mediaProgressId)
|
let mediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: mediaProgressId)
|
||||||
|
|
||||||
// TODO: Clean up add mediaType methods for displayTitle and displayAuthor
|
|
||||||
let mediaMetadata = self.media?.metadata
|
let mediaMetadata = self.media?.metadata
|
||||||
let audioTracks = self.media?.tracks
|
let chapters = self.media?.chapters
|
||||||
let authorName = mediaMetadata?.authorName
|
var audioTracks = self.media?.tracks
|
||||||
|
let authorName = mediaMetadata?.authorDisplayName
|
||||||
|
|
||||||
if let episode = episode {
|
if let episode = episode, let track = episode.audioTrack {
|
||||||
// TODO: Implement podcast
|
audioTracks = [track]
|
||||||
}
|
}
|
||||||
|
|
||||||
let dateNow = Date().timeIntervalSince1970
|
let dateNow = Date().timeIntervalSince1970
|
||||||
|
@ -81,18 +87,18 @@ extension LocalLibraryItem {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
userId: self.serverUserId,
|
userId: self.serverUserId,
|
||||||
libraryItemId: self.libraryItemId,
|
libraryItemId: self.libraryItemId,
|
||||||
episodeId: episode?.id,
|
episodeId: episode?.serverEpisodeId,
|
||||||
mediaType: self.mediaType,
|
mediaType: self.mediaType,
|
||||||
chapters: [],
|
chapters: chapters ?? [],
|
||||||
displayTitle: mediaMetadata?.title,
|
displayTitle: mediaMetadata?.title,
|
||||||
displayAuthor: authorName,
|
displayAuthor: authorName,
|
||||||
coverPath: nil,
|
coverPath: self.coverContentUrl,
|
||||||
duration: self.getDuration(),
|
duration: self.getDuration(),
|
||||||
playMethod: 3,
|
playMethod: PlayMethod.local.rawValue,
|
||||||
startedAt: dateNow,
|
startedAt: dateNow,
|
||||||
updatedAt: 0,
|
updatedAt: 0,
|
||||||
timeListening: 0.0,
|
timeListening: 0.0,
|
||||||
audioTracks: [],
|
audioTracks: audioTracks ?? [],
|
||||||
currentTime: mediaProgress?.currentTime ?? 0.0,
|
currentTime: mediaProgress?.currentTime ?? 0.0,
|
||||||
libraryItem: nil,
|
libraryItem: nil,
|
||||||
serverConnectionConfigId: self.serverConnectionConfigId,
|
serverConnectionConfigId: self.serverConnectionConfigId,
|
||||||
|
|
|
@ -286,6 +286,17 @@ class AudioPlayer: NSObject {
|
||||||
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
||||||
let url = URL(string: urlstr)!
|
let url = URL(string: urlstr)!
|
||||||
return AVURLAsset(url: url)
|
return AVURLAsset(url: url)
|
||||||
|
} 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
|
} else { // HLS Transcode
|
||||||
let headers: [String: String] = [
|
let headers: [String: String] = [
|
||||||
"Authorization": "Bearer \(Store.serverConfig!.token)"
|
"Authorization": "Bearer \(Store.serverConfig!.token)"
|
||||||
|
|
|
@ -131,6 +131,11 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getLocalFile(localFileId: String) -> LocalFile? {
|
||||||
|
let realm = try! Realm()
|
||||||
|
return realm.object(ofType: LocalFile.self, forPrimaryKey: localFileId)
|
||||||
|
}
|
||||||
|
|
||||||
public func getDownloadItem(downloadItemId: String) -> DownloadItem? {
|
public func getDownloadItem(downloadItemId: String) -> DownloadItem? {
|
||||||
let realm = try! Realm()
|
let realm = try! Realm()
|
||||||
return realm.object(ofType: DownloadItem.self, forPrimaryKey: downloadItemId)
|
return realm.object(ofType: DownloadItem.self, forPrimaryKey: downloadItemId)
|
||||||
|
|
|
@ -15,6 +15,10 @@ struct NowPlayingMetadata {
|
||||||
var title: String
|
var title: String
|
||||||
var author: String?
|
var author: String?
|
||||||
var series: String?
|
var series: String?
|
||||||
|
var coverUrl: URL? {
|
||||||
|
guard let url = URL(string: "\(Store.serverConfig!.address)/api/items/\(itemId)/cover?token=\(Store.serverConfig!.token)") else { return nil }
|
||||||
|
return url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NowPlayingInfo {
|
class NowPlayingInfo {
|
||||||
|
@ -30,9 +34,17 @@ class NowPlayingInfo {
|
||||||
public func setSessionMetadata(metadata: NowPlayingMetadata) {
|
public func setSessionMetadata(metadata: NowPlayingMetadata) {
|
||||||
setMetadata(artwork: nil, metadata: metadata)
|
setMetadata(artwork: nil, metadata: metadata)
|
||||||
|
|
||||||
guard let url = URL(string: "\(Store.serverConfig!.address)/api/items/\(metadata.itemId)/cover?token=\(Store.serverConfig!.token)") else {
|
let isLocalItem = metadata.itemId.starts(with: "local_")
|
||||||
return
|
if isLocalItem {
|
||||||
|
guard let artworkUrl = metadata.artworkUrl else { return }
|
||||||
|
let coverImage = UIImage(contentsOfFile: artworkUrl)
|
||||||
|
guard let coverImage = coverImage else { return }
|
||||||
|
let artwork = MPMediaItemArtwork(boundsSize: coverImage.size) { _ -> UIImage in
|
||||||
|
return coverImage
|
||||||
}
|
}
|
||||||
|
self.setMetadata(artwork: artwork, metadata: metadata)
|
||||||
|
} else {
|
||||||
|
guard let url = metadata.coverUrl else { return }
|
||||||
ApiClient.getData(from: url) { [self] image in
|
ApiClient.getData(from: url) { [self] image in
|
||||||
guard let downloadedImage = image else {
|
guard let downloadedImage = image else {
|
||||||
return
|
return
|
||||||
|
@ -44,6 +56,7 @@ class NowPlayingInfo {
|
||||||
self.setMetadata(artwork: artwork, metadata: metadata)
|
self.setMetadata(artwork: artwork, metadata: metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public func update(duration: Double, currentTime: Double, rate: Float) {
|
public func update(duration: Double, currentTime: Double, rate: Float) {
|
||||||
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
||||||
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue