From 6640a8a4b7890757738a32c4b916f27d202cc772 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Wed, 16 Apr 2025 11:30:47 +0100 Subject: [PATCH 1/4] add debounce to SearchBar --- Sora/Views/SearchView.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index cfd739d..99d1c4a 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -311,6 +311,7 @@ struct SearchView: View { } struct SearchBar: View { + @State private var debounceTimer: Timer? @Binding var text: String var onSearchButtonClicked: () -> Void @@ -321,6 +322,14 @@ struct SearchBar: View { .padding(.horizontal, 25) .background(Color(.systemGray6)) .cornerRadius(8) + .onChange(of: text){newValue in + debounceTimer?.invalidate() + // Start a new timer to wait before performing the action + debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in + // Perform the action after the delay (debouncing) + onSearchButtonClicked() + } + } .overlay( HStack { Image(systemName: "magnifyingglass") From 78865f9330a141bca3dca41f8e02704ba4b8c4b3 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Thu, 17 Apr 2025 13:36:48 +0100 Subject: [PATCH 2/4] add debounce to searchBar --- Sora/Views/SearchView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 99d1c4a..ab63ae9 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -325,7 +325,7 @@ struct SearchBar: View { .onChange(of: text){newValue in debounceTimer?.invalidate() // Start a new timer to wait before performing the action - debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in + debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in // Perform the action after the delay (debouncing) onSearchButtonClicked() } From 791b033efc1480c7521faaf76475d8248c1abc47 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Thu, 1 May 2025 01:09:40 +0100 Subject: [PATCH 3/4] provided headers for streams --- .../ContinueWatchingItem.swift | 1 + .../Utils/JSLoader/JSController-Streams.swift | 97 ++++++--- .../CustomPlayer/CustomPlayer.swift | 49 ++++- Sora/Utils/MediaPlayer/VideoPlayer.swift | 18 +- Sora/Views/LibraryView/LibraryView.swift | 4 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 187 +++++++----------- 6 files changed, 191 insertions(+), 165 deletions(-) diff --git a/Sora/Utils/ContinueWatching/ContinueWatchingItem.swift b/Sora/Utils/ContinueWatching/ContinueWatchingItem.swift index 7490295..7154e4b 100644 --- a/Sora/Utils/ContinueWatching/ContinueWatchingItem.swift +++ b/Sora/Utils/ContinueWatching/ContinueWatchingItem.swift @@ -18,4 +18,5 @@ struct ContinueWatchingItem: Codable, Identifiable { let subtitles: String? let aniListID: Int? let module: ScrapingModule + let headers: [String:String]? } diff --git a/Sora/Utils/JSLoader/JSController-Streams.swift b/Sora/Utils/JSLoader/JSController-Streams.swift index 85615de..2f29a05 100644 --- a/Sora/Utils/JSLoader/JSController-Streams.swift +++ b/Sora/Utils/JSLoader/JSController-Streams.swift @@ -9,9 +9,9 @@ import JavaScriptCore extension JSController { - func fetchStreamUrl(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) { + func fetchStreamUrl(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?, sources: [[String:Any]]? )) -> Void) { guard let url = URL(string: episodeUrl) else { - completion((nil, nil)) + completion((nil, nil,nil)) return } @@ -20,13 +20,13 @@ extension JSController { if let error = error { Logger.shared.log("Network error: \(error)", type: "Error") - DispatchQueue.main.async { completion((nil, nil)) } + DispatchQueue.main.async { completion((nil, nil,nil)) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { Logger.shared.log("Failed to decode HTML", type: "Error") - DispatchQueue.main.async { completion((nil, nil)) } + DispatchQueue.main.async { completion((nil, nil, nil)) } return } @@ -36,10 +36,21 @@ extension JSController { if let data = resultString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("JSON DATA IS \(json) 2") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil - - if let streamsArray = json["streams"] as? [String] { + var streamUrlsAndHeaders : [[String:Any]]? = nil + if let streamSources = json["streams"] as? [[String:Any]] + { + streamUrlsAndHeaders = streamSources + Logger.shared.log("Found \(streamSources.count) streams and headers", type: "Stream") + } + else if let streamSource = json["stream"] as? [String:Any] + { + streamUrlsAndHeaders = [streamSource] + Logger.shared.log("Found single stream with headers", type: "Stream") + } + else if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") } else if let streamUrl = json["stream"] as? String { @@ -57,45 +68,45 @@ extension JSController { Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") DispatchQueue.main.async { - completion((streamUrls, subtitleUrls)) + completion((streamUrls, subtitleUrls,streamUrlsAndHeaders)) } return } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") - DispatchQueue.main.async { completion((streamsArray, nil)) } + DispatchQueue.main.async { completion((streamsArray, nil,nil)) } return } } } Logger.shared.log("Starting stream from: \(resultString)", type: "Stream") - DispatchQueue.main.async { completion(([resultString], nil)) } + DispatchQueue.main.async { completion(([resultString], nil,nil)) } } else { Logger.shared.log("Failed to extract stream URL", type: "Error") - DispatchQueue.main.async { completion((nil, nil)) } + DispatchQueue.main.async { completion((nil, nil,nil)) } } }.resume() } - func fetchStreamUrlJS(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) { + func fetchStreamUrlJS(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?,sources: [[String:Any]]? )) -> Void) { if let exception = context.exception { Logger.shared.log("JavaScript exception: \(exception)", type: "Error") - completion((nil, nil)) + completion((nil, nil,nil)) return } guard let extractStreamUrlFunction = context.objectForKeyedSubscript("extractStreamUrl") else { Logger.shared.log("No JavaScript function extractStreamUrl found", type: "Error") - completion((nil, nil)) + completion((nil, nil,nil)) return } let promiseValue = extractStreamUrlFunction.call(withArguments: [episodeUrl]) guard let promise = promiseValue else { Logger.shared.log("extractStreamUrl did not return a Promise", type: "Error") - completion((nil, nil)) + completion((nil, nil,nil)) return } @@ -106,10 +117,21 @@ extension JSController { let data = jsonString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("JSON object is \(json) 1") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil - - if let streamsArray = json["streams"] as? [String] { + var streamUrlsAndHeaders : [[String:Any]]? = nil + if let streamSources = json["streams"] as? [[String:Any]] + { + streamUrlsAndHeaders = streamSources + Logger.shared.log("Found \(streamSources.count) streams and headers", type: "Stream") + } + else if let streamSource = json["stream"] as? [String:Any] + { + streamUrlsAndHeaders = [streamSource] + Logger.shared.log("Found single stream with headers", type: "Stream") + } + else if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") } else if let streamUrl = json["stream"] as? String { @@ -127,14 +149,14 @@ extension JSController { Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") DispatchQueue.main.async { - completion((streamUrls, subtitleUrls)) + completion((streamUrls, subtitleUrls,streamUrlsAndHeaders)) } return } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") - DispatchQueue.main.async { completion((streamsArray, nil)) } + DispatchQueue.main.async { completion((streamsArray, nil,nil)) } return } } @@ -143,14 +165,14 @@ extension JSController { let streamUrl = result.toString() Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream") DispatchQueue.main.async { - completion((streamUrl != nil ? [streamUrl!] : nil, nil)) + completion((streamUrl != nil ? [streamUrl!] : nil, nil,nil)) } } let catchBlock: @convention(block) (JSValue) -> Void = { error in Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") DispatchQueue.main.async { - completion((nil, nil)) + completion((nil, nil,nil)) } } @@ -161,40 +183,40 @@ extension JSController { promise.invokeMethod("catch", withArguments: [catchFunction as Any]) } - func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) { + func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?,sources: [[String:Any]]? )) -> Void) { let url = URL(string: episodeUrl)! let task = URLSession.custom.dataTask(with: url) { [weak self] data, response, error in guard let self = self else { return } if let error = error { Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error") - DispatchQueue.main.async { completion((nil, nil)) } + DispatchQueue.main.async { completion((nil, nil,nil)) } return } guard let data = data, let htmlString = String(data: data, encoding: .utf8) else { Logger.shared.log("Failed to fetch HTML data", type: "Error") - DispatchQueue.main.async { completion((nil, nil)) } + DispatchQueue.main.async { completion((nil, nil, nil)) } return } DispatchQueue.main.async { if let exception = self.context.exception { Logger.shared.log("JavaScript exception: \(exception)", type: "Error") - completion((nil, nil)) + completion((nil, nil, nil)) return } guard let extractStreamUrlFunction = self.context.objectForKeyedSubscript("extractStreamUrl") else { Logger.shared.log("No JavaScript function extractStreamUrl found", type: "Error") - completion((nil, nil)) + completion((nil, nil, nil)) return } let promiseValue = extractStreamUrlFunction.call(withArguments: [htmlString]) guard let promise = promiseValue else { Logger.shared.log("extractStreamUrl did not return a Promise", type: "Error") - completion((nil, nil)) + completion((nil, nil, nil)) return } @@ -205,10 +227,21 @@ extension JSController { let data = jsonString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("JSON object is \(json) 3 ") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil - - if let streamsArray = json["streams"] as? [String] { + var streamUrlsAndHeaders : [[String:Any]]? = nil + if let streamSources = json["streams"] as? [[String:Any]] + { + streamUrlsAndHeaders = streamSources + Logger.shared.log("Found \(streamSources.count) streams and headers", type: "Stream") + } + else if let streamSource = json["stream"] as? [String:Any] + { + streamUrlsAndHeaders = [streamSource] + Logger.shared.log("Found single stream with headers", type: "Stream") + } + else if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") } else if let streamUrl = json["stream"] as? String { @@ -226,14 +259,14 @@ extension JSController { Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") DispatchQueue.main.async { - completion((streamUrls, subtitleUrls)) + completion((streamUrls, subtitleUrls, streamUrlsAndHeaders)) } return } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") - DispatchQueue.main.async { completion((streamsArray, nil)) } + DispatchQueue.main.async { completion((streamsArray, nil, nil)) } return } } @@ -242,14 +275,14 @@ extension JSController { let streamUrl = result.toString() Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream") DispatchQueue.main.async { - completion((streamUrl != nil ? [streamUrl!] : nil, nil)) + completion((streamUrl != nil ? [streamUrl!] : nil, nil, nil)) } } let catchBlock: @convention(block) (JSValue) -> Void = { error in Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") DispatchQueue.main.async { - completion((nil, nil)) + completion((nil, nil, nil)) } } diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 174809d..753bfd7 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -22,6 +22,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let subtitlesURL: String? let onWatchNext: () -> Void let aniListID: Int + var headers: [String:String]? = nil private var aniListUpdatedSuccessfully = false private var aniListUpdateImpossible: Bool = false @@ -177,7 +178,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele onWatchNext: @escaping () -> Void, subtitlesURL: String?, aniListID: Int, - episodeImageUrl: String) { + episodeImageUrl: String,headers:[String:String]?) { self.module = module self.streamURL = urlString @@ -188,6 +189,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele self.onWatchNext = onWatchNext self.subtitlesURL = subtitlesURL self.aniListID = aniListID + self.headers = headers super.init(nibName: nil, bundle: nil) @@ -196,8 +198,18 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } var request = URLRequest(url: url) - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + if let mydict = headers, !mydict.isEmpty + { + for (key,value) in mydict + { + request.addValue(value, forHTTPHeaderField: key) + } + } + else + { + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + } request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") @@ -1380,7 +1392,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele fullUrl: self.fullUrl, subtitles: self.subtitlesURL, aniListID: self.aniListID, - module: self.module + module: self.module, + headers: self.headers ) ContinueWatchingManager.shared.save(item: item) } @@ -1712,8 +1725,18 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private func parseM3U8(url: URL, completion: @escaping () -> Void) { var request = URLRequest(url: url) - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + if let mydict = headers, !mydict.isEmpty + { + for (key,value) in mydict + { + request.addValue(value, forHTTPHeaderField: key) + } + } + else + { + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + } request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") @@ -1799,8 +1822,18 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let wasPlaying = player.rate > 0 var request = URLRequest(url: url) - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + if let mydict = headers, !mydict.isEmpty + { + for (key,value) in mydict + { + request.addValue(value, forHTTPHeaderField: key) + } + } + else + { + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + } request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") diff --git a/Sora/Utils/MediaPlayer/VideoPlayer.swift b/Sora/Utils/MediaPlayer/VideoPlayer.swift index 122c466..b8bbb20 100644 --- a/Sora/Utils/MediaPlayer/VideoPlayer.swift +++ b/Sora/Utils/MediaPlayer/VideoPlayer.swift @@ -18,6 +18,7 @@ class VideoPlayerViewController: UIViewController { var fullUrl: String = "" var subtitles: String = "" var aniListID: Int = 0 + var headers: [String:String]? = nil var episodeNumber: Int = 0 var episodeImageUrl: String = "" @@ -40,8 +41,18 @@ class VideoPlayerViewController: UIViewController { } var request = URLRequest(url: url) - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") - request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + if let mydict = headers, !mydict.isEmpty + { + for (key,value) in mydict + { + request.addValue(value, forHTTPHeaderField: key) + } + } + else + { + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") + request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin") + } request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]]) @@ -127,7 +138,8 @@ class VideoPlayerViewController: UIViewController { fullUrl: self.fullUrl, subtitles: self.subtitles, aniListID: self.aniListID, - module: self.module + module: self.module, + headers: self.headers ) ContinueWatchingManager.shared.save(item: item) } diff --git a/Sora/Views/LibraryView/LibraryView.swift b/Sora/Views/LibraryView/LibraryView.swift index 0f7a233..94ea459 100644 --- a/Sora/Views/LibraryView/LibraryView.swift +++ b/Sora/Views/LibraryView/LibraryView.swift @@ -272,7 +272,9 @@ struct ContinueWatchingCell: View { onWatchNext: { }, subtitlesURL: item.subtitles, aniListID: item.aniListID ?? 0, - episodeImageUrl: item.imageUrl + episodeImageUrl: item.imageUrl, + headers: item.headers ?? nil + ) customMediaPlayer.modalPresentationStyle = .fullScreen diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 709e3c2..dc28939 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -643,119 +643,41 @@ struct MediaInfoView: View { currentStreamTitle = "Episode \(selectedEpisodeNumber)" showStreamLoadingView = true isFetchingEpisode = true + let completion: ((streams: [String]?, subtitles: [String]?, sources: [[String: Any]]?)) -> Void = { result in + guard self.activeFetchID == fetchID else { return } + if let streams = result.sources, !streams.isEmpty{ + if streams.count > 1 { + self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + } else { + self.playStream(url: streams[0]["streamUrl"] as? String ?? "", fullURL: href, subtitles: streams[0]["subtitle"] as? String ?? "",headers: streams[0]["headers"] as! [String : String]) + } + } + else if let streams = result.streams, !streams.isEmpty { + if streams.count > 1 { + self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + } else { + self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + } + } else { + self.handleStreamFailure(error: nil) + } + DispatchQueue.main.async { + self.isFetchingEpisode = false + } + } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { Task { do { let jsContent = try moduleManager.getModuleContent(module) jsController.loadScript(jsContent) - - if module.metadata.softsub == true { - if module.metadata.asyncJS == true { - jsController.fetchStreamUrlJS(episodeUrl: href, softsub: true, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } else if module.metadata.streamAsyncJS == true { - jsController.fetchStreamUrlJSSecond(episodeUrl: href, softsub: true, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } else { - jsController.fetchStreamUrl(episodeUrl: href, softsub: true, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } + if module.metadata.asyncJS == true { + jsController.fetchStreamUrlJS(episodeUrl: href, softsub: module.metadata.softsub == true, module: module, completion: completion) + } else if module.metadata.streamAsyncJS == true { + jsController.fetchStreamUrlJSSecond(episodeUrl: href, softsub: module.metadata.softsub == true, module: module, completion: completion) } else { - if module.metadata.asyncJS == true { - jsController.fetchStreamUrlJS(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } else if module.metadata.streamAsyncJS == true { - jsController.fetchStreamUrlJSSecond(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } else { - jsController.fetchStreamUrl(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } - - if let streams = result.streams, !streams.isEmpty { - if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) - } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) - } - } else { - self.handleStreamFailure(error: nil) - } - DispatchQueue.main.async { - self.isFetchingEpisode = false - } - } - } + jsController.fetchStreamUrl(episodeUrl: href, softsub: module.metadata.softsub == true, module: module, completion: completion) } + } catch { self.handleStreamFailure(error: error) DispatchQueue.main.async { @@ -779,9 +701,10 @@ struct MediaInfoView: View { self.isLoading = false } - func showStreamSelectionAlert(streams: [String], fullURL: String, subtitles: String? = nil) { + func showStreamSelectionAlert(streams: [Any], fullURL: String, subtitles: String? = nil) { self.isFetchingEpisode = false self.showStreamLoadingView = false + print("MULTIPLE STREAMS \(streams)") DispatchQueue.main.async { let alert = UIAlertController(title: "Select Server", message: "Choose a server to play from", preferredStyle: .actionSheet) @@ -789,27 +712,46 @@ struct MediaInfoView: View { var streamIndex = 1 while index < streams.count { - let title: String - let streamUrl: String - - if index + 1 < streams.count { - if !streams[index].lowercased().contains("http") { - title = streams[index] - streamUrl = streams[index + 1] - index += 2 + var title: String = "" + var streamUrl: String = "" + var headers: [String:String]? = nil + if let streams = streams as? [String] + { + if index + 1 < streams.count { + if !streams[index].lowercased().contains("http") { + title = streams[index] + streamUrl = streams[index + 1] + index += 2 + } else { + title = "Stream \(streamIndex)" + streamUrl = streams[index] + index += 1 + } } else { title = "Stream \(streamIndex)" streamUrl = streams[index] index += 1 } - } else { - title = "Stream \(streamIndex)" - streamUrl = streams[index] + } + else if let streams = streams as? [[String: Any]] + { + if let currTitle = streams[index]["title"] as? String + { + title = currTitle + streamUrl = (streams[index]["streamUrl"] as? String)! + } + else + { + title = "Stream \(streamIndex)" + streamUrl = (streams[index]["streamUrl"] as? String)! + } + headers = streams[index]["headers"] as? [String:String] ?? [:] index += 1 } + alert.addAction(UIAlertAction(title: title, style: .default) { _ in - self.playStream(url: streamUrl, fullURL: fullURL, subtitles: subtitles) + self.playStream(url: streamUrl, fullURL: fullURL, subtitles: subtitles,headers: headers) }) streamIndex += 1 @@ -843,7 +785,7 @@ struct MediaInfoView: View { } } - func playStream(url: String, fullURL: String, subtitles: String? = nil) { + func playStream(url: String, fullURL: String, subtitles: String? = nil,headers: [String:String]? = nil) { self.isFetchingEpisode = false self.showStreamLoadingView = false DispatchQueue.main.async { @@ -861,6 +803,7 @@ struct MediaInfoView: View { scheme = "nplayer-\(url)" case "Default": let videoPlayerViewController = VideoPlayerViewController(module: module) + videoPlayerViewController.headers = headers videoPlayerViewController.streamUrl = url videoPlayerViewController.fullUrl = fullURL videoPlayerViewController.episodeNumber = selectedEpisodeNumber @@ -890,6 +833,7 @@ struct MediaInfoView: View { } let customMediaPlayer = CustomMediaPlayerViewController( + module: module, urlString: url.absoluteString, fullUrl: fullURL, @@ -900,7 +844,8 @@ struct MediaInfoView: View { }, subtitlesURL: subtitles, aniListID: itemID ?? 0, - episodeImageUrl: selectedEpisodeImage + episodeImageUrl: selectedEpisodeImage, + headers: headers ?? nil ) customMediaPlayer.modalPresentationStyle = .fullScreen Logger.shared.log("Opening custom media player with url: \(url)") From 4b4f76392dc7b92aecd3aad3c7f56efa0551298c Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Mon, 5 May 2025 17:07:48 +0100 Subject: [PATCH 4/4] allow string as post body data type --- Sora/Utils/Extensions/JavaScriptCore+Extensions.swift | 2 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift index 18f4d39..b5ddc34 100644 --- a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift @@ -162,7 +162,7 @@ extension JSContext { if(method != "GET") { // Ensure body is properly serialized - processedBody = body ? JSON.stringify(body) : null + processedBody = (body && (typeof body === 'object')) ? JSON.stringify(body) : (body || null) } return new Promise(function(resolve, reject) { diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index dc28939..f4de2ad 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -738,7 +738,7 @@ struct MediaInfoView: View { if let currTitle = streams[index]["title"] as? String { title = currTitle - streamUrl = (streams[index]["streamUrl"] as? String)! + streamUrl = (streams[index]["streamUrl"] as? String) ?? "" } else {