mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 06:37:16 +02:00
Merge branch 'ios-downloads' into ios-downloads-realm-native
This commit is contained in:
@@ -264,7 +264,7 @@ class PodcastEpisode: Object, Codable {
|
||||
@Persisted var audioTrack: AudioTrack?
|
||||
@Persisted var duration: Double = 0
|
||||
@Persisted var size: Int = 0
|
||||
// var serverEpisodeId: String?
|
||||
var serverEpisodeId: String { self.id }
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case id,
|
||||
@@ -277,14 +277,44 @@ class PodcastEpisode: Object, Codable {
|
||||
audioFile,
|
||||
audioTrack,
|
||||
duration,
|
||||
size
|
||||
size,
|
||||
serverEpisodeId
|
||||
}
|
||||
|
||||
// TODO: Encoding
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decode(String.self, forKey: .id)
|
||||
index = try? values.decode(Int.self, forKey: .index)
|
||||
episode = try? values.decode(String.self, forKey: .episode)
|
||||
episodeType = try? values.decode(String.self, forKey: .episodeType)
|
||||
title = try values.decode(String.self, forKey: .title)
|
||||
subtitle = try? values.decode(String.self, forKey: .subtitle)
|
||||
desc = try? values.decode(String.self, forKey: .desc)
|
||||
audioFile = try? values.decode(AudioFile.self, forKey: .audioFile)
|
||||
audioTrack = try? values.decode(AudioTrack.self, forKey: .audioTrack)
|
||||
duration = try? values.decode(Double.self, forKey: .duration)
|
||||
size = try? values.decode(Int.self, forKey: .size)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(index, forKey: .index)
|
||||
try container.encode(episode, forKey: .episode)
|
||||
try container.encode(episodeType, forKey: .episodeType)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(subtitle, forKey: .subtitle)
|
||||
try container.encode(desc, forKey: .desc)
|
||||
try container.encode(audioFile, forKey: .audioFile)
|
||||
try container.encode(audioTrack, forKey: .audioTrack)
|
||||
try container.encode(duration, forKey: .duration)
|
||||
try container.encode(size, forKey: .size)
|
||||
try container.encode(serverEpisodeId, forKey: .serverEpisodeId)
|
||||
}
|
||||
}
|
||||
|
||||
class AudioFile: Object, Codable {
|
||||
@Persisted var index: Int = 0
|
||||
@Persisted var index: Int?
|
||||
@Persisted var ino: String = ""
|
||||
@Persisted var metadata: FileMetadata?
|
||||
|
||||
@@ -299,7 +329,7 @@ class AudioFile: Object, Codable {
|
||||
required init(from decoder: Decoder) throws {
|
||||
super.init()
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
index = try values.decode(Int.self, forKey: .index)
|
||||
index = try? values.decode(Int.self, forKey: .index)
|
||||
ino = try values.decode(String.self, forKey: .ino)
|
||||
metadata = try? values.decode(FileMetadata.self, forKey: .metadata)
|
||||
}
|
||||
@@ -339,6 +369,9 @@ class Author: Object, Codable {
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(coverPath, forKey: .coverPath)
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
class Chapter: Object, Codable {
|
||||
@@ -381,8 +414,7 @@ class AudioTrack: Object, Codable {
|
||||
@Persisted var contentUrl: String?
|
||||
@Persisted var mimeType: String = ""
|
||||
@Persisted var metadata: FileMetadata?
|
||||
// var isLocal: Bool
|
||||
// var localFileId: String?
|
||||
var localFileId: String?
|
||||
// var audioProbeResult: AudioProbeResult? Needed for local playback
|
||||
@Persisted var serverIndex: Int?
|
||||
|
||||
@@ -418,6 +450,20 @@ class AudioTrack: Object, Codable {
|
||||
try container.encode(metadata, forKey: .metadata)
|
||||
try container.encode(serverIndex, forKey: .serverIndex)
|
||||
}
|
||||
|
||||
mutating func setLocalInfo(filenameIdMap: [String: String], serverIndex: Int) -> Bool {
|
||||
if let localFileId = filenameIdMap[self.metadata?.filename ?? ""] {
|
||||
self.localFileId = localFileId
|
||||
self.serverIndex = serverIndex
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getLocalFile() -> LocalFile? {
|
||||
guard let localFileId = self.localFileId else { return nil }
|
||||
return Database.shared.getLocalFile(localFileId: localFileId)
|
||||
}
|
||||
}
|
||||
|
||||
class FileMetadata: Object, Codable {
|
||||
@@ -562,18 +608,17 @@ class MediaProgress: Object, Codable {
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
super.init()
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decode(String.self, forKey: .id)
|
||||
libraryItemId = try values.decode(String.self, forKey: .libraryItemId)
|
||||
episodeId = try? values.decode(String.self, forKey: .episodeId)
|
||||
duration = try values.decode(Double.self, forKey: .duration)
|
||||
progress = try values.decode(Double.self, forKey: .progress)
|
||||
currentTime = try values.decode(Double.self, forKey: .currentTime)
|
||||
duration = try values.doubleOrStringDecoder(key: .duration)
|
||||
progress = try values.doubleOrStringDecoder(key: .progress)
|
||||
currentTime = try values.doubleOrStringDecoder(key: .currentTime)
|
||||
isFinished = try values.decode(Bool.self, forKey: .isFinished)
|
||||
lastUpdate = try values.decode(Int.self, forKey: .lastUpdate)
|
||||
startedAt = try values.decode(Int.self, forKey: .startedAt)
|
||||
finishedAt = try? values.decode(Int.self, forKey: .finishedAt)
|
||||
lastUpdate = try values.intOrStringDecoder(key: .lastUpdate)
|
||||
startedAt = try values.intOrStringDecoder(key: .startedAt)
|
||||
finishedAt = try? values.intOrStringDecoder(key: .finishedAt)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
|
||||
@@ -73,6 +73,35 @@ class DownloadItemPart: Object, Codable {
|
||||
@Persisted var uri: String?
|
||||
@Persisted var destinationUri: String?
|
||||
@Persisted var progress: Double = 0
|
||||
var task: URLSessionDownloadTask?
|
||||
|
||||
struct DownloadItemPart: Realmable, Codable {
|
||||
var id: String = UUID().uuidString
|
||||
var filename: String?
|
||||
var itemTitle: String?
|
||||
var serverPath: String?
|
||||
var audioTrack: AudioTrack?
|
||||
var episode: PodcastEpisode?
|
||||
var completed: Bool = false
|
||||
var moved: Bool = false
|
||||
var failed: Bool = false
|
||||
var uri: String?
|
||||
var downloadURL: URL? {
|
||||
if let uri = self.uri {
|
||||
return URL(string: uri)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var destinationUri: String?
|
||||
var destinationURL: URL? {
|
||||
if let destinationUri = self.destinationUri {
|
||||
return AbsDownloader.itemDownloadFolder(path: destinationUri)!
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var progress: Double = 0
|
||||
var task: URLSessionDownloadTask!
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
@@ -105,3 +134,36 @@ class DownloadItemPart: Object, Codable {
|
||||
try container.encode(progress, forKey: .progress)
|
||||
}
|
||||
}
|
||||
|
||||
extension DownloadItemPart {
|
||||
init(filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?) {
|
||||
self.filename = filename
|
||||
self.itemTitle = itemTitle
|
||||
self.serverPath = serverPath
|
||||
self.audioTrack = audioTrack
|
||||
self.episode = episode
|
||||
|
||||
let config = Store.serverConfig!
|
||||
var downloadUrl = ""
|
||||
if (serverPath.hasSuffix("/cover")) {
|
||||
downloadUrl += "\(config.address)\(serverPath)?token=\(config.token)"
|
||||
downloadUrl += "&format=jpeg" // For cover images force to jpeg
|
||||
} else {
|
||||
downloadUrl = destination
|
||||
}
|
||||
self.uri = downloadUrl
|
||||
self.destinationUri = destination
|
||||
}
|
||||
|
||||
func mimeType() -> String? {
|
||||
if let track = audioTrack {
|
||||
return track.mimeType
|
||||
} else if let podcastTrack = episode?.audioTrack {
|
||||
return podcastTrack.mimeType
|
||||
} else if serverPath?.hasSuffix("/cover") ?? false {
|
||||
return "image/jpg"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,33 @@ class LocalLibraryItem: Object, Codable {
|
||||
@Persisted var serverAddress: String?
|
||||
@Persisted var serverUserId: String?
|
||||
@Persisted(indexed: true) var libraryItemId: String?
|
||||
|
||||
var contentUrl: String? {
|
||||
if let path = _contentUrl {
|
||||
return AbsDownloader.itemDownloadFolder(path: path)!.absoluteString
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var contentDirectory: URL? {
|
||||
if let path = _contentUrl {
|
||||
return AbsDownloader.itemDownloadFolder(path: path)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var coverContentUrl: String? {
|
||||
if let path = self._coverContentUrl {
|
||||
return AbsDownloader.itemDownloadFolder(path: path)!.absoluteString
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var isBook: Bool { self.mediaType == "book" }
|
||||
var isPodcast: Bool { self.mediaType == "podcast" }
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case id, basePath, contentUrl, isInvalid, mediaType, media, localFiles, coverContentUrl, isLocal, serverConnectionConfigId, serverAddress, serverUserId, libraryItemId
|
||||
@@ -37,14 +64,12 @@ class LocalLibraryItem: Object, Codable {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decode(String.self, forKey: .id)
|
||||
basePath = try values.decode(String.self, forKey: .basePath)
|
||||
contentUrl = try values.decode(String.self, forKey: .contentUrl)
|
||||
isInvalid = try values.decode(Bool.self, forKey: .isInvalid)
|
||||
mediaType = try values.decode(String.self, forKey: .mediaType)
|
||||
media = try? values.decode(MediaType.self, forKey: .media)
|
||||
if let files = try? values.decode([LocalFile].self, forKey: .localFiles) {
|
||||
localFiles.append(objectsIn: files)
|
||||
}
|
||||
_coverContentUrl = try values.decode(String.self, forKey: .coverContentUrl)
|
||||
isLocal = try values.decode(Bool.self, forKey: .isLocal)
|
||||
serverConnectionConfigId = try? values.decode(String.self, forKey: .serverConnectionConfigId)
|
||||
serverAddress = try? values.decode(String.self, forKey: .serverAddress)
|
||||
@@ -111,9 +136,13 @@ class LocalFile: Object, Codable {
|
||||
@Persisted var contentUrl: String = ""
|
||||
@Persisted var mimeType: String?
|
||||
@Persisted var size: Int = 0
|
||||
|
||||
var contentUrl: String { AbsDownloader.itemDownloadFolder(path: _contentUrl)!.absoluteString }
|
||||
var contentPath: URL { AbsDownloader.itemDownloadFolder(path: _contentUrl)! }
|
||||
var basePath: String? { self.filename }
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case id, filename, contentUrl, absolutePath, mimeType, size
|
||||
case id, filename, contentUrl, mimeType, size, basePath
|
||||
}
|
||||
|
||||
override init() {
|
||||
@@ -124,7 +153,6 @@ class LocalFile: Object, Codable {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decode(String.self, forKey: .id)
|
||||
filename = try? values.decode(String.self, forKey: .filename)
|
||||
contentUrl = try values.decode(String.self, forKey: .contentUrl)
|
||||
mimeType = try? values.decode(String.self, forKey: .mimeType)
|
||||
size = try values.decode(Int.self, forKey: .size)
|
||||
}
|
||||
@@ -134,9 +162,9 @@ class LocalFile: Object, Codable {
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(filename, forKey: .filename)
|
||||
try container.encode(contentUrl, forKey: .contentUrl)
|
||||
try container.encode(absolutePath, forKey: .absolutePath)
|
||||
try container.encode(mimeType, forKey: .mimeType)
|
||||
try container.encode(size, forKey: .size)
|
||||
try container.encode(basePath, forKey: .basePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +185,8 @@ class LocalMediaProgress: Object, Codable {
|
||||
@Persisted var serverUserId: String?
|
||||
@Persisted(indexed: true) var libraryItemId: String?
|
||||
@Persisted(indexed: true) var episodeId: String?
|
||||
|
||||
var progressPercent: Int { Int(self.progress * 100) }
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case id, localLibraryItemId, localEpisodeId, duration, progress, currentTime, isFinished, lastUpdate, startedAt, finishedAt, serverConnectionConfigId, serverAddress, serverUserId, libraryItemId, episodeId
|
||||
|
||||
@@ -13,39 +13,44 @@ extension LocalLibraryItem {
|
||||
|
||||
self.contentUrl = localUrl
|
||||
self.mediaType = item.mediaType
|
||||
self.media = item.media
|
||||
self.localFiles.append(objectsIn: files)
|
||||
self.coverContentUrl = coverPath
|
||||
self._coverContentUrl = coverPath
|
||||
self.libraryItemId = item.id
|
||||
self.serverConnectionConfigId = server.id
|
||||
self.serverAddress = server.address
|
||||
self.serverUserId = server.userId
|
||||
|
||||
// Link the audio tracks and files
|
||||
linkLocalFiles(files, fromMedia: item.media)
|
||||
}
|
||||
|
||||
var contentUrl: String? {
|
||||
set(url) {
|
||||
_contentUrl = url
|
||||
}
|
||||
get {
|
||||
if let path = _contentUrl {
|
||||
return AbsDownloader.downloadsDirectory.appendingPathComponent(path).absoluteString
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
mutating func addFiles(_ files: [LocalFile], item: LibraryItem) throws {
|
||||
guard self.isPodcast else { throw LibraryItemDownloadError.podcastOnlySupported }
|
||||
self.localFiles.append(contentsOf: files.filter({ $0.isAudioFile() }))
|
||||
linkLocalFiles(self.localFiles, fromMedia: item.media)
|
||||
}
|
||||
|
||||
var coverContentUrl: String? {
|
||||
set(url) {
|
||||
_coverContentUrl = url
|
||||
}
|
||||
get {
|
||||
if let path = self._coverContentUrl {
|
||||
return AbsDownloader.downloadsDirectory.appendingPathComponent(path).absoluteString
|
||||
} else {
|
||||
return nil
|
||||
mutating private func linkLocalFiles(_ files: [LocalFile], fromMedia: MediaType) {
|
||||
var fromMedia = fromMedia
|
||||
let fileMap = files.map { ($0.filename ?? "", $0.id) }
|
||||
let fileIdByFilename = Dictionary(fileMap, uniquingKeysWith: { (_, last) in last })
|
||||
if ( self.isBook ) {
|
||||
if let tracks = fromMedia.tracks {
|
||||
for i in tracks.indices {
|
||||
_ = fromMedia.tracks?[i].setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: i)
|
||||
}
|
||||
}
|
||||
} else if ( self.isPodcast ) {
|
||||
if let episodes = fromMedia.episodes {
|
||||
fromMedia.episodes = episodes.compactMap { episode in
|
||||
// Filter out episodes not downloaded
|
||||
var episode = episode
|
||||
let episodeIsDownloaded = episode.audioTrack?.setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: 0) ?? false
|
||||
return episodeIsDownloaded ? episode : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
self.media = fromMedia
|
||||
}
|
||||
|
||||
func getDuration() -> Double {
|
||||
@@ -54,7 +59,13 @@ extension LocalLibraryItem {
|
||||
return total
|
||||
}
|
||||
|
||||
func getPlaybackSession(episode: LocalPodcastEpisode?) -> PlaybackSession {
|
||||
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 {
|
||||
let localEpisodeId = episode?.id
|
||||
let sessionId = "play_local_\(UUID().uuidString)"
|
||||
|
||||
@@ -62,13 +73,13 @@ extension LocalLibraryItem {
|
||||
let mediaProgressId = (localEpisodeId != nil) ? "\(self.id)-\(localEpisodeId!)" : self.id
|
||||
let mediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: mediaProgressId)
|
||||
|
||||
// TODO: Clean up add mediaType methods for displayTitle and displayAuthor
|
||||
let mediaMetadata = self.media?.metadata
|
||||
let audioTracks = self.media?.tracks
|
||||
let authorName = mediaMetadata?.authorName
|
||||
let chapters = self.media?.chapters
|
||||
var audioTracks = self.media?.tracks
|
||||
let authorName = mediaMetadata?.authorDisplayName
|
||||
|
||||
if let episode = episode {
|
||||
// TODO: Implement podcast
|
||||
if let episode = episode, let track = episode.audioTrack {
|
||||
audioTracks = [track]
|
||||
}
|
||||
|
||||
let dateNow = Date().timeIntervalSince1970
|
||||
@@ -78,18 +89,19 @@ extension LocalLibraryItem {
|
||||
libraryItemId: self.libraryItemId,
|
||||
episodeId: episode?.serverEpisodeId,
|
||||
mediaType: self.mediaType,
|
||||
chapters: [],
|
||||
chapters: chapters ?? [],
|
||||
displayTitle: mediaMetadata?.title,
|
||||
displayAuthor: authorName,
|
||||
coverPath: nil,
|
||||
coverPath: self.coverContentUrl,
|
||||
duration: self.getDuration(),
|
||||
playMethod: 3,
|
||||
playMethod: PlayMethod.local.rawValue,
|
||||
startedAt: dateNow,
|
||||
updatedAt: 0,
|
||||
timeListening: 0.0,
|
||||
audioTracks: [],
|
||||
audioTracks: audioTracks ?? [],
|
||||
currentTime: mediaProgress?.currentTime ?? 0.0,
|
||||
libraryItem: nil,
|
||||
localLibraryItem: self,
|
||||
serverConnectionConfigId: self.serverConnectionConfigId,
|
||||
serverAddress: self.serverAddress
|
||||
)
|
||||
@@ -103,7 +115,7 @@ extension LocalFile {
|
||||
self.id = "\(libraryItemId)_\(filename.toBase64())"
|
||||
self.filename = filename
|
||||
self.mimeType = mimeType
|
||||
self.contentUrl = localUrl
|
||||
self._contentUrl = localUrl
|
||||
self.size = fileSize
|
||||
}
|
||||
|
||||
@@ -123,27 +135,84 @@ extension LocalFile {
|
||||
}
|
||||
|
||||
extension LocalMediaProgress {
|
||||
convenience init(localLibraryItem: LocalLibraryItem, episode: LocalPodcastEpisode?, progress: MediaProgress) {
|
||||
convenience init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?) {
|
||||
self.init()
|
||||
|
||||
self.id = localLibraryItem.id
|
||||
self.localLibraryItemId = localLibraryItem.id
|
||||
self.libraryItemId = localLibraryItem.libraryItemId
|
||||
|
||||
if let episode = episode {
|
||||
self.id += "-\(episode.id)"
|
||||
self.episodeId = episode.id
|
||||
}
|
||||
|
||||
self.serverAddress = localLibraryItem.serverAddress
|
||||
self.serverUserId = localLibraryItem.serverUserId
|
||||
self.serverConnectionConfigId = localLibraryItem.serverConnectionConfigId
|
||||
|
||||
self.duration = progress.duration
|
||||
self.currentTime = progress.currentTime
|
||||
self.duration = localLibraryItem.getDuration()
|
||||
self.progress = 0.0
|
||||
self.currentTime = 0.0
|
||||
self.isFinished = false
|
||||
self.lastUpdate = Int(Date().timeIntervalSince1970)
|
||||
self.startedAt = 0
|
||||
self.finishedAt = nil
|
||||
|
||||
if let episode = episode {
|
||||
self.id += "-\(episode.id)"
|
||||
self.episodeId = episode.id
|
||||
self.duration = episode.duration ?? 0.0
|
||||
}
|
||||
}
|
||||
|
||||
init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?, progress: MediaProgress) {
|
||||
self.init(localLibraryItem: localLibraryItem, episode: episode)
|
||||
self.duration = progress.duration
|
||||
self.progress = progress.progress
|
||||
self.currentTime = progress.currentTime
|
||||
self.isFinished = progress.isFinished
|
||||
self.lastUpdate = progress.lastUpdate
|
||||
self.startedAt = progress.startedAt
|
||||
self.finishedAt = progress.finishedAt
|
||||
}
|
||||
|
||||
mutating func updateIsFinished(_ finished: Bool) {
|
||||
if self.isFinished != finished {
|
||||
self.progress = finished ? 1.0 : 0.0
|
||||
}
|
||||
|
||||
if self.startedAt == 0 && finished {
|
||||
self.startedAt = Int(Date().timeIntervalSince1970)
|
||||
}
|
||||
|
||||
self.isFinished = finished
|
||||
self.lastUpdate = Int(Date().timeIntervalSince1970)
|
||||
self.finishedAt = finished ? lastUpdate : nil
|
||||
}
|
||||
|
||||
mutating func updateFromPlaybackSession(_ playbackSession: PlaybackSession) {
|
||||
self.currentTime = playbackSession.currentTime
|
||||
self.progress = playbackSession.progress
|
||||
self.lastUpdate = Int(Date().timeIntervalSince1970)
|
||||
self.isFinished = playbackSession.progress >= 100.0
|
||||
self.finishedAt = self.isFinished ? self.lastUpdate : nil
|
||||
}
|
||||
|
||||
mutating func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) {
|
||||
self.isFinished = serverMediaProgress.isFinished
|
||||
self.progress = serverMediaProgress.progress
|
||||
self.currentTime = serverMediaProgress.currentTime
|
||||
self.duration = serverMediaProgress.duration
|
||||
self.lastUpdate = serverMediaProgress.lastUpdate
|
||||
self.finishedAt = serverMediaProgress.finishedAt
|
||||
self.startedAt = serverMediaProgress.startedAt
|
||||
}
|
||||
|
||||
static func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) -> LocalMediaProgress? {
|
||||
if let localMediaProgressId = localMediaProgressId {
|
||||
return Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId)
|
||||
} else if let localLibraryItemId = localLibraryItemId {
|
||||
guard let localLibraryItem = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) else { return nil }
|
||||
let episode = localLibraryItem.getPodcastEpisode(episodeId: localEpisodeId)
|
||||
return LocalMediaProgress(localLibraryItem: localLibraryItem, episode: episode)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PlaybackSession: Decodable, Encodable {
|
||||
struct PlaybackSession: Codable {
|
||||
var id: String
|
||||
var userId: String?
|
||||
var libraryItemId: String?
|
||||
@@ -26,7 +26,25 @@ struct PlaybackSession: Decodable, Encodable {
|
||||
var audioTracks: [AudioTrack]
|
||||
var currentTime: Double
|
||||
var libraryItem: LibraryItem?
|
||||
//var localLibraryItem: LocalLibraryItem?
|
||||
var localLibraryItem: LocalLibraryItem?
|
||||
var serverConnectionConfigId: String?
|
||||
var serverAddress: String?
|
||||
|
||||
var isLocal: Bool { self.localLibraryItem != nil }
|
||||
|
||||
var localMediaProgressId: String {
|
||||
if let episodeId = episodeId {
|
||||
return "\(localLibraryItem!.id)-\(episodeId)"
|
||||
} else {
|
||||
return localLibraryItem!.id
|
||||
}
|
||||
}
|
||||
|
||||
var totalDuration: Double {
|
||||
var total = 0.0
|
||||
self.audioTracks.forEach { total += $0.duration }
|
||||
return total
|
||||
}
|
||||
|
||||
var progress: Double { self.currentTime / self.totalDuration }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user