mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-12 15:04:43 +02:00
feat: iOS download groundwork
This commit is contained in:
parent
e07e7f70d6
commit
2ca9ce797d
6 changed files with 368 additions and 120 deletions
|
@ -35,6 +35,7 @@
|
||||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
|
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
|
||||||
|
C4B265F5285A5A6600E1B5C3 /* LocalLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */; };
|
||||||
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D0677428106D0C00B8F875 /* DataClasses.swift */; };
|
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D0677428106D0C00B8F875 /* DataClasses.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
||||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalLibrary.swift; sourceTree = "<group>"; };
|
||||||
C4D0677428106D0C00B8F875 /* DataClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataClasses.swift; sourceTree = "<group>"; };
|
C4D0677428106D0C00B8F875 /* DataClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataClasses.swift; sourceTree = "<group>"; };
|
||||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
@ -135,6 +137,7 @@
|
||||||
3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */,
|
3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */,
|
||||||
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */,
|
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */,
|
||||||
C4D0677428106D0C00B8F875 /* DataClasses.swift */,
|
C4D0677428106D0C00B8F875 /* DataClasses.swift */,
|
||||||
|
C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */,
|
||||||
3A90295E280968E700E1D427 /* PlaybackReport.swift */,
|
3A90295E280968E700E1D427 /* PlaybackReport.swift */,
|
||||||
4DF74911287105C600AC7814 /* DeviceSettings.swift */,
|
4DF74911287105C600AC7814 /* DeviceSettings.swift */,
|
||||||
);
|
);
|
||||||
|
@ -315,6 +318,7 @@
|
||||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
||||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||||
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
|
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
|
||||||
|
C4B265F5285A5A6600E1B5C3 /* LocalLibrary.swift in Sources */,
|
||||||
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
|
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
|
||||||
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */,
|
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */,
|
||||||
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */,
|
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */,
|
||||||
|
|
|
@ -14,27 +14,33 @@ public class AbsDownloader: CAPPlugin {
|
||||||
let libraryItemId = call.getString("libraryItemId")
|
let libraryItemId = call.getString("libraryItemId")
|
||||||
let episodeId = call.getString("episodeId")
|
let episodeId = call.getString("episodeId")
|
||||||
|
|
||||||
NSLog("Download library item \(libraryItemId ?? "N/A") episode \(episodeId ?? "")")
|
NSLog("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "")")
|
||||||
|
|
||||||
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId!, episodeId: episodeId) { libraryItem in
|
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId!, episodeId: episodeId) { libraryItem in
|
||||||
if (libraryItem == nil) {
|
if (libraryItem == nil) {
|
||||||
NSLog("Library item not found")
|
NSLog("Library item not found")
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} else {
|
} else {
|
||||||
NSLog("Got library item \(libraryItem!)")
|
NSLog("Got library item from server \(libraryItem!.id)")
|
||||||
|
|
||||||
// TODO: break out in seperate functions
|
self.startLibraryItemDownload(libraryItem: libraryItem!)
|
||||||
libraryItem!.media.tracks?.forEach { track in
|
call.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func startLibraryItemDownload(libraryItem: LibraryItem) {
|
||||||
|
let length = libraryItem.media.tracks.count
|
||||||
|
if length > 0 {
|
||||||
|
libraryItem.media.tracks.enumerated().forEach { position, track in
|
||||||
NSLog("TRACK \(track.contentUrl!)")
|
NSLog("TRACK \(track.contentUrl!)")
|
||||||
// filename needs to be encoded otherwise would just use contentUrl
|
// filename needs to be encoded otherwise would just use contentUrl
|
||||||
let filename = track.metadata?.filename ?? ""
|
let filename = track.metadata?.filename ?? ""
|
||||||
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
|
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
|
||||||
let urlstr = "\(Store.serverConfig!.address)/s/item/\(libraryItemId!)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
let urlstr = "\(Store.serverConfig!.address)/s/item/\(libraryItem.id)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
||||||
let url = URL(string: urlstr)!
|
let url = URL(string: urlstr)!
|
||||||
|
|
||||||
|
|
||||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
let itemDirectory = documentsDirectory.appendingPathComponent("\(libraryItemId!)")
|
let itemDirectory = documentsDirectory.appendingPathComponent("\(libraryItem.id)")
|
||||||
NSLog("ITEM DIR \(itemDirectory)")
|
NSLog("ITEM DIR \(itemDirectory)")
|
||||||
|
|
||||||
// Create library item directory
|
// Create library item directory
|
||||||
|
@ -53,8 +59,8 @@ public class AbsDownloader: CAPPlugin {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
NSLog("Download TMP file URL \(fileURL)")
|
NSLog("Download TMP file URL \(fileURL)")
|
||||||
let imageData = try Data(contentsOf:fileURL)
|
let audioData = try Data(contentsOf:fileURL)
|
||||||
try imageData.write(to: trackFilename)
|
try audioData.write(to: trackFilename)
|
||||||
NSLog("Download written to \(trackFilename)")
|
NSLog("Download written to \(trackFilename)")
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("FILE ERROR: \(error)")
|
NSLog("FILE ERROR: \(error)")
|
||||||
|
@ -62,10 +68,17 @@ public class AbsDownloader: CAPPlugin {
|
||||||
}
|
}
|
||||||
downloadTask.resume()
|
downloadTask.resume()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
NSLog("No audio tracks for the supplied library item")
|
||||||
call.resolve()
|
}
|
||||||
}
|
// let encoder = JSONEncoder()
|
||||||
|
// let jsobj = try encoder.encode(Download)
|
||||||
|
// notifyListeners("onItemDownloadComplete", data: jsobj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
struct DownloadItem: Codable {
|
||||||
|
var isDownloading = false
|
||||||
|
var progress: Float = 0
|
||||||
|
var resumeData: Data?
|
||||||
|
// var task: URLSessionDownloadTask?
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
// DataClasses.swift
|
// DataClasses.swift
|
||||||
// App
|
// App
|
||||||
//
|
//
|
||||||
// Created by Benonymity on 4/20/22.
|
// Created by benonymity on 4/20/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreMedia
|
import CoreMedia
|
||||||
|
import RealmSwift
|
||||||
|
|
||||||
struct LibraryItem: Codable {
|
struct LibraryItem: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
|
@ -30,87 +31,88 @@ struct LibraryItem: Codable {
|
||||||
var libraryFiles: [LibraryFile]
|
var libraryFiles: [LibraryFile]
|
||||||
var userMediaProgress:MediaProgress?
|
var userMediaProgress:MediaProgress?
|
||||||
}
|
}
|
||||||
struct MediaType: Codable {
|
class MediaType: Object, Codable {
|
||||||
var libraryItemId: String?
|
var libraryItemId: String? = ""
|
||||||
var metadata: Metadata
|
var metadata: Metadata?
|
||||||
var coverPath: String?
|
var coverPath: String? = ""
|
||||||
var tags: [String]?
|
var tags: List<String?>
|
||||||
var audioFiles: [AudioTrack]?
|
var audioFiles: List<AudioFile>
|
||||||
var chapters: [Chapter]?
|
var chapters: List<Chapter>
|
||||||
var tracks: [AudioTrack]?
|
var tracks: List<AudioTrack>
|
||||||
var size: Int64?
|
var size: Int64? = nil
|
||||||
var duration: Double?
|
var duration: Double? = nil
|
||||||
var episodes: [PodcastEpisode]?
|
var episodes: List<PodcastEpisode>
|
||||||
var autoDownloadEpisodes: Bool?
|
var autoDownloadEpisodes: Bool? = nil
|
||||||
}
|
}
|
||||||
struct Metadata: Codable {
|
class Metadata: Object, Codable {
|
||||||
var title: String
|
var title: String
|
||||||
var subtitle: String?
|
var subtitle: String? = ""
|
||||||
var authors: [Author]?
|
var authors: List<Author>
|
||||||
var narrators: [String]?
|
var narrators: List<String?>
|
||||||
var genres: [String]
|
var genres: List<String?>
|
||||||
var publishedYear: String?
|
var publishedYear: String? = ""
|
||||||
var publishedDate: String?
|
var publishedDate: String? = ""
|
||||||
var publisher: String?
|
var publisher: String? = ""
|
||||||
var description: String?
|
// I think calling the below variable description conflicts with some public variables declared in some of the Pods we use, so it's desc. ¯\_(ツ)_/¯
|
||||||
var isbn: String?
|
final var description: String
|
||||||
var asin: String?
|
var isbn: String? = ""
|
||||||
var language: String?
|
var asin: String? = ""
|
||||||
|
var language: String? = ""
|
||||||
var explicit: Bool
|
var explicit: Bool
|
||||||
var authorName: String?
|
var authorName: String? = ""
|
||||||
var authorNameLF: String?
|
var authorNameLF: String? = ""
|
||||||
var narratorName: String?
|
var narratorName: String? = ""
|
||||||
var seriesName: String?
|
var seriesName: String? = ""
|
||||||
var feedUrl: String?
|
var feedUrl: String? = ""
|
||||||
}
|
}
|
||||||
struct PodcastEpisode: Codable {
|
class PodcastEpisode: Object, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var index: Int
|
var index: Int
|
||||||
var episode: String?
|
var episode: String? = ""
|
||||||
var episodeType: String?
|
var episodeType: String? = ""
|
||||||
var title: String
|
var title: String
|
||||||
var subtitle: String?
|
var subtitle: String? = ""
|
||||||
var description: String?
|
var escription: String? = ""
|
||||||
var audioFile: AudioFile?
|
var audioFile: AudioFile? = nil
|
||||||
var audioTrack: AudioTrack?
|
var audioTrack: AudioTrack? = nil
|
||||||
var duration: Double
|
var duration: Double
|
||||||
var size: Int64
|
var size: Int64
|
||||||
// var serverEpisodeId: String?
|
// var serverEpisodeId: String?
|
||||||
}
|
}
|
||||||
struct AudioFile: Codable {
|
class AudioFile: Object, Codable {
|
||||||
var index: Int
|
@Persisted var index: Int
|
||||||
var ino: String
|
@Persisted var ino: String
|
||||||
var metadata: FileMetadata
|
@Persisted var metadata: FileMetadata?
|
||||||
}
|
}
|
||||||
struct Author: Codable {
|
class Author: Object, Codable {
|
||||||
var id: String
|
@Persisted var id: String
|
||||||
var name: String
|
@Persisted var name: String
|
||||||
var coverPath: String?
|
@Persisted var coverPath: String? = ""
|
||||||
}
|
}
|
||||||
struct Chapter: Codable {
|
class Chapter: Object, Codable {
|
||||||
var id: Int
|
@Persisted var id: Int
|
||||||
var start: Double
|
@Persisted var start: Double
|
||||||
var end: Double
|
@Persisted var end: Double
|
||||||
var title: String?
|
@Persisted var title: String? = nil
|
||||||
}
|
}
|
||||||
struct AudioTrack: Codable {
|
struct AudioTrack: Codable {
|
||||||
var index: Int?
|
var index: Int? = nil
|
||||||
var startOffset: Double?
|
var startOffset: Double? = nil
|
||||||
var duration: Double
|
var duration: Double
|
||||||
var title: String?
|
var title: String? = ""
|
||||||
var contentUrl: String?
|
var contentUrl: String? = ""
|
||||||
var mimeType: String
|
var mimeType: String
|
||||||
var metadata: FileMetadata?
|
var metadata: FileMetadata? = nil
|
||||||
// var isLocal: Bool
|
var isLocal: Bool
|
||||||
// var localFileId: String?
|
var localFileId: String? = ""
|
||||||
// var audioProbeResult: AudioProbeResult? Needed for local playback
|
// var audioProbeResult: AudioProbeResult? // Needed for local playback. Requires local FFMPEG? Not sure how doable this is on iOS
|
||||||
var serverIndex: Int?
|
var serverIndex: Int? = nil
|
||||||
}
|
}
|
||||||
struct FileMetadata: Codable {
|
class FileMetadata: Object, Codable {
|
||||||
var filename: String
|
@Persisted var filename: String
|
||||||
var ext: String
|
@Persisted var ext: String
|
||||||
var path: String
|
@Persisted var path: String
|
||||||
var relPath: String
|
@Persisted var relPath: String
|
||||||
}
|
}
|
||||||
struct Library: Codable {
|
struct Library: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
|
@ -125,7 +127,7 @@ struct Folder: Codable {
|
||||||
}
|
}
|
||||||
struct LibraryFile: Codable {
|
struct LibraryFile: Codable {
|
||||||
var ino: String
|
var ino: String
|
||||||
var metadata: FileMetadata
|
var metadata: FileMetadata?
|
||||||
}
|
}
|
||||||
struct MediaProgress: Codable {
|
struct MediaProgress: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
|
@ -139,3 +141,14 @@ struct MediaProgress:Codable {
|
||||||
var startedAt: Int64
|
var startedAt: Int64
|
||||||
var finishedAt: Int64?
|
var finishedAt: Int64?
|
||||||
}
|
}
|
||||||
|
struct PlaybackMetadata: Codable {
|
||||||
|
var duration: Double
|
||||||
|
var currentTime: Double
|
||||||
|
var playerState: PlayerState
|
||||||
|
}
|
||||||
|
enum PlayerState: Codable {
|
||||||
|
case IDLE
|
||||||
|
case BUFFERING
|
||||||
|
case READY
|
||||||
|
case ENDED
|
||||||
|
}
|
||||||
|
|
153
ios/App/Shared/models/LocalLibrary.swift
Normal file
153
ios/App/Shared/models/LocalLibrary.swift
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
//
|
||||||
|
// LocalLibrary.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Created by benonymity on 6/15/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RealmSwift
|
||||||
|
|
||||||
|
|
||||||
|
class LocalLibraryItem: Object, Codable {
|
||||||
|
@Persisted(primaryKey: true) var id: String
|
||||||
|
@Persisted var basePath: String
|
||||||
|
@Persisted var absolutePath: String
|
||||||
|
@Persisted var contentUrl: String
|
||||||
|
@Persisted var isInvalid: Bool
|
||||||
|
@Persisted var mediaType: String
|
||||||
|
@Persisted var media: MediaType?
|
||||||
|
@Persisted var localFiles: List<LocalFile>
|
||||||
|
@Persisted var coverContentUrl: String? = nil
|
||||||
|
@Persisted var coverAbsolutePath: String? = nil
|
||||||
|
@Persisted var isLocal: Bool
|
||||||
|
@Persisted var serverConnectionConfigId: String? = nil
|
||||||
|
@Persisted var serverAddress: String? = nil
|
||||||
|
@Persisted var serverUserId: String? = nil
|
||||||
|
@Persisted var libraryItemId: String? = nil
|
||||||
|
}
|
||||||
|
class LocalMediaItem: Object, Codable {
|
||||||
|
@Persisted var id: String
|
||||||
|
@Persisted var name: String
|
||||||
|
@Persisted var mediaType: String
|
||||||
|
@Persisted var folderId: String
|
||||||
|
@Persisted var contentUrl: String
|
||||||
|
@Persisted var simplePath: String
|
||||||
|
@Persisted var basePath: String
|
||||||
|
@Persisted var absolutePath: String
|
||||||
|
@Persisted var audioTracks: List<AudioTrack>
|
||||||
|
@Persisted var localFiles: List<LocalFile>
|
||||||
|
@Persisted var coverContentUrl: String? = ""
|
||||||
|
@Persisted var coverAbsolutePath: String? = ""
|
||||||
|
}
|
||||||
|
class MediaType: Object, Codable {
|
||||||
|
@Persisted var libraryItemId: String? = ""
|
||||||
|
@Persisted var metadata: Metadata?
|
||||||
|
@Persisted var coverPath: String? = ""
|
||||||
|
@Persisted var tags: List<String?>
|
||||||
|
@Persisted var audioFiles: List<AudioFile>
|
||||||
|
@Persisted var chapters: List<Chapter>
|
||||||
|
@Persisted var tracks: List<AudioTrack>
|
||||||
|
@Persisted var size: Int64? = nil
|
||||||
|
@Persisted var duration: Double? = nil
|
||||||
|
@Persisted var episodes: List<PodcastEpisode>
|
||||||
|
@Persisted var autoDownloadEpisodes: Bool? = nil
|
||||||
|
}
|
||||||
|
class Metadata: Object, Codable {
|
||||||
|
@Persisted var title: String
|
||||||
|
@Persisted var subtitle: String? = ""
|
||||||
|
@Persisted var authors: List<Author>
|
||||||
|
@Persisted var narrators: List<String?>
|
||||||
|
@Persisted var genres: List<String?>
|
||||||
|
@Persisted var publishedYear: String? = ""
|
||||||
|
@Persisted var publishedDate: String? = ""
|
||||||
|
@Persisted var publisher: String? = ""
|
||||||
|
// I think calling the below variable description conflicts with some public variables declared in some of the Pods we use, so it's desc. ¯\_(ツ)_/¯
|
||||||
|
@Persisted final var description: String
|
||||||
|
@Persisted var isbn: String? = ""
|
||||||
|
@Persisted var asin: String? = ""
|
||||||
|
@Persisted var language: String? = ""
|
||||||
|
@Persisted var explicit: Bool
|
||||||
|
@Persisted var authorName: String? = ""
|
||||||
|
@Persisted var authorNameLF: String? = ""
|
||||||
|
@Persisted var narratorName: String? = ""
|
||||||
|
@Persisted var seriesName: String? = ""
|
||||||
|
@Persisted var feedUrl: String? = ""
|
||||||
|
}
|
||||||
|
class PodcastEpisode: Object, Codable {
|
||||||
|
@Persisted var id: String
|
||||||
|
@Persisted var index: Int
|
||||||
|
@Persisted var episode: String? = ""
|
||||||
|
@Persisted var episodeType: String? = ""
|
||||||
|
@Persisted var title: String
|
||||||
|
@Persisted var subtitle: String? = ""
|
||||||
|
@Persisted var escription: String? = ""
|
||||||
|
@Persisted var audioFile: AudioFile? = nil
|
||||||
|
@Persisted var audioTrack: AudioTrack? = nil
|
||||||
|
@Persisted var duration: Double
|
||||||
|
@Persisted var size: Int64
|
||||||
|
// @Persisted var serverEpisodeId: String?
|
||||||
|
}
|
||||||
|
class AudioFile: Object, Codable {
|
||||||
|
@Persisted var index: Int
|
||||||
|
@Persisted var ino: String
|
||||||
|
@Persisted var metadata: FileMetadata?
|
||||||
|
}
|
||||||
|
class Author: Object, Codable {
|
||||||
|
@Persisted var id: String
|
||||||
|
@Persisted var name: String
|
||||||
|
@Persisted var coverPath: String? = ""
|
||||||
|
}
|
||||||
|
class Chapter: Object, Codable {
|
||||||
|
@Persisted var id: Int
|
||||||
|
@Persisted var start: Double
|
||||||
|
@Persisted var end: Double
|
||||||
|
@Persisted var title: String? = nil
|
||||||
|
}
|
||||||
|
class AudioTrack: Object, Codable {
|
||||||
|
@Persisted var index: Int? = nil
|
||||||
|
@Persisted var startOffset: Double? = nil
|
||||||
|
@Persisted var duration: Double
|
||||||
|
@Persisted var title: String? = ""
|
||||||
|
@Persisted var contentUrl: String? = ""
|
||||||
|
@Persisted var mimeType: String
|
||||||
|
@Persisted var metadata: FileMetadata? = nil
|
||||||
|
@Persisted var isLocal: Bool
|
||||||
|
@Persisted var localFileId: String? = ""
|
||||||
|
// var audioProbeResult: AudioProbeResult? // Needed for local playback. Requires local FFMPEG? Not sure how doable this is on iOS
|
||||||
|
@Persisted var serverIndex: Int? = nil
|
||||||
|
}
|
||||||
|
class FileMetadata: Object, Codable {
|
||||||
|
@Persisted var filename: String
|
||||||
|
@Persisted var ext: String
|
||||||
|
@Persisted var path: String
|
||||||
|
@Persisted var relPath: String
|
||||||
|
}
|
||||||
|
class LocalFile: Object, Codable {
|
||||||
|
@Persisted var id: String
|
||||||
|
@Persisted var filename: String? = ""
|
||||||
|
@Persisted var contentUrl: String
|
||||||
|
@Persisted var basePath: String
|
||||||
|
@Persisted var absolutePath: String
|
||||||
|
@Persisted var simplePath: String
|
||||||
|
@Persisted var mimeType: String? = ""
|
||||||
|
@Persisted var size: Int64
|
||||||
|
}
|
||||||
|
class LocalMediaProgress: Object, Codable {
|
||||||
|
@Persisted var id: String
|
||||||
|
@Persisted var localLibraryItemId: String
|
||||||
|
@Persisted var localEpisodeId: String? = ""
|
||||||
|
@Persisted var duration: Double
|
||||||
|
@Persisted var progress: Double // 0 to 1
|
||||||
|
@Persisted var currentTime: Double
|
||||||
|
@Persisted var isFinished: Bool
|
||||||
|
@Persisted var lastUpdate: Int64
|
||||||
|
@Persisted var startedAt: Int64
|
||||||
|
@Persisted var finishedAt: Int64? = nil
|
||||||
|
// For local lib items from server to support server sync
|
||||||
|
@Persisted var serverConnectionConfigId: String? = ""
|
||||||
|
@Persisted var serverAddress: String? = ""
|
||||||
|
@Persisted var serverUserId: String? = ""
|
||||||
|
@Persisted var libraryItemId: String? = ""
|
||||||
|
@Persisted var episodeId: String? = ""
|
||||||
|
}
|
|
@ -64,6 +64,7 @@ class Database {
|
||||||
setLastActiveConfigIndex(index: config.index)
|
setLastActiveConfigIndex(index: config.index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteServerConnectionConfig(id: String) {
|
public func deleteServerConnectionConfig(id: String) {
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let config = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
|
let config = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
|
||||||
|
@ -80,6 +81,7 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
||||||
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
||||||
|
|
||||||
|
@ -108,6 +110,7 @@ class Database {
|
||||||
setLastActiveConfigIndex(index: nil)
|
setLastActiveConfigIndex(index: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setLastActiveConfigIndex(index: Int?) {
|
public func setLastActiveConfigIndex(index: Int?) {
|
||||||
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
||||||
let obj = ServerConnectionConfigActiveIndex()
|
let obj = ServerConnectionConfigActiveIndex()
|
||||||
|
@ -123,6 +126,7 @@ class Database {
|
||||||
debugPrint(exception)
|
debugPrint(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLastActiveConfigIndex() -> Int? {
|
public func getLastActiveConfigIndex() -> Int? {
|
||||||
return Database.realmQueue.sync {
|
return Database.realmQueue.sync {
|
||||||
return instance.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
|
return instance.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
|
||||||
|
@ -139,6 +143,49 @@ class Database {
|
||||||
}
|
}
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
NSLog("failed to save device settings")
|
NSLog("failed to save device settings")
|
||||||
|
|
||||||
|
public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
|
||||||
|
var localLibraryItems: [ThreadSafeReference<LocalLibraryItem>] = []
|
||||||
|
|
||||||
|
Database.realmQueue.sync {
|
||||||
|
let items = instance.objects(LocalLibraryItem.self)
|
||||||
|
localLibraryItems = items.map { item in
|
||||||
|
return ThreadSafeReference(to: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
|
|
||||||
|
return localLibraryItems.map { item in
|
||||||
|
return realm.resolve(item)!
|
||||||
|
}
|
||||||
|
} catch(let exception) {
|
||||||
|
NSLog("error while readling local library items")
|
||||||
|
debugPrint(exception)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getLocalLibraryItemByLLId(libraryItem: String) -> LocalLibraryItem? {
|
||||||
|
let items = getLocalLibraryItems()
|
||||||
|
for item in items {
|
||||||
|
if (item.id == libraryItem) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSLog("Local library item with id \(libraryItem) not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
|
||||||
|
Database.realmQueue.sync {
|
||||||
|
do {
|
||||||
|
try instance.write {
|
||||||
|
instance.add(localLibraryItem);
|
||||||
|
}
|
||||||
|
} catch(let exception) {
|
||||||
|
NSLog("Unable to save local library item")
|
||||||
debugPrint(exception)
|
debugPrint(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,4 +195,22 @@ class Database {
|
||||||
return instance.objects(DeviceSettings.self).first ?? getDefaultDeviceSettings()
|
return instance.objects(DeviceSettings.self).first ?? getDefaultDeviceSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeLocalLibraryItem(localLibraryItemId: String) {
|
||||||
|
let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId)
|
||||||
|
Database.realmQueue.sync {
|
||||||
|
do {
|
||||||
|
try instance.write {
|
||||||
|
if item != nil {
|
||||||
|
instance.delete(item!)
|
||||||
|
} else {
|
||||||
|
NSLog("Unable to find local library item to delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(let exception) {
|
||||||
|
NSLog("Unable to delete local library item")
|
||||||
|
debugPrint(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,7 @@ export default {
|
||||||
return this.ebookFile && this.ebookFormat !== 'pdf'
|
return this.ebookFile && this.ebookFormat !== 'pdf'
|
||||||
},
|
},
|
||||||
showDownload() {
|
showDownload() {
|
||||||
if (this.isIos) return false
|
// if (this.isIos) return false
|
||||||
return this.user && this.userCanDownload && this.showPlay && !this.hasLocal
|
return this.user && this.userCanDownload && this.showPlay && !this.hasLocal
|
||||||
},
|
},
|
||||||
ebookFile() {
|
ebookFile() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue