mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 00:22:12 +00:00
experimental OP & ED timestamp download with downloads
This commit is contained in:
parent
ff1eaf6227
commit
afab4eec9d
7 changed files with 131 additions and 39 deletions
|
|
@ -48,7 +48,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
var isPlaying = true
|
var isPlaying = true
|
||||||
var currentTimeVal: Double = 0.0
|
var currentTimeVal: Double = 0.0
|
||||||
var duration: Double = 0.0
|
var duration: Double = 0.0
|
||||||
var isVideoLoaded = false
|
|
||||||
|
let localSkipSegments: [(String, Double, Double)]?
|
||||||
|
var isVideoLoaded = false
|
||||||
|
|
||||||
private var isHoldPauseEnabled: Bool {
|
private var isHoldPauseEnabled: Bool {
|
||||||
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
|
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
|
||||||
|
|
@ -267,7 +269,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
subtitlesURL: String?,
|
subtitlesURL: String?,
|
||||||
aniListID: Int,
|
aniListID: Int,
|
||||||
totalEpisodes: Int,
|
totalEpisodes: Int,
|
||||||
episodeImageUrl: String,headers:[String:String]?) {
|
episodeImageUrl: String, localSkipSegments: [(String, Double, Double)]? = nil, headers:[String:String]?) {
|
||||||
|
|
||||||
self.module = module
|
self.module = module
|
||||||
self.streamURL = urlString
|
self.streamURL = urlString
|
||||||
|
|
@ -276,6 +278,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
self.episodeNumber = episodeNumber
|
self.episodeNumber = episodeNumber
|
||||||
self.episodeImageUrl = episodeImageUrl
|
self.episodeImageUrl = episodeImageUrl
|
||||||
self.episodeTitle = episodeTitle
|
self.episodeTitle = episodeTitle
|
||||||
|
self.localSkipSegments = localSkipSegments
|
||||||
self.seasonNumber = seasonNumber
|
self.seasonNumber = seasonNumber
|
||||||
self.onWatchNext = onWatchNext
|
self.onWatchNext = onWatchNext
|
||||||
self.subtitlesURL = subtitlesURL
|
self.subtitlesURL = subtitlesURL
|
||||||
|
|
@ -393,7 +396,19 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
view.bringSubviewToFront(subtitleStackView)
|
view.bringSubviewToFront(subtitleStackView)
|
||||||
subtitleStackView.isHidden = !SubtitleSettingsManager.shared.settings.enabled
|
subtitleStackView.isHidden = !SubtitleSettingsManager.shared.settings.enabled
|
||||||
|
|
||||||
AniListMutation().fetchMalID(animeId: aniListID) { [weak self] result in
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateSegments()
|
||||||
|
}
|
||||||
|
if localSkipSegments == nil {
|
||||||
|
AniListMutation().fetchMalID(animeId: aniListID) { [weak self] result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mal):
|
case .success(let mal):
|
||||||
self?.malID = mal
|
self?.malID = mal
|
||||||
|
|
@ -403,6 +418,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
Logger.shared.log("Unable to fetch MAL ID: \(error)",type:"Error")
|
Logger.shared.log("Unable to fetch MAL ID: \(error)",type:"Error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for control in controlsToHide {
|
for control in controlsToHide {
|
||||||
originalHiddenStates[control] = control.isHidden
|
originalHiddenStates[control] = control.isHidden
|
||||||
|
|
@ -3863,4 +3879,4 @@ class GradientBlurButton: UIButton {
|
||||||
cleanupVisualEffects()
|
cleanupVisualEffects()
|
||||||
super.removeFromSuperview()
|
super.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ struct DownloadRequest {
|
||||||
let headers: [String: String]
|
let headers: [String: String]
|
||||||
let title: String?
|
let title: String?
|
||||||
let imageURL: URL?
|
let imageURL: URL?
|
||||||
|
let aniListID: Int?
|
||||||
let isEpisode: Bool
|
let isEpisode: Bool
|
||||||
let showTitle: String?
|
let showTitle: String?
|
||||||
let season: Int?
|
let season: Int?
|
||||||
|
|
@ -52,13 +53,13 @@ struct QualityOption {
|
||||||
extension JSController {
|
extension JSController {
|
||||||
|
|
||||||
func downloadWithM3U8Support(url: URL, headers: [String: String], title: String? = nil,
|
func downloadWithM3U8Support(url: URL, headers: [String: String], title: String? = nil,
|
||||||
imageURL: URL? = nil, isEpisode: Bool = false,
|
imageURL: URL? = nil, aniListID: Int? = nil, isEpisode: Bool = false,
|
||||||
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||||
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||||
|
|
||||||
let request = DownloadRequest(
|
let request = DownloadRequest(
|
||||||
url: url, headers: headers, title: title, imageURL: imageURL,
|
url: url, headers: headers, title: title, imageURL: imageURL, aniListID: aniListID,
|
||||||
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
||||||
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL
|
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL
|
||||||
)
|
)
|
||||||
|
|
@ -94,7 +95,7 @@ extension JSController {
|
||||||
if let qualityURL = URL(string: selectedQuality.url) {
|
if let qualityURL = URL(string: selectedQuality.url) {
|
||||||
let qualityRequest = DownloadRequest(
|
let qualityRequest = DownloadRequest(
|
||||||
url: qualityURL, headers: request.headers, title: request.title,
|
url: qualityURL, headers: request.headers, title: request.title,
|
||||||
imageURL: request.imageURL, isEpisode: request.isEpisode,
|
imageURL: request.imageURL, aniListID: request.aniListID, isEpisode: request.isEpisode,
|
||||||
showTitle: request.showTitle, season: request.season,
|
showTitle: request.showTitle, season: request.season,
|
||||||
episode: request.episode, subtitleURL: request.subtitleURL,
|
episode: request.episode, subtitleURL: request.subtitleURL,
|
||||||
showPosterURL: request.showPosterURL
|
showPosterURL: request.showPosterURL
|
||||||
|
|
@ -122,7 +123,7 @@ extension JSController {
|
||||||
|
|
||||||
|
|
||||||
func downloadMP4(url: URL, headers: [String: String], title: String? = nil,
|
func downloadMP4(url: URL, headers: [String: String], title: String? = nil,
|
||||||
imageURL: URL? = nil, isEpisode: Bool = false,
|
imageURL: URL? = nil, aniListID: Int? = nil, isEpisode: Bool = false,
|
||||||
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||||
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||||
|
|
@ -399,6 +400,7 @@ extension JSController {
|
||||||
private func downloadWithOriginalMethod(request: DownloadRequest, completionHandler: ((Bool, String) -> Void)?) {
|
private func downloadWithOriginalMethod(request: DownloadRequest, completionHandler: ((Bool, String) -> Void)?) {
|
||||||
self.startDownload(
|
self.startDownload(
|
||||||
url: request.url,
|
url: request.url,
|
||||||
|
aniListID: request.aniListID,
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
title: request.title,
|
title: request.title,
|
||||||
imageURL: request.imageURL,
|
imageURL: request.imageURL,
|
||||||
|
|
@ -531,4 +533,4 @@ extension JSController {
|
||||||
private func logQualitySelectionResult(quality: QualityOption, preference: String) {
|
private func logQualitySelectionResult(quality: QualityOption, preference: String) {
|
||||||
Logger.shared.log("Quality selected: \(quality.name) (preference: \(preference))", type: "Download")
|
Logger.shared.log("Quality selected: \(quality.name) (preference: \(preference))", type: "Download")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +104,7 @@ extension JSController {
|
||||||
/// - completionHandler: Optional callback for download status
|
/// - completionHandler: Optional callback for download status
|
||||||
func startDownload(
|
func startDownload(
|
||||||
url: URL,
|
url: URL,
|
||||||
|
aniListID: Int? = nil,
|
||||||
headers: [String: String] = [:],
|
headers: [String: String] = [:],
|
||||||
title: String? = nil,
|
title: String? = nil,
|
||||||
imageURL: URL? = nil,
|
imageURL: URL? = nil,
|
||||||
|
|
@ -121,6 +122,7 @@ extension JSController {
|
||||||
// Use the stream type aware download method
|
// Use the stream type aware download method
|
||||||
downloadWithStreamTypeSupport(
|
downloadWithStreamTypeSupport(
|
||||||
url: url,
|
url: url,
|
||||||
|
aniListID: aniListID,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
title: title,
|
title: title,
|
||||||
imageURL: imageURL,
|
imageURL: imageURL,
|
||||||
|
|
@ -177,7 +179,8 @@ extension JSController {
|
||||||
subtitleURL: subtitleURL,
|
subtitleURL: subtitleURL,
|
||||||
asset: asset,
|
asset: asset,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
module: module // Pass the module to store it for queue processing
|
module: module, // Pass the module to store it for queue processing
|
||||||
|
aniListID: aniListID
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add to the download queue
|
// Add to the download queue
|
||||||
|
|
@ -271,6 +274,7 @@ extension JSController {
|
||||||
// Use the exact same method that manual downloads use
|
// Use the exact same method that manual downloads use
|
||||||
downloadWithStreamTypeSupport(
|
downloadWithStreamTypeSupport(
|
||||||
url: queuedDownload.originalURL,
|
url: queuedDownload.originalURL,
|
||||||
|
aniListID: queuedDownload.aniListID,
|
||||||
headers: queuedDownload.headers,
|
headers: queuedDownload.headers,
|
||||||
title: queuedDownload.title,
|
title: queuedDownload.title,
|
||||||
imageURL: queuedDownload.imageURL,
|
imageURL: queuedDownload.imageURL,
|
||||||
|
|
@ -929,6 +933,13 @@ extension JSController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadPersistence.delete(id: asset.id)
|
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
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.savedAssets = DownloadPersistence.load()
|
self?.savedAssets = DownloadPersistence.load()
|
||||||
self?.objectWillChange.send()
|
self?.objectWillChange.send()
|
||||||
|
|
@ -1208,7 +1219,13 @@ extension JSController: AVAssetDownloadDelegate {
|
||||||
|
|
||||||
// Add to saved assets and save
|
// Add to saved assets and save
|
||||||
DownloadPersistence.upsert(newAsset)
|
DownloadPersistence.upsert(newAsset)
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
|
// 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: aniListID, episode: epNumber, assetID: newAsset.id.uuidString)
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.savedAssets = DownloadPersistence.load()
|
self?.savedAssets = DownloadPersistence.load()
|
||||||
self?.objectWillChange.send()
|
self?.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
@ -1543,6 +1560,7 @@ struct JSActiveDownload: Identifiable, Equatable {
|
||||||
var asset: AVURLAsset?
|
var asset: AVURLAsset?
|
||||||
var headers: [String: String]
|
var headers: [String: String]
|
||||||
var module: ScrapingModule? // Add module property to store ScrapingModule
|
var module: ScrapingModule? // Add module property to store ScrapingModule
|
||||||
|
var aniListID: Int? = nil
|
||||||
|
|
||||||
// Computed property to get the current task state
|
// Computed property to get the current task state
|
||||||
var taskState: URLSessionTask.State {
|
var taskState: URLSessionTask.State {
|
||||||
|
|
@ -1586,7 +1604,8 @@ struct JSActiveDownload: Identifiable, Equatable {
|
||||||
subtitleURL: URL? = nil,
|
subtitleURL: URL? = nil,
|
||||||
asset: AVURLAsset? = nil,
|
asset: AVURLAsset? = nil,
|
||||||
headers: [String: String] = [:],
|
headers: [String: String] = [:],
|
||||||
module: ScrapingModule? = nil // Add module parameter to initializer
|
module: ScrapingModule? = nil,
|
||||||
|
aniListID: Int? = nil // Add module parameter to initializer
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.originalURL = originalURL
|
self.originalURL = originalURL
|
||||||
|
|
@ -1646,4 +1665,58 @@ enum DownloadQueueStatus: Equatable {
|
||||||
case downloading
|
case downloading
|
||||||
/// Download has been completed
|
/// Download has been completed
|
||||||
case completed
|
case completed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,26 +24,14 @@ extension JSController {
|
||||||
/// - episode: Episode number (optional)
|
/// - episode: Episode number (optional)
|
||||||
/// - subtitleURL: Optional subtitle URL to download after video (optional)
|
/// - subtitleURL: Optional subtitle URL to download after video (optional)
|
||||||
/// - completionHandler: Called when the download is initiated or fails
|
/// - completionHandler: Called when the download is initiated or fails
|
||||||
func downloadWithStreamTypeSupport(
|
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) {
|
||||||
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,
|
|
||||||
subtitleURL: URL? = nil,
|
|
||||||
showPosterURL: URL? = nil,
|
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil
|
|
||||||
) {
|
|
||||||
let streamType = module.metadata.streamType.lowercased()
|
let streamType = module.metadata.streamType.lowercased()
|
||||||
|
|
||||||
if streamType == "hls" || streamType == "m3u8" || url.absoluteString.contains(".m3u8") {
|
if streamType == "hls" || streamType == "m3u8" || url.absoluteString.contains(".m3u8") {
|
||||||
Logger.shared.log("Using HLS download method")
|
Logger.shared.log("Using HLS download method")
|
||||||
downloadWithM3U8Support(
|
downloadWithM3U8Support(
|
||||||
url: url,
|
url: url,
|
||||||
|
aniListID: aniListID,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
title: title,
|
title: title,
|
||||||
imageURL: imageURL,
|
imageURL: imageURL,
|
||||||
|
|
@ -59,6 +47,7 @@ extension JSController {
|
||||||
Logger.shared.log("Using MP4 download method")
|
Logger.shared.log("Using MP4 download method")
|
||||||
downloadMP4(
|
downloadMP4(
|
||||||
url: url,
|
url: url,
|
||||||
|
aniListID: aniListID,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
title: title,
|
title: title,
|
||||||
imageURL: imageURL,
|
imageURL: imageURL,
|
||||||
|
|
@ -72,4 +61,4 @@ extension JSController {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
|
import Foundation
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
@ -242,7 +243,24 @@ struct DownloadView: View {
|
||||||
metadataUrl: ""
|
metadataUrl: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
let customPlayer = CustomMediaPlayerViewController(
|
|
||||||
|
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(
|
||||||
module: dummyModule,
|
module: dummyModule,
|
||||||
urlString: asset.localURL.absoluteString,
|
urlString: asset.localURL.absoluteString,
|
||||||
fullUrl: asset.originalURL.absoluteString,
|
fullUrl: asset.originalURL.absoluteString,
|
||||||
|
|
@ -277,6 +295,7 @@ struct DownloadView: View {
|
||||||
aniListID: 0,
|
aniListID: 0,
|
||||||
totalEpisodes: asset.metadata?.episode ?? 0,
|
totalEpisodes: asset.metadata?.episode ?? 0,
|
||||||
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
||||||
|
localSkipSegments: localSkipSegments,
|
||||||
headers: nil
|
headers: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1548,4 +1567,4 @@ struct SearchableStyleModifier: ViewModifier {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -691,13 +691,7 @@ private extension EpisodeCell {
|
||||||
let fullEpisodeTitle = episodeTitle.isEmpty ? baseTitle : "\(baseTitle): \(episodeTitle)"
|
let fullEpisodeTitle = episodeTitle.isEmpty ? baseTitle : "\(baseTitle): \(episodeTitle)"
|
||||||
let animeTitle = parentTitle.isEmpty ? "Unknown Anime" : parentTitle
|
let animeTitle = parentTitle.isEmpty ? "Unknown Anime" : parentTitle
|
||||||
|
|
||||||
jsController.downloadWithStreamTypeSupport(
|
jsController.downloadWithStreamTypeSupport(url: url, headers: headers, title: fullEpisodeTitle, imageURL: episodeThumbnailURL, module: module, isEpisode: true, aniListID: itemID,
|
||||||
url: url,
|
|
||||||
headers: headers,
|
|
||||||
title: fullEpisodeTitle,
|
|
||||||
imageURL: episodeThumbnailURL,
|
|
||||||
module: module,
|
|
||||||
isEpisode: true,
|
|
||||||
showTitle: animeTitle,
|
showTitle: animeTitle,
|
||||||
season: 1,
|
season: 1,
|
||||||
episode: episodeID + 1,
|
episode: episodeID + 1,
|
||||||
|
|
@ -976,8 +970,6 @@ private extension EpisodeCell {
|
||||||
}
|
}
|
||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed Jikan fetching from EpisodeCell. All filler/Jikan handling is now in MediaInfoView and passed in via `fillerEpisodes`.
|
|
||||||
|
|
||||||
func handleFetchFailure(error: Error) {
|
func handleFetchFailure(error: Error) {
|
||||||
Logger.shared.log("Episode details fetch error: \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Episode details fetch error: \(error.localizedDescription)", type: "Error")
|
||||||
|
|
|
||||||
|
|
@ -2228,6 +2228,7 @@ struct MediaInfoView: View {
|
||||||
|
|
||||||
self.jsController.downloadWithStreamTypeSupport(
|
self.jsController.downloadWithStreamTypeSupport(
|
||||||
url: url,
|
url: url,
|
||||||
|
aniListID: self.itemID,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
title: episodeTitle,
|
title: episodeTitle,
|
||||||
imageURL: episodeThumbnailURL,
|
imageURL: episodeThumbnailURL,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue