diff --git a/IINA+/Utils/VideoDecoder/Huya.swift b/IINA+/Utils/VideoDecoder/Huya.swift index 9cd2a938..26f04dce 100644 --- a/IINA+/Utils/VideoDecoder/Huya.swift +++ b/IINA+/Utils/VideoDecoder/Huya.swift @@ -26,20 +26,15 @@ class Huya: NSObject, SupportSiteProtocol { private let huyaUid = (Int(Date().timeIntervalSince1970 * 1000) % Int(1e10) * Int(1e3) + Int.random(in: Int(1e2).. Promise { - getHuyaInfo(url).map { + getHuyaInfoMP(url).map { $0 } } func decodeUrl(_ url: String) -> Promise { - getHuyaVideos(url) - /* - getHuyaInfoM(url).map { - var yougetJson = YouGetJSON(rawUrl: url) - yougetJson.title = $0.title - return $0.write(to: yougetJson, uid: self.huyaUid) - } - */ + getHuyaInfoMP(url).map { + $0.videos(url, uid: self.huyaUid) + } } // MARK: - Huya @@ -138,6 +133,19 @@ class Huya: NSObject, SupportSiteProtocol { return info } } + + func getHuyaInfoMP(_ url: String) -> Promise { + let ucs = url.pathComponents + guard ucs.count >= 3 else { + return .init(error: VideoGetError.invalidLink) + } + let rid = ucs[2] + + return pSession.request("https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid=\(rid)").responseData().map { + let jsonObj: JSONObject = try JSONParser.JSONObjectWithData($0.data) + return try HuyaInfoMP(object: jsonObj) + } + } } /* @@ -433,6 +441,84 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { } } +struct HuyaInfoMP: Unmarshaling, LiveInfo { + + var title: String + var name: String + var avatar: String + var cover: String + var isLiving: Bool + var site: SupportSites = .huya + + var streamInfos: [HuyaInfoM.StreamInfo] + var bitRateInfos: [HuyaInfoM.BitRateInfo] + + init(object: any Marshal.MarshaledObject) throws { + let name1: String = try object.value(for: "data.liveData.roomName") + let name2: String = try object.value(for: "data.liveData.introduction") + + title = name1 == "" ? name2 : name1 + + name = try object.value(for: "data.liveData.nick") + avatar = try object.value(for: "data.liveData.avatar180") + avatar = avatar.https() + cover = try object.value(for: "data.liveData.screenshot") + cover = cover.https() + + let liveStatus: String = try object.value(for: "data.liveStatus") + isLiving = liveStatus == "ON" + + streamInfos = try object.value(for: "data.stream.baseSteamInfoList") + + let bitRateInfoString: String = try object.value(for: "data.liveData.bitRateInfo") + guard let data = bitRateInfoString.data(using: .utf8) else { + throw VideoGetError.notFountData + } + let jsonObj: [JSONObject] = try JSONParser.JSONArrayWithData(data) + bitRateInfos = try jsonObj.map(HuyaInfoM.BitRateInfo.init) + } + + + func videos(_ url: String, uid: Int) -> YouGetJSON { + var yougetJson = YouGetJSON(rawUrl: url) + yougetJson.title = title + + let urls = streamInfos +// .sorted { i1, i2 -> Bool in +// i1.sCdnType == defaultCDN +// } + .sorted { i1, i2 -> Bool in + !i1.sFlvUrl.contains("txdirect.flv.huya.com") + }.compactMap { + HuyaUrl.format( + uid, + sStreamName: $0.sStreamName, + sFlvUrl: $0.sFlvUrl, + sFlvUrlSuffix: $0.sFlvUrlSuffix, + sFlvAntiCode: $0.sFlvAntiCode) + } + + guard urls.count > 0 else { + return yougetJson + } + + bitRateInfos.map { + ($0.sDisplayName, $0.iBitRate) + }.forEach { (name, rate) in + var us = urls.map { + $0.replacingOccurrences(of: "&ratio=0", with: "&ratio=\(rate)") + } + var s = Stream(url: us.removeFirst()) + s.src = us + s.quality = rate == 0 ? 9999999 : rate + + yougetJson.streams[name] = s + } + + return yougetJson + } +} + struct HuyaVideoSelector: VideoSelector { var id: String diff --git a/IINA+/Utils/VideoDecoder/HuyaUrl.swift b/IINA+/Utils/VideoDecoder/HuyaUrl.swift index f72d8126..8e60de6f 100644 --- a/IINA+/Utils/VideoDecoder/HuyaUrl.swift +++ b/IINA+/Utils/VideoDecoder/HuyaUrl.swift @@ -21,36 +21,27 @@ class HuyaUrl: NSObject { let sid = now() - var parameters = [String: String]() + var antiCodes = antiCodeDic(sFlvAntiCode) - sFlvAntiCode.split(separator: "&").map { - $0.split(separator: "=", maxSplits: 1).map(String.init) - }.filter { - $0.count == 2 - }.forEach { - parameters[$0[0]] = $0[1] - } - - // (seqid - uid) > sid let seqid = uid + now() guard let convertUid = rotUid(uid), - let wsSecret = wsSecret(sFlvAntiCode, convertUid: convertUid, seqid: seqid, streamName: sStreamName) else { return "" } + let wsSecret = wsSecret(antiCodes, convertUid: convertUid, seqid: seqid, streamName: sStreamName) else { return "" } - parameters["u"] = "\(convertUid)" - parameters["wsSecret"] = wsSecret + antiCodes["u"] = "\(convertUid)" + antiCodes["wsSecret"] = wsSecret -// parameters["fm"] = nil - parameters["seqid"] = "\(seqid)" - parameters["sdk_sid"] = "\(sid)" - parameters["sv"] = "2405220949" +// antiCodes["fm"] = nil + antiCodes["seqid"] = "\(seqid)" + antiCodes["sdk_sid"] = "\(sid)" + antiCodes["sv"] = "2405220949" - parameters["sdkPcdn"] = "1_1" - parameters["t"] = "100" - parameters["a_block"] = "0" - parameters["ver"] = "1" - parameters["ratio"] = "0" - parameters["dMod"] = "mseh-32" + antiCodes["sdkPcdn"] = "1_1" +// parameters["t"] = "100" + antiCodes["a_block"] = "0" + antiCodes["ver"] = "1" + antiCodes["ratio"] = "0" + antiCodes["dMod"] = "mseh-32" let example = "https://hw.flv.huya.com/src/1394575534-1394575534-5989656310331736064-2789274524-10057-A-0-1.flv?wsSecret=4b1ac7c8b5b3792b756f419bd6db09f8&wsTime=665aeff5&seqid=1750435781966&ctype=huya_webh5&ver=1&txyp=o%3An4%3B&fs=bgct&sphdcdn=al_7-tx_3-js_3-ws_7-bd_2-hw_2&sphdDC=huya&sphd=264_*-265_*&exsphd=264_500,264_2000,264_4000,264_6000,264_8000,&ratio=2000&dMod=mseh-32&sdkPcdn=1_1&u=33818100666&t=100&sv=2405220949&sdk_sid=1717235700093&a_block=0" @@ -63,7 +54,7 @@ class HuyaUrl: NSObject { let pars = URLComponents(string: example)!.queryItems!.compactMap { let key = $0.name - if let value = parameters[key] { + if let value = antiCodes[key] { return key + "=" + value } else { Log("Huya parameters missing key, \($0.description)") @@ -101,31 +92,35 @@ class HuyaUrl: NSObject { return Int(a + n, radix: 2) } - private static func wsSecret(_ antiCode: String, - convertUid: Int, - seqid: Int, - streamName: String) -> String? { - - let d = antiCode.components(separatedBy: "&").reduce([String: String]()) { (re, str) -> [String: String] in - var r = re - let kv = str.components(separatedBy: "=") - guard kv.count == 2 else { return r } - r[kv[0]] = kv[1] - return r - } - - guard var u = d["fm"]?.removingPercentEncoding?.base64Decode(), - let l = d["wsTime"], - let ctype = d["ctype"] else { return nil } - - let s = "\(seqid)|\(ctype)|100".md5() - -// let o = this[Mt].replace(Xt, r).replace($t, this[Kt]).replace(Zt, s).replace(te, this[Bt]); + private static func wsSecret(_ antiCodes: [String: String], + convertUid: Int, + seqid: Int, + streamName: String) -> String? { + guard var u = antiCodes["fm"]?.removingPercentEncoding?.base64Decode(), + let l = antiCodes["wsTime"], + let ctype = antiCodes["ctype"] else { return nil } + let t = antiCodes["t"] ?? "100" + let s = "\(seqid)|\(ctype)|\(t)".md5() + + // let o = this[Mt].replace(Xt, r).replace($t, this[Kt]).replace(Zt, s).replace(te, this[Bt]); u = u.replacingOccurrences(of: "$0", with: "\(convertUid)") u = u.replacingOccurrences(of: "$1", with: streamName) u = u.replacingOccurrences(of: "$2", with: s) u = u.replacingOccurrences(of: "$3", with: l) - + return u.md5() } + + private static func antiCodeDic(_ antiCode: String) -> [String: String] { + var dic = [String: String]() + + antiCode.split(separator: "&").map { + $0.split(separator: "=", maxSplits: 1).map(String.init) + }.filter { + $0.count == 2 + }.forEach { + dic[$0[0]] = $0[1] + } + return dic + } }