mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-13 04:50:41 +00:00
Please work
This commit is contained in:
parent
16467ea2b0
commit
4f28f785bf
8 changed files with 119 additions and 129 deletions
|
|
@ -48,9 +48,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
var isPlaying = true
|
||||
var currentTimeVal: Double = 0.0
|
||||
var duration: Double = 0.0
|
||||
|
||||
let localSkipSegments: [(String, Double, Double)]?
|
||||
var isVideoLoaded = false
|
||||
var isVideoLoaded = false
|
||||
|
||||
private var isHoldPauseEnabled: Bool {
|
||||
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
|
||||
|
|
@ -154,6 +152,7 @@ var isVideoLoaded = false
|
|||
|
||||
private var malID: Int?
|
||||
private var skipIntervals: (op: CMTimeRange?, ed: CMTimeRange?) = (nil, nil)
|
||||
private var preloadedSkipInfo: SkipInfo? = nil
|
||||
|
||||
private var skipIntroButton: UIButton!
|
||||
private var skipOutroButton: UIButton!
|
||||
|
|
@ -268,8 +267,9 @@ var isVideoLoaded = false
|
|||
onWatchNext: @escaping () -> Void,
|
||||
subtitlesURL: String?,
|
||||
aniListID: Int,
|
||||
skipInfo: SkipInfo? = nil,
|
||||
totalEpisodes: Int,
|
||||
episodeImageUrl: String, localSkipSegments: [(String, Double, Double)]? = nil, headers:[String:String]?) {
|
||||
episodeImageUrl: String,headers:[String:String]?) {
|
||||
|
||||
self.module = module
|
||||
self.streamURL = urlString
|
||||
|
|
@ -278,11 +278,11 @@ var isVideoLoaded = false
|
|||
self.episodeNumber = episodeNumber
|
||||
self.episodeImageUrl = episodeImageUrl
|
||||
self.episodeTitle = episodeTitle
|
||||
self.localSkipSegments = localSkipSegments
|
||||
self.seasonNumber = seasonNumber
|
||||
self.onWatchNext = onWatchNext
|
||||
self.subtitlesURL = subtitlesURL
|
||||
self.aniListID = aniListID
|
||||
self.preloadedSkipInfo = skipInfo
|
||||
self.headers = headers
|
||||
self.totalEpisodes = totalEpisodes
|
||||
|
||||
|
|
@ -396,19 +396,12 @@ var isVideoLoaded = false
|
|||
view.bringSubviewToFront(subtitleStackView)
|
||||
subtitleStackView.isHidden = !SubtitleSettingsManager.shared.settings.enabled
|
||||
|
||||
|
||||
if let segs = localSkipSegments {
|
||||
for s in segs {
|
||||
if s.0 == "op" {
|
||||
self.skipIntervals.op = CMTimeRange(start: CMTime(seconds: s.1, preferredTimescale: 600), end: CMTime(seconds: s.2, preferredTimescale: 600))
|
||||
} else if s.0 == "ed" {
|
||||
self.skipIntervals.ed = CMTimeRange(start: CMTime(seconds: s.1, preferredTimescale: 600), end: CMTime(seconds: s.2, preferredTimescale: 600))
|
||||
}
|
||||
}
|
||||
if let info = preloadedSkipInfo {
|
||||
if let s = info.opStart, let e = info.opEnd { self.skipIntervals.op = CMTimeRange(start: CMTime(seconds: s, preferredTimescale: 600), end: CMTime(seconds: e, preferredTimescale: 600)) }
|
||||
if let s = info.edStart, let e = info.edEnd { self.skipIntervals.ed = CMTimeRange(start: CMTime(seconds: s, preferredTimescale: 600), end: CMTime(seconds: e, preferredTimescale: 600)) }
|
||||
self.updateSegments()
|
||||
}
|
||||
if localSkipSegments == nil {
|
||||
AniListMutation().fetchMalID(animeId: aniListID) { [weak self] result in
|
||||
AniListMutation().fetchMalID(animeId: aniListID) { [weak self] result in
|
||||
switch result {
|
||||
case .success(let mal):
|
||||
self?.malID = mal
|
||||
|
|
@ -418,7 +411,6 @@ if localSkipSegments == nil {
|
|||
Logger.shared.log("Unable to fetch MAL ID: \(error)",type:"Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for control in controlsToHide {
|
||||
originalHiddenStates[control] = control.isHidden
|
||||
|
|
@ -3879,4 +3871,4 @@ class GradientBlurButton: UIButton {
|
|||
cleanupVisualEffects()
|
||||
super.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ enum DownloadType: String, Codable {
|
|||
case .episode:
|
||||
return "Episode"
|
||||
}
|
||||
|
||||
// MARK: - Skip Information
|
||||
struct SkipInfo: Codable, Equatable {
|
||||
let opStart: Double?
|
||||
let opEnd: Double?
|
||||
let edStart: Double?
|
||||
let edEnd: Double?
|
||||
let introURL: String?
|
||||
let outroURL: String?
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +82,7 @@ struct DownloadedAsset: Identifiable, Codable, Equatable {
|
|||
// New fields for subtitle support
|
||||
let subtitleURL: URL?
|
||||
let localSubtitleURL: URL?
|
||||
let skipInfo: SkipInfo?
|
||||
|
||||
// For caching purposes, but not stored as part of the codable object
|
||||
private var _cachedFileSize: Int64? = nil
|
||||
|
|
@ -413,6 +426,7 @@ struct AssetMetadata: Codable {
|
|||
let showPosterURL: URL? // Main show poster URL (distinct from episode-specific images)
|
||||
let episodeTitle: String?
|
||||
let seasonNumber: Int?
|
||||
let anilistId: Int?
|
||||
|
||||
init(
|
||||
title: String,
|
||||
|
|
|
|||
|
|
@ -14,29 +14,28 @@ struct DownloadRequest {
|
|||
let headers: [String: String]
|
||||
let title: String?
|
||||
let imageURL: URL?
|
||||
let aniListID: Int?
|
||||
let isEpisode: Bool
|
||||
let showTitle: String?
|
||||
let season: Int?
|
||||
let episode: Int?
|
||||
let subtitleURL: URL?
|
||||
let showPosterURL: URL?
|
||||
let anilistId: Int?
|
||||
|
||||
init(url: URL, headers: [String: String], title: String? = nil, imageURL: URL? = nil,
|
||||
aniListID: Int? = nil,
|
||||
isEpisode: Bool = false, showTitle: String? = nil, season: Int? = nil,
|
||||
episode: Int? = nil, subtitleURL: URL? = nil, showPosterURL: URL? = nil) {
|
||||
episode: Int? = nil, subtitleURL: URL? = nil, showPosterURL: URL? = nil, anilistId: Int? = nil) {
|
||||
self.url = url
|
||||
self.headers = headers
|
||||
self.title = title
|
||||
self.imageURL = imageURL
|
||||
self.aniListID = aniListID
|
||||
self.isEpisode = isEpisode
|
||||
self.showTitle = showTitle
|
||||
self.season = season
|
||||
self.episode = episode
|
||||
self.subtitleURL = subtitleURL
|
||||
self.showPosterURL = showPosterURL
|
||||
self.anilistId = anilistId
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,12 +53,14 @@ struct QualityOption {
|
|||
|
||||
extension JSController {
|
||||
|
||||
func downloadWithM3U8Support(url: URL, headers: [String: String], title: String? = nil, imageURL: URL? = nil, aniListID: Int? = nil, isEpisode: Bool = false, showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||
func downloadWithM3U8Support(url: URL, headers: [String: String], title: String? = nil,
|
||||
imageURL: URL? = nil, isEpisode: Bool = false,
|
||||
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||
|
||||
let request = DownloadRequest(
|
||||
url: url, headers: headers, title: title, imageURL: imageURL, aniListID: aniListID,
|
||||
url: url, headers: headers, title: title, imageURL: imageURL,
|
||||
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
||||
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL
|
||||
)
|
||||
|
|
@ -95,7 +96,7 @@ extension JSController {
|
|||
if let qualityURL = URL(string: selectedQuality.url) {
|
||||
let qualityRequest = DownloadRequest(
|
||||
url: qualityURL, headers: request.headers, title: request.title,
|
||||
imageURL: request.imageURL, aniListID: request.aniListID, isEpisode: request.isEpisode,
|
||||
imageURL: request.imageURL, isEpisode: request.isEpisode,
|
||||
showTitle: request.showTitle, season: request.season,
|
||||
episode: request.episode, subtitleURL: request.subtitleURL,
|
||||
showPosterURL: request.showPosterURL
|
||||
|
|
@ -123,12 +124,13 @@ extension JSController {
|
|||
|
||||
|
||||
func downloadMP4(url: URL, headers: [String: String], title: String? = nil,
|
||||
aniListID: Int? = nil, imageURL: URL? = nil, aniListID: Int? = nil, isEpisode: Bool = false, showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||
imageURL: URL? = nil, isEpisode: Bool = false,
|
||||
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||
|
||||
let request = DownloadRequest(
|
||||
url: url, headers: headers, title: title, imageURL: imageURL, aniListID: aniListID,
|
||||
url: url, headers: headers, title: title, imageURL: imageURL,
|
||||
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
||||
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL
|
||||
)
|
||||
|
|
@ -399,7 +401,6 @@ extension JSController {
|
|||
private func downloadWithOriginalMethod(request: DownloadRequest, completionHandler: ((Bool, String) -> Void)?) {
|
||||
self.startDownload(
|
||||
url: request.url,
|
||||
aniListID: request.aniListID,
|
||||
headers: request.headers,
|
||||
title: request.title,
|
||||
imageURL: request.imageURL,
|
||||
|
|
@ -409,6 +410,7 @@ extension JSController {
|
|||
episode: request.episode,
|
||||
subtitleURL: request.subtitleURL,
|
||||
showPosterURL: request.showPosterURL,
|
||||
anilistId: request.anilistId,
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
}
|
||||
|
|
@ -532,4 +534,4 @@ extension JSController {
|
|||
private func logQualitySelectionResult(quality: QualityOption, preference: String) {
|
||||
Logger.shared.log("Quality selected: \(quality.name) (preference: \(preference))", type: "Download")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ extension JSController {
|
|||
/// - completionHandler: Optional callback for download status
|
||||
func startDownload(
|
||||
url: URL,
|
||||
aniListID: Int? = nil,
|
||||
headers: [String: String] = [:],
|
||||
title: String? = nil,
|
||||
imageURL: URL? = nil,
|
||||
|
|
@ -127,7 +126,6 @@ extension JSController {
|
|||
imageURL: imageURL,
|
||||
module: module,
|
||||
isEpisode: isEpisode,
|
||||
aniListID: aniListID,
|
||||
showTitle: showTitle,
|
||||
season: season,
|
||||
episode: episode,
|
||||
|
|
@ -179,8 +177,7 @@ extension JSController {
|
|||
subtitleURL: subtitleURL,
|
||||
asset: asset,
|
||||
headers: headers,
|
||||
module: module, // Pass the module to store it for queue processing
|
||||
aniListID: aniListID
|
||||
module: module // Pass the module to store it for queue processing
|
||||
)
|
||||
|
||||
// Add to the download queue
|
||||
|
|
@ -279,7 +276,6 @@ extension JSController {
|
|||
imageURL: queuedDownload.imageURL,
|
||||
module: module,
|
||||
isEpisode: queuedDownload.type == .episode,
|
||||
aniListID: queuedDownload.aniListID,
|
||||
showTitle: queuedDownload.metadata?.showTitle,
|
||||
season: queuedDownload.metadata?.season,
|
||||
episode: queuedDownload.metadata?.episode,
|
||||
|
|
@ -933,13 +929,6 @@ extension JSController {
|
|||
}
|
||||
}
|
||||
DownloadPersistence.delete(id: asset.id)
|
||||
|
||||
// Remove AniSkip sidecar if present
|
||||
if let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
|
||||
let dir = appSupport.appendingPathComponent("SoraDownloads", isDirectory: true)
|
||||
let sidecar = dir.appendingPathComponent("aniskip-\(asset.id.uuidString).json")
|
||||
try? FileManager.default.removeItem(at: sidecar)
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.savedAssets = DownloadPersistence.load()
|
||||
self?.objectWillChange.send()
|
||||
|
|
@ -1219,13 +1208,32 @@ extension JSController: AVAssetDownloadDelegate {
|
|||
|
||||
// Add to saved assets and save
|
||||
DownloadPersistence.upsert(newAsset)
|
||||
|
||||
// Fetch and save AniSkip OP/ED markers as a sidecar (non-blocking, optional)
|
||||
if let isEp = download.metadata?.episode, isEp > 0 {
|
||||
let epNumber = isEp
|
||||
fetchAndSaveAniSkipSidecar(aniListID: download.aniListID, episode: epNumber, assetID: newAsset.id.uuidString)
|
||||
// Fetch skip info in background if we know AniList ID & episode number
|
||||
if let meta = newAsset.metadata, let aId = meta.anilistId, let ep = meta.episodeNumber {
|
||||
self.fetchSkipInfo(anilistId: aId, episodeNumber: ep) { info in
|
||||
guard let info = info else { return }
|
||||
let updated = DownloadedAsset(
|
||||
id: newAsset.id,
|
||||
name: newAsset.name,
|
||||
downloadDate: newAsset.downloadDate,
|
||||
originalURL: newAsset.originalURL,
|
||||
localURL: newAsset.localURL,
|
||||
imageURL: newAsset.imageURL,
|
||||
fileSize: newAsset.fileSize,
|
||||
type: newAsset.type,
|
||||
progress: newAsset.progress,
|
||||
headers: newAsset.headers,
|
||||
referer: newAsset.referer,
|
||||
userAgent: newAsset.userAgent,
|
||||
metadata: newAsset.metadata,
|
||||
subtitleURL: newAsset.subtitleURL,
|
||||
localSubtitleURL: newAsset.localSubtitleURL,
|
||||
skipInfo: info
|
||||
)
|
||||
DownloadPersistence.upsert(updated)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.savedAssets = DownloadPersistence.load()
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
|
|
@ -1560,7 +1568,6 @@ struct JSActiveDownload: Identifiable, Equatable {
|
|||
var asset: AVURLAsset?
|
||||
var headers: [String: String]
|
||||
var module: ScrapingModule? // Add module property to store ScrapingModule
|
||||
var aniListID: Int? = nil
|
||||
|
||||
// Computed property to get the current task state
|
||||
var taskState: URLSessionTask.State {
|
||||
|
|
@ -1604,8 +1611,7 @@ struct JSActiveDownload: Identifiable, Equatable {
|
|||
subtitleURL: URL? = nil,
|
||||
asset: AVURLAsset? = nil,
|
||||
headers: [String: String] = [:],
|
||||
module: ScrapingModule? = nil,
|
||||
aniListID: Int? = nil // Add module parameter to initializer
|
||||
module: ScrapingModule? = nil // Add module parameter to initializer
|
||||
) {
|
||||
self.id = id
|
||||
self.originalURL = originalURL
|
||||
|
|
@ -1665,57 +1671,35 @@ enum DownloadQueueStatus: Equatable {
|
|||
case downloading
|
||||
/// Download has been completed
|
||||
case completed
|
||||
|
||||
// MARK: - Offline Skip Info (lightweight)
|
||||
private struct _AniSkipEntry: Codable { let interval: _Interval }
|
||||
private struct _Interval: Codable { let startTime: Double; let endTime: Double }
|
||||
private struct _AniSkipAPIResponse: Codable { let found: Bool; let results: [String:[_AniSkipEntry]]? }
|
||||
|
||||
private func fetchSkipInfo(anilistId: Int, episodeNumber: Int, completion: @escaping (SkipInfo?) -> Void) {
|
||||
// Convert AniList -> MAL
|
||||
AniListMutation().fetchMalID(animeId: anilistId) { res in
|
||||
guard case .success(let malId) = res, let malId = malId else { completion(nil); return }
|
||||
// Build requests for OP and ED
|
||||
let types = ["op","ed"]
|
||||
var info = SkipInfo(opStart: nil, opEnd: nil, edStart: nil, edEnd: nil, introURL: nil, outroURL: nil)
|
||||
let group = DispatchGroup()
|
||||
for t in types {
|
||||
group.enter()
|
||||
let urlStr = "https://api.aniskip.com/v2/skip-times/" + String(malId) + "?episode=" + String(episodeNumber) + "&types=" + t + "&anilistID=" + String(anilistId)
|
||||
guard let url = URL(string: urlStr) else { group.leave(); continue }
|
||||
URLSession.shared.dataTask(with: url) { data, _, _ in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let resp = try? JSONDecoder().decode(_AniSkipAPIResponse.self, from: data), resp.found else { return }
|
||||
if let entries = resp.results?[t], let first = entries.first {
|
||||
if t == "op" { info = SkipInfo(opStart: first.interval.startTime, opEnd: first.interval.endTime, edStart: info.edStart, edEnd: info.edEnd, introURL: nil, outroURL: nil) }
|
||||
if t == "ed" { info = SkipInfo(opStart: info.opStart, opEnd: info.opEnd, edStart: first.interval.startTime, edEnd: first.interval.endTime, introURL: nil, outroURL: nil) }
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
group.notify(queue: .main) { completion((info.opStart != nil || info.edStart != nil) ? info : nil) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AniSkip Sidecar
|
||||
private func fetchAndSaveAniSkipSidecar(aniListID: Int?, episode: Int, assetID: String) {
|
||||
guard let ani = aniListID else { return }
|
||||
AniListMutation().fetchMalID(animeId: ani) { result in
|
||||
switch result {
|
||||
case .success(let mal):
|
||||
let types = ["op", "ed"]
|
||||
var segments: [(String, Double, Double)] = []
|
||||
let group = DispatchGroup()
|
||||
for t in types {
|
||||
group.enter()
|
||||
if let url = URL(string: "https://api.aniskip.com/v2/skip-times/\(mal)/\(episode)?types=\(t)&episodeLength=0") {
|
||||
URLSession.shared.dataTask(with: url) { data, _, _ in
|
||||
defer { group.leave() }
|
||||
guard let d = data,
|
||||
let resp = try? JSONDecoder().decode(AniSkipResponse.self, from: d),
|
||||
resp.found,
|
||||
let interval = resp.results.first?.interval else { return }
|
||||
segments.append((t, interval.startTime, interval.endTime))
|
||||
}.resume()
|
||||
} else {
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
group.notify(queue: .global()) {
|
||||
guard !segments.isEmpty else { return }
|
||||
let payload: [String: Any] = [
|
||||
"provider": "aniskip",
|
||||
"malId": mal,
|
||||
"episode": episode,
|
||||
"segments": segments.map { ["type": $0.0, "start": $0.1, "end": $0.2] },
|
||||
"fetchedAt": ISO8601DateFormatter().string(from: Date()),
|
||||
"v": 1
|
||||
]
|
||||
do {
|
||||
if let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
|
||||
let dir = appSupport.appendingPathComponent("SoraDownloads", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
let file = dir.appendingPathComponent("aniskip-\(assetID).json")
|
||||
let data = try JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted])
|
||||
try data.write(to: file, options: .atomic)
|
||||
Logger.shared.log("Saved AniSkip sidecar: \(file.lastPathComponent)", type: "Download")
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Failed to save AniSkip sidecar: \(error)", type: "Error")
|
||||
}
|
||||
}
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,21 @@ extension JSController {
|
|||
/// - episode: Episode number (optional)
|
||||
/// - subtitleURL: Optional subtitle URL to download after video (optional)
|
||||
/// - completionHandler: Called when the download is initiated or fails
|
||||
func downloadWithStreamTypeSupport(url: URL, headers: [String: String], title: String? = nil, imageURL: URL? = nil, module: ScrapingModule, isEpisode: Bool = false, aniListID: Int? = nil, showTitle: String? = nil, season: Int? = nil, episode: Int? = nil, subtitleURL: URL? = nil, showPosterURL: URL? = nil, completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||
func downloadWithStreamTypeSupport(
|
||||
url: URL,
|
||||
headers: [String: String],
|
||||
title: String? = nil,
|
||||
imageURL: URL? = nil,
|
||||
module: ScrapingModule,
|
||||
isEpisode: Bool = false,
|
||||
showTitle: String? = nil,
|
||||
season: Int? = nil,
|
||||
episode: Int? = nil,
|
||||
anilistId: Int? = nil,
|
||||
subtitleURL: URL? = nil,
|
||||
showPosterURL: URL? = nil,
|
||||
completionHandler: ((Bool, String) -> Void)? = nil
|
||||
) {
|
||||
let streamType = module.metadata.streamType.lowercased()
|
||||
|
||||
if streamType == "hls" || streamType == "m3u8" || url.absoluteString.contains(".m3u8") {
|
||||
|
|
@ -34,7 +48,6 @@ extension JSController {
|
|||
headers: headers,
|
||||
title: title,
|
||||
imageURL: imageURL,
|
||||
aniListID: aniListID,
|
||||
isEpisode: isEpisode,
|
||||
showTitle: showTitle,
|
||||
season: season,
|
||||
|
|
@ -50,7 +63,6 @@ extension JSController {
|
|||
headers: headers,
|
||||
title: title,
|
||||
imageURL: imageURL,
|
||||
aniListID: aniListID,
|
||||
isEpisode: isEpisode,
|
||||
showTitle: showTitle,
|
||||
season: season,
|
||||
|
|
@ -61,4 +73,4 @@ extension JSController {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import AVKit
|
||||
import Foundation
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
|
||||
|
|
@ -243,24 +242,7 @@ struct DownloadView: View {
|
|||
metadataUrl: ""
|
||||
)
|
||||
|
||||
|
||||
var localSkipSegments: [(String, Double, Double)]? = nil
|
||||
if let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
|
||||
let dir = appSupport.appendingPathComponent("SoraDownloads", isDirectory: true)
|
||||
let sidecar = dir.appendingPathComponent("aniskip-\(asset.id.uuidString).json")
|
||||
if let data = try? Data(contentsOf: sidecar),
|
||||
let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
let segs = obj["segments"] as? [[String: Any]] {
|
||||
localSkipSegments = segs.compactMap { dict in
|
||||
if let t = dict["type"] as? String,
|
||||
let st = dict["start"] as? Double,
|
||||
let en = dict["end"] as? Double { return (t, st, en) }
|
||||
return nil
|
||||
}
|
||||
if localSkipSegments?.isEmpty == true { localSkipSegments = nil }
|
||||
}
|
||||
}
|
||||
let customPlayer = CustomMediaPlayerViewController(
|
||||
let customPlayer = CustomMediaPlayerViewController(
|
||||
module: dummyModule,
|
||||
urlString: asset.localURL.absoluteString,
|
||||
fullUrl: asset.originalURL.absoluteString,
|
||||
|
|
@ -295,7 +277,6 @@ let customPlayer = CustomMediaPlayerViewController(
|
|||
aniListID: 0,
|
||||
totalEpisodes: asset.metadata?.episode ?? 0,
|
||||
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
||||
localSkipSegments: localSkipSegments,
|
||||
headers: nil
|
||||
)
|
||||
|
||||
|
|
@ -1567,4 +1548,4 @@ struct SearchableStyleModifier: ViewModifier {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -691,7 +691,13 @@ private extension EpisodeCell {
|
|||
let fullEpisodeTitle = episodeTitle.isEmpty ? baseTitle : "\(baseTitle): \(episodeTitle)"
|
||||
let animeTitle = parentTitle.isEmpty ? "Unknown Anime" : parentTitle
|
||||
|
||||
jsController.downloadWithStreamTypeSupport(url: url, headers: headers, title: fullEpisodeTitle, imageURL: episodeThumbnailURL, module: module, isEpisode: true, aniListID: itemID,
|
||||
jsController.downloadWithStreamTypeSupport(
|
||||
url: url,
|
||||
headers: headers,
|
||||
title: fullEpisodeTitle,
|
||||
imageURL: episodeThumbnailURL,
|
||||
module: module,
|
||||
isEpisode: true,
|
||||
showTitle: animeTitle,
|
||||
season: 1,
|
||||
episode: episodeID + 1,
|
||||
|
|
|
|||
|
|
@ -2233,7 +2233,6 @@ struct MediaInfoView: View {
|
|||
imageURL: episodeThumbnailURL,
|
||||
module: self.module,
|
||||
isEpisode: true,
|
||||
aniListID: self.itemID,
|
||||
showTitle: self.title,
|
||||
season: 1,
|
||||
episode: episode.number,
|
||||
|
|
|
|||
Loading…
Reference in a new issue