MULTI SERVER SUPPORT!!!!!!!!!!!!!!!!!!!!!

This commit is contained in:
Francesco 2025-04-04 17:42:47 +02:00
parent d878b13528
commit 71acdefd0b
2 changed files with 165 additions and 157 deletions

View file

@ -33,56 +33,45 @@ extension JSController {
Logger.shared.log(html, type: "HTMLStrings")
if let parseFunction = self.context.objectForKeyedSubscript("extractStreamUrl"),
let resultString = parseFunction.call(withArguments: [html]).toString() {
if softsub {
if let data = resultString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let isMultiStream = module.metadata.multiStream ?? false
let isMultiSubs = module.metadata.multiSubs ?? false
var streamUrls: [String]?
if isMultiStream, 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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
if let data = resultString.data(using: .utf8) {
do {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
var streamUrls: [String]? = nil
var subtitleUrls: [String]? = nil
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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
}
if let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
return
}
var subtitleUrls: [String]?
if isMultiSubs, let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
} else {
Logger.shared.log("Failed to parse softsub JSON", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
}
} else {
let moduleMetadata = self.context.objectForKeyedSubscript("module")?.objectForKeyedSubscript("metadata")
let isMultiStream = moduleMetadata?.objectForKeyedSubscript("multiStream")?.toBool() == true
if isMultiStream {
if let data = resultString.data(using: .utf8),
let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] {
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)) }
} else {
Logger.shared.log("Failed to parse multi-stream JSON array", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
return
}
} else {
Logger.shared.log("Starting stream from: \(resultString)", type: "Stream")
DispatchQueue.main.async { completion(([resultString], nil)) }
}
}
Logger.shared.log("Starting stream from: \(resultString)", type: "Stream")
DispatchQueue.main.async { completion(([resultString], nil)) }
} else {
Logger.shared.log("Failed to extract stream URL", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
@ -111,65 +100,51 @@ extension JSController {
}
let thenBlock: @convention(block) (JSValue) -> Void = { [weak self] result in
guard let self = self else { return }
guard self != nil else { return }
if softsub {
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let isMultiStream = module.metadata.multiStream ?? false
let isMultiSubs = module.metadata.multiSubs ?? false
var streamUrls: [String]?
if isMultiStream, 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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8) {
do {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
var streamUrls: [String]? = nil
var subtitleUrls: [String]? = nil
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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
}
if let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
return
}
var subtitleUrls: [String]?
if isMultiSubs, let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
} else {
Logger.shared.log("Failed to parse softsub JSON in JS", type: "Error")
DispatchQueue.main.async {
completion((nil, nil))
}
}
} else {
let moduleMetadata = self.context.objectForKeyedSubscript("module")?.objectForKeyedSubscript("metadata")
let isMultiStream = moduleMetadata?.objectForKeyedSubscript("multiStream")?.toBool() == true
if isMultiStream {
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8),
let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] {
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)) }
} else {
Logger.shared.log("Failed to parse multi-stream JSON array", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
}
} else {
let streamUrl = result.toString()
Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream")
DispatchQueue.main.async {
completion((streamUrl != nil ? [streamUrl!] : nil, nil))
return
}
}
}
let streamUrl = result.toString()
Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream")
DispatchQueue.main.async {
completion((streamUrl != nil ? [streamUrl!] : nil, nil))
}
}
let catchBlock: @convention(block) (JSValue) -> Void = { error in
@ -224,65 +199,51 @@ extension JSController {
}
let thenBlock: @convention(block) (JSValue) -> Void = { [weak self] result in
guard let self = self else { return }
guard self != nil else { return }
if softsub {
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let isMultiStream = module.metadata.multiStream ?? false
let isMultiSubs = module.metadata.multiSubs ?? false
var streamUrls: [String]?
if isMultiStream, 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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8) {
do {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
var streamUrls: [String]? = nil
var subtitleUrls: [String]? = nil
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 {
streamUrls = [streamUrl]
Logger.shared.log("Found single stream", type: "Stream")
}
if let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
return
}
var subtitleUrls: [String]?
if isMultiSubs, let subsArray = json["subtitles"] as? [String] {
subtitleUrls = subsArray
Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream")
} else if let subtitleUrl = json["subtitles"] as? String {
subtitleUrls = [subtitleUrl]
Logger.shared.log("Found single subtitle track", type: "Stream")
}
Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream")
DispatchQueue.main.async {
completion((streamUrls, subtitleUrls))
}
} else {
Logger.shared.log("Failed to parse softsub JSON in JSSecond", type: "Error")
DispatchQueue.main.async {
completion((nil, nil))
}
}
} else {
let moduleMetadata = self.context.objectForKeyedSubscript("module")?.objectForKeyedSubscript("metadata")
let isMultiStream = moduleMetadata?.objectForKeyedSubscript("multiStream")?.toBool() == true
if isMultiStream {
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8),
let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] {
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)) }
} else {
Logger.shared.log("Failed to parse multi-stream JSON array", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
}
} else {
let streamUrl = result.toString()
Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream")
DispatchQueue.main.async {
completion((streamUrl != nil ? [streamUrl!] : nil, nil))
return
}
}
}
let streamUrl = result.toString()
Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream")
DispatchQueue.main.async {
completion((streamUrl != nil ? [streamUrl!] : nil, nil))
}
}
let catchBlock: @convention(block) (JSValue) -> Void = { error in

View file

@ -259,8 +259,8 @@ struct MediaInfoView: View {
Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General")
}
)
.id(refreshTrigger)
.disabled(isFetchingEpisode)
.id(refreshTrigger)
.disabled(isFetchingEpisode)
}
} else {
Text("No episodes available")
@ -299,8 +299,8 @@ struct MediaInfoView: View {
Logger.shared.log("Marked \(ep.number - 1) episodes watched within series \"\(title)\".", type: "General")
}
)
.id(refreshTrigger)
.disabled(isFetchingEpisode)
.id(refreshTrigger)
.disabled(isFetchingEpisode)
}
}
}
@ -448,7 +448,7 @@ struct MediaInfoView: View {
groups.append(currentGroup)
return groups
}
func fetchDetails() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
@ -500,9 +500,12 @@ struct MediaInfoView: View {
if module.metadata.softsub == true {
if module.metadata.asyncJS == true {
jsController.fetchStreamUrlJS(episodeUrl: href, softsub: true, module: module) { result in
// Use first stream from array
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first)
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)
}
@ -513,7 +516,11 @@ struct MediaInfoView: View {
} else if module.metadata.streamAsyncJS == true {
jsController.fetchStreamUrlJSSecond(episodeUrl: href, softsub: true, module: module) { result in
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first)
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)
}
@ -524,7 +531,11 @@ struct MediaInfoView: View {
} else {
jsController.fetchStreamUrl(episodeUrl: href, softsub: true, module: module) { result in
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first)
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)
}
@ -537,7 +548,11 @@ struct MediaInfoView: View {
if module.metadata.asyncJS == true {
jsController.fetchStreamUrlJS(episodeUrl: href, module: module) { result in
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href)
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)
}
@ -548,7 +563,11 @@ struct MediaInfoView: View {
} else if module.metadata.streamAsyncJS == true {
jsController.fetchStreamUrlJSSecond(episodeUrl: href, module: module) { result in
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href)
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)
}
@ -559,7 +578,11 @@ struct MediaInfoView: View {
} else {
jsController.fetchStreamUrl(episodeUrl: href, module: module) { result in
if let streams = result.streams, !streams.isEmpty {
self.playStream(url: streams[0], fullURL: href)
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)
}
@ -590,6 +613,30 @@ struct MediaInfoView: View {
self.isLoading = false
}
func showStreamSelectionAlert(streams: [String], fullURL: String, subtitles: String? = nil) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Select Server", message: "Choose a server to play from", preferredStyle: .actionSheet)
for (index, stream) in streams.enumerated() {
let quality = "Stream \(index + 1)"
alert.addAction(UIAlertAction(title: quality, style: .default) { _ in
self.playStream(url: stream, fullURL: fullURL, subtitles: subtitles)
})
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(alert, animated: true)
}
DispatchQueue.main.async {
self.isFetchingEpisode = false
}
}
}
func playStream(url: String, fullURL: String, subtitles: String? = nil) {
DispatchQueue.main.async {
let externalPlayer = UserDefaults.standard.string(forKey: "externalPlayer") ?? "Sora"