Working local file playback

This commit is contained in:
ronaldheft 2022-08-11 18:29:55 -04:00
parent 65fcc45bc6
commit 9477860bca
6 changed files with 74 additions and 24 deletions

View file

@ -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")

View file

@ -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 {

View file

@ -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,

View file

@ -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)"

View file

@ -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)

View file

@ -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,18 +34,27 @@ 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 }
ApiClient.getData(from: url) { [self] image in let coverImage = UIImage(contentsOfFile: artworkUrl)
guard let downloadedImage = image else { guard let coverImage = coverImage else { return }
return let artwork = MPMediaItemArtwork(boundsSize: coverImage.size) { _ -> UIImage in
return coverImage
} }
let artwork = MPMediaItemArtwork.init(boundsSize: downloadedImage.size, requestHandler: { _ -> UIImage in
return downloadedImage
})
self.setMetadata(artwork: artwork, metadata: metadata) self.setMetadata(artwork: artwork, metadata: metadata)
} else {
guard let url = metadata.coverUrl else { return }
ApiClient.getData(from: url) { [self] image in
guard let downloadedImage = image else {
return
}
let artwork = MPMediaItemArtwork.init(boundsSize: downloadedImage.size, requestHandler: { _ -> UIImage in
return downloadedImage
})
self.setMetadata(artwork: artwork, metadata: metadata)
}
} }
} }
public func update(duration: Double, currentTime: Double, rate: Float) { public func update(duration: Double, currentTime: Double, rate: Float) {