mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-06 20:08:48 +02:00
feat: iOS download groundwork
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
// DataClasses.swift
|
||||
// App
|
||||
//
|
||||
// Created by Benonymity on 4/20/22.
|
||||
// Created by benonymity on 4/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreMedia
|
||||
import RealmSwift
|
||||
|
||||
struct LibraryItem: Codable {
|
||||
var id: String
|
||||
@@ -30,87 +31,88 @@ struct LibraryItem: Codable {
|
||||
var libraryFiles: [LibraryFile]
|
||||
var userMediaProgress:MediaProgress?
|
||||
}
|
||||
struct MediaType: Codable {
|
||||
var libraryItemId: String?
|
||||
var metadata: Metadata
|
||||
var coverPath: String?
|
||||
var tags: [String]?
|
||||
var audioFiles: [AudioTrack]?
|
||||
var chapters: [Chapter]?
|
||||
var tracks: [AudioTrack]?
|
||||
var size: Int64?
|
||||
var duration: Double?
|
||||
var episodes: [PodcastEpisode]?
|
||||
var autoDownloadEpisodes: Bool?
|
||||
class MediaType: Object, Codable {
|
||||
var libraryItemId: String? = ""
|
||||
var metadata: Metadata?
|
||||
var coverPath: String? = ""
|
||||
var tags: List<String?>
|
||||
var audioFiles: List<AudioFile>
|
||||
var chapters: List<Chapter>
|
||||
var tracks: List<AudioTrack>
|
||||
var size: Int64? = nil
|
||||
var duration: Double? = nil
|
||||
var episodes: List<PodcastEpisode>
|
||||
var autoDownloadEpisodes: Bool? = nil
|
||||
}
|
||||
struct Metadata: Codable {
|
||||
class Metadata: Object, Codable {
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var authors: [Author]?
|
||||
var narrators: [String]?
|
||||
var genres: [String]
|
||||
var publishedYear: String?
|
||||
var publishedDate: String?
|
||||
var publisher: String?
|
||||
var description: String?
|
||||
var isbn: String?
|
||||
var asin: String?
|
||||
var language: String?
|
||||
var subtitle: String? = ""
|
||||
var authors: List<Author>
|
||||
var narrators: List<String?>
|
||||
var genres: List<String?>
|
||||
var publishedYear: String? = ""
|
||||
var publishedDate: String? = ""
|
||||
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. ¯\_(ツ)_/¯
|
||||
final var description: String
|
||||
var isbn: String? = ""
|
||||
var asin: String? = ""
|
||||
var language: String? = ""
|
||||
var explicit: Bool
|
||||
var authorName: String?
|
||||
var authorNameLF: String?
|
||||
var narratorName: String?
|
||||
var seriesName: String?
|
||||
var feedUrl: String?
|
||||
var authorName: String? = ""
|
||||
var authorNameLF: String? = ""
|
||||
var narratorName: String? = ""
|
||||
var seriesName: String? = ""
|
||||
var feedUrl: String? = ""
|
||||
}
|
||||
struct PodcastEpisode: Codable {
|
||||
class PodcastEpisode: Object, Codable {
|
||||
var id: String
|
||||
var index: Int
|
||||
var episode: String?
|
||||
var episodeType: String?
|
||||
var episode: String? = ""
|
||||
var episodeType: String? = ""
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var description: String?
|
||||
var audioFile: AudioFile?
|
||||
var audioTrack: AudioTrack?
|
||||
var subtitle: String? = ""
|
||||
var escription: String? = ""
|
||||
var audioFile: AudioFile? = nil
|
||||
var audioTrack: AudioTrack? = nil
|
||||
var duration: Double
|
||||
var size: Int64
|
||||
// var serverEpisodeId: String?
|
||||
}
|
||||
struct AudioFile: Codable {
|
||||
var index: Int
|
||||
var ino: String
|
||||
var metadata: FileMetadata
|
||||
class AudioFile: Object, Codable {
|
||||
@Persisted var index: Int
|
||||
@Persisted var ino: String
|
||||
@Persisted var metadata: FileMetadata?
|
||||
}
|
||||
struct Author: Codable {
|
||||
var id: String
|
||||
var name: String
|
||||
var coverPath: String?
|
||||
class Author: Object, Codable {
|
||||
@Persisted var id: String
|
||||
@Persisted var name: String
|
||||
@Persisted var coverPath: String? = ""
|
||||
}
|
||||
struct Chapter: Codable {
|
||||
var id: Int
|
||||
var start: Double
|
||||
var end: Double
|
||||
var title: String?
|
||||
class Chapter: Object, Codable {
|
||||
@Persisted var id: Int
|
||||
@Persisted var start: Double
|
||||
@Persisted var end: Double
|
||||
@Persisted var title: String? = nil
|
||||
}
|
||||
struct AudioTrack: Codable {
|
||||
var index: Int?
|
||||
var startOffset: Double?
|
||||
var index: Int? = nil
|
||||
var startOffset: Double? = nil
|
||||
var duration: Double
|
||||
var title: String?
|
||||
var contentUrl: String?
|
||||
var title: String? = ""
|
||||
var contentUrl: String? = ""
|
||||
var mimeType: String
|
||||
var metadata: FileMetadata?
|
||||
// var isLocal: Bool
|
||||
// var localFileId: String?
|
||||
// var audioProbeResult: AudioProbeResult? Needed for local playback
|
||||
var serverIndex: Int?
|
||||
var metadata: FileMetadata? = nil
|
||||
var isLocal: Bool
|
||||
var localFileId: String? = ""
|
||||
// var audioProbeResult: AudioProbeResult? // Needed for local playback. Requires local FFMPEG? Not sure how doable this is on iOS
|
||||
var serverIndex: Int? = nil
|
||||
}
|
||||
struct FileMetadata: Codable {
|
||||
var filename: String
|
||||
var ext: String
|
||||
var path: String
|
||||
var relPath: String
|
||||
class FileMetadata: Object, Codable {
|
||||
@Persisted var filename: String
|
||||
@Persisted var ext: String
|
||||
@Persisted var path: String
|
||||
@Persisted var relPath: String
|
||||
}
|
||||
struct Library: Codable {
|
||||
var id: String
|
||||
@@ -125,17 +127,28 @@ struct Folder: Codable {
|
||||
}
|
||||
struct LibraryFile: Codable {
|
||||
var ino: String
|
||||
var metadata: FileMetadata
|
||||
var metadata: FileMetadata?
|
||||
}
|
||||
struct MediaProgress:Codable {
|
||||
var id:String
|
||||
var libraryItemId:String
|
||||
var episodeId:String?
|
||||
var duration:Double
|
||||
var progress:Double
|
||||
var currentTime:Double
|
||||
var isFinished:Bool
|
||||
var lastUpdate:Int64
|
||||
var startedAt:Int64
|
||||
var finishedAt:Int64?
|
||||
struct MediaProgress: Codable {
|
||||
var id: String
|
||||
var libraryItemId: String
|
||||
var episodeId: String?
|
||||
var duration: Double
|
||||
var progress: Double
|
||||
var currentTime: Double
|
||||
var isFinished: Bool
|
||||
var lastUpdate: Int64
|
||||
var startedAt: 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteServerConnectionConfig(id: String) {
|
||||
Database.realmQueue.sync {
|
||||
let config = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
|
||||
@@ -80,6 +81,7 @@ class Database {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
||||
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
||||
|
||||
@@ -108,6 +110,7 @@ class Database {
|
||||
setLastActiveConfigIndex(index: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func setLastActiveConfigIndex(index: Int?) {
|
||||
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
||||
let obj = ServerConnectionConfigActiveIndex()
|
||||
@@ -123,6 +126,7 @@ class Database {
|
||||
debugPrint(exception)
|
||||
}
|
||||
}
|
||||
|
||||
public func getLastActiveConfigIndex() -> Int? {
|
||||
return Database.realmQueue.sync {
|
||||
return instance.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
|
||||
@@ -139,6 +143,49 @@ class Database {
|
||||
}
|
||||
} catch(let exception) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -148,4 +195,22 @@ class Database {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user