From 732ae7203795f13159137870b8bc47e1967ea420 Mon Sep 17 00:00:00 2001 From: xjbeta Date: Sat, 10 Feb 2024 11:04:57 +0800 Subject: [PATCH 1/4] fix script --- IINA+.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IINA+.xcodeproj/project.pbxproj b/IINA+.xcodeproj/project.pbxproj index 1e7a6e48..36454ef6 100644 --- a/IINA+.xcodeproj/project.pbxproj +++ b/IINA+.xcodeproj/project.pbxproj @@ -622,10 +622,10 @@ 01AEC8A320EDFD01001406E8 /* Sources */, 01AEC8A420EDFD01001406E8 /* Frameworks */, 01AEC8A520EDFD01001406E8 /* Resources */, - 019BFF5D212485B900E715CC /* ShellScript */, 013CE8EF2185D09F000271FB /* Copy Web Files */, 014F541926D9184100B9E82A /* Embed Frameworks */, 0152EF4329ACED2700F89E2A /* Copy DouYin */, + 01003E712B771D9100A152ED /* ShellScript */, ); buildRules = ( ); @@ -739,9 +739,9 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 019BFF5D212485B900E715CC /* ShellScript */ = { + 01003E712B771D9100A152ED /* ShellScript */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 8; + buildActionMask = 12; files = ( ); inputFileListPaths = ( @@ -752,7 +752,7 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 1; + runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/bin/bash\nBUILD_NUMBER=`date +%y%m%d%H`\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $BUILD_NUMBER\" \"${TARGET_BUILD_DIR}\"/\"${INFOPLIST_PATH}\"\n"; }; From 4f210a93a48e4eae1056ceb6f5a3ba9812ef9ccc Mon Sep 17 00:00:00 2001 From: xjbeta Date: Sun, 11 Feb 2024 13:34:41 +0800 Subject: [PATCH 2/4] fix huya --- IINA+/Utils/VideoDecoder/Huya.swift | 395 ++++++++++++++++------------ 1 file changed, 220 insertions(+), 175 deletions(-) diff --git a/IINA+/Utils/VideoDecoder/Huya.swift b/IINA+/Utils/VideoDecoder/Huya.swift index 8251d822..bd3f0a32 100644 --- a/IINA+/Utils/VideoDecoder/Huya.swift +++ b/IINA+/Utils/VideoDecoder/Huya.swift @@ -21,6 +21,8 @@ class Huya: NSObject, SupportSiteProtocol { configuration.headers.add(.userAgent(ua)) return Session(configuration: configuration) }() + + private var huyaUid = 0 func liveInfo(_ url: String) -> Promise { getHuyaInfoM(url).map { @@ -29,14 +31,7 @@ class Huya: NSObject, SupportSiteProtocol { } func decodeUrl(_ url: String) -> Promise { - getHuyaInfoM(url).map { - var yougetJson = YouGetJSON(rawUrl: url) - yougetJson.title = $0.0.title - $0.1.enumerated().forEach { - yougetJson.streams[$0.element.0] = $0.element.1 - } - return yougetJson - } + getHuyaInfo(url) } // MARK: - Huya @@ -69,77 +64,40 @@ class Huya: NSObject, SupportSiteProtocol { } } - func getHuyaInfo(_ url: String) -> Promise<(HuyaInfo, [(String, Stream)])> { -// https://github.com/zhangn1985/ykdl/blob/master/ykdl/extractors/huya/live.py + func getHuyaInfo(_ url: String) -> Promise { AF.request(url).responseString().map { + if self.huyaUid == 0 { + self.huyaUid = Int.random(in: Int(1e12).. c1.count { + str = String(str[str.startIndex...c2[c1.count-1]]) + } + } return str }() + + guard let data = hyPlayerConfigStr?.data(using: .utf8) else { + throw VideoGetError.notFountData + } - guard let roomInfoData = text.subString(from: "var TT_ROOM_DATA = ", to: ";var").data(using: .utf8), - let profileInfoData = text.subString(from: "var TT_PROFILE_INFO = ", to: ";var").data(using: .utf8), - let playerInfoData = hyPlayerConfigStr?.data(using: .utf8) else { - throw VideoGetError.notFindUrls - } - - - var roomInfoJson: JSONObject = try JSONParser.JSONObjectWithData(roomInfoData) - let profileInfoJson: JSONObject = try JSONParser.JSONObjectWithData(profileInfoData) - let playerInfoJson: JSONObject = try JSONParser.JSONObjectWithData(playerInfoData) - - roomInfoJson.merge(profileInfoJson) { (current, _) in current } - let info: HuyaInfo = try HuyaInfo(object: roomInfoJson) - - if !info.isLiving { - return (info, []) - } - - - - let streamStr: String = try playerInfoJson.value(for: "stream") - - guard let streamData = Data(base64Encoded: streamStr) else { - throw VideoGetError.notFindUrls - } - - let streamJSON: JSONObject = try JSONParser.JSONObjectWithData(streamData) - - let huyaStream: HuyaStream = try HuyaStream(object: streamJSON) - - var urls = [String]() - - if info.isSeeTogetherRoom { - urls = huyaStream.data.first?.urlsBak ?? [] - } else { - urls = huyaStream.data.first?.urls ?? [] - } - - guard urls.count > 0 else { - throw VideoGetError.notFindUrls - } - - let re = huyaStream.vMultiStreamInfo.enumerated().map { info -> (String, Stream) in - - let u = urls.first!.replacingOccurrences(of: "ratio=0", with: "ratio=\(info.element.iBitRate)") - var s = Stream(url: u) - - if info.element.iBitRate == 0, - info.offset == 0 { - s.quality = huyaStream.vMultiStreamInfo.map { - $0.iBitRate - }.max() ?? 999999999 - s.quality += 1 - } else { - s.quality = info.element.iBitRate - } - return (info.element.sDisplayName, s) - } - return (info, re) + let jsonObj: JSONObject = try JSONParser.JSONObjectWithData(data) + let info = try HuyaStream(object: jsonObj) + + let yougetJson = YouGetJSON(rawUrl: url) + + return info.write(to: yougetJson, uid: self.huyaUid) + } } @@ -178,75 +136,206 @@ struct HuyaInfo: Unmarshaling, LiveInfo { cover = try object.value(for: "screenshot") cover = cover.replacingOccurrences(of: "http://", with: "https://") - let str: String = try object.value(for: "profileRoom") - rid = Int(str) ?? -1 + rid = try object.value(for: "profileRoom") + let gameHostName: String = try object.value(for: "gameHostName") isSeeTogetherRoom = gameHostName == "seeTogether" } } + struct HuyaStream: Unmarshaling { - var data: [HuyaUrl] - var vMultiStreamInfo: [StreamInfo] - - struct StreamInfo: Unmarshaling { - var sDisplayName: String - var iBitRate: Int - var iHEVCBitRate: Int - - init(object: MarshaledObject) throws { - sDisplayName = try object.value(for: "sDisplayName") - iBitRate = try object.value(for: "iBitRate") - iHEVCBitRate = try object.value(for: "iHEVCBitRate") - } - } - - struct HuyaUrl: Unmarshaling { - var urls: [String] = [] - var urlsBak: [String] = [] - - struct StreamInfo: Unmarshaling { - var sStreamName: String - var sFlvUrl: String - var newCFlvAntiCode: String - var sFlvAntiCode: String - - init(object: MarshaledObject) throws { - sStreamName = try object.value(for: "sStreamName") - sFlvUrl = try object.value(for: "sFlvUrl") - newCFlvAntiCode = try object.value(for: "newCFlvAntiCode") - sFlvAntiCode = try object.value(for: "sFlvAntiCode") - } - } - - init(object: MarshaledObject) throws { - let streamInfos: [StreamInfo] = try object.value(for: "gameStreamInfoList") - - - urls = streamInfos.compactMap { i -> String? in - let u = i.sFlvUrl + "/" + i.sStreamName + ".flv?" + i.newCFlvAntiCode + "&ratio=0" - return u - .replacingOccurrences(of: "&", with: "&") - .replacingOccurrences(of: "http://", with: "https://") - .replacingOccurrences(of: "https://tx.flv.huya.com/huyalive/", with: "https://tx.flv.huya.com/src/") - } - - - urlsBak = streamInfos.compactMap { i -> String? in - let u = i.sFlvUrl + "/" + i.sStreamName + ".flv?" + i.sFlvAntiCode - return huyaUrlFormatter(u.replacingOccurrences(of: "&", with: "&"))?.replacingOccurrences(of: "http://", with: "https://") - } - } - } - - init(object: MarshaledObject) throws { - data = try object.value(for: "data") - vMultiStreamInfo = try object.value(for: "vMultiStreamInfo") - } - + var data: [HuyaInfoData] + var vMultiStreamInfo: [StreamInfo] + + init(object: MarshaledObject) throws { + data = try object.value(for: "data") + vMultiStreamInfo = try object.value(for: "vMultiStreamInfo") + } + + func write(to yougetJson: YouGetJSON, uid: Int) -> YouGetJSON { + var yougetJson = yougetJson + + if let infoData = data.first { + yougetJson.title = infoData.liveInfo.roomName + + let urls = infoData.streamInfoList.map { + $0.url(uid) + } + + vMultiStreamInfo.forEach { + let rate = $0.iBitRate + + 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[$0.sDisplayName] = s + } + } + + return yougetJson + } + + struct StreamInfo: Unmarshaling { + var sDisplayName: String + var iBitRate: Int + var iHEVCBitRate: Int + + init(object: MarshaledObject) throws { + sDisplayName = try object.value(for: "sDisplayName") + iBitRate = try object.value(for: "iBitRate") + iHEVCBitRate = try object.value(for: "iHEVCBitRate") + } + } + + struct HuyaInfoData: Unmarshaling { + var liveInfo: GameLiveInfo + var streamInfoList: [GameStreamInfo] + + init(object: MarshaledObject) throws { + liveInfo = try object.value(for: "gameLiveInfo") + streamInfoList = try object.value(for: "gameStreamInfoList") + } + } + + struct GameLiveInfo: Unmarshaling { + + let roomName: String + let uid: Int + let isSecret: Int + let screenshot: String + let nick: String + let avatar180: String + + init(object: MarshaledObject) throws { + roomName = try object.value(for: "roomName") + uid = try object.value(for: "uid") + isSecret = try object.value(for: "isSecret") + screenshot = try object.value(for: "screenshot") + nick = try object.value(for: "nick") + avatar180 = try object.value(for: "avatar180") + } + } + + struct GameStreamInfo: Unmarshaling { + var sStreamName: String + var sFlvUrl: String + var sFlvUrlSuffix: String + var sFlvAntiCode: String + + init(object: MarshaledObject) throws { + sStreamName = try object.value(for: "sStreamName") + sFlvUrl = try object.value(for: "sFlvUrl") + sFlvUrlSuffix = try object.value(for: "sFlvUrlSuffix") + sFlvAntiCode = try object.value(for: "sFlvAntiCode") + } + + func url(_ uid: Int) -> String { + + func now() -> Int { + Int(Date().timeIntervalSince1970 * 1000) + } + + let seqid = uid + now() + let sid = now() + + guard let convertUid = rotUid(uid), + let wsSecret = wsSecret(sFlvAntiCode, convertUid: convertUid, seqid: seqid, streamName: sStreamName) else { return "" } + + let newAntiCode: String = { + var s = sFlvAntiCode.split(separator: "&") + .filter { + !$0.contains("fm=") && + !$0.contains("wsSecret=") + } + s.append("wsSecret=\(wsSecret)") + return s.joined(separator: "&") + }() + + + return sFlvUrl.replacingOccurrences(of: "http://", with: "https://") + + "/" + + sStreamName + + "." + + sFlvUrlSuffix + + "?" + + newAntiCode + + "&ver=1" + + "&seqid=\(seqid)" + + "&ratio=0" + + "&dMod=mseh-32" + + "&sdkPcdn=1_1" + + "&u=\(convertUid)" + + "&t=100" + + "&sv=2401310322" + + "&sdk_sid=\(sid)" + + "&https=1" +// + "&codec=av1" + } + + private func turnStr(_ e: Int, _ t: Int, _ i: Int) -> String { + var s = String(e, radix: t) + while s.count < i { + s = "0" + s + } + return s + } + + private func rotUid(_ t: Int) -> Int? { + let i = 8 + + let s = turnStr(t, 2, 64) + let si = s.index(s.startIndex, offsetBy: 32) + let a = s[s.startIndex.. 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 let fm = d["fm"]?.removingPercentEncoding, + let fmData = Data(base64Encoded: fm), + var u = String(data: fmData, encoding: .utf8), + let l = d["wsTime"] else { return nil } + + let s = "\(seqid)|huya_live|100".md5() + + 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() + } + } + + } + struct HuyaInfoM: Unmarshaling, LiveInfo { var title: String = "" @@ -416,50 +505,6 @@ fileprivate func huyaUrlFormatter(_ u: String) -> String? { } -fileprivate func huyaUrlFormatter2(_ u: String) -> String? { - guard var uc = URLComponents(string: u) else { - return nil - } - - uc.scheme = "https" - - if let fm = uc.queryItems?.first(where: { - $0.name == "fm" - })?.value { -// fm - - - - - } - - uc.queryItems?.removeAll { - $0.name == "fm" - } - - //Number((Date.now() % 1e10 * 1e3 + (1e3 * Math.random() | 0)) % 4294967295) - - let date = Int(Date().timeIntervalSince1970 * 1000) - - let uuid = (date % Int(1e10) + Int.random(in: 1...999)) % 4294967295 - - let uid = 1462391016094 - let seqid = date + uid - - let newItems: [URLQueryItem] = [ - .init(name: "seqid", value: "\(seqid)"), - .init(name: "ver", value: "1"), - .init(name: "uid", value: "\(uid)"), - .init(name: "uuid", value: "\(uuid)"), - .init(name: "sv", value: "2110131611"), - ] - - uc.queryItems?.append(contentsOf: newItems) - - - return uc.url?.absoluteString -} - struct HuyaVideoSelector: VideoSelector { var id: String var coverUrl: URL? From 2ba36ae7658ba9f08b05b70d41f2af8a8270aa6a Mon Sep 17 00:00:00 2001 From: xjbeta Date: Sun, 11 Feb 2024 13:43:34 +0800 Subject: [PATCH 3/4] Bump Version to 0.7.19. --- IINA+.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IINA+.xcodeproj/project.pbxproj b/IINA+.xcodeproj/project.pbxproj index 36454ef6..5baf7046 100644 --- a/IINA+.xcodeproj/project.pbxproj +++ b/IINA+.xcodeproj/project.pbxproj @@ -1029,7 +1029,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.7.18; + MARKETING_VERSION = 0.7.19; PRODUCT_BUNDLE_IDENTIFIER = "com.xjbeta.iina-plus"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1056,7 +1056,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.7.18; + MARKETING_VERSION = 0.7.19; PRODUCT_BUNDLE_IDENTIFIER = "com.xjbeta.iina-plus"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From f275862c1ee7e49c39b0055cbcb8dcf69cf91a85 Mon Sep 17 00:00:00 2001 From: xjbeta Date: Sun, 11 Feb 2024 14:22:07 +0800 Subject: [PATCH 4/4] huya url --- IINA+.xcodeproj/project.pbxproj | 4 + IINA+/Utils/VideoDecoder/Huya.swift | 250 ++++++------------------- IINA+/Utils/VideoDecoder/HuyaUrl.swift | 112 +++++++++++ 3 files changed, 169 insertions(+), 197 deletions(-) create mode 100644 IINA+/Utils/VideoDecoder/HuyaUrl.swift diff --git a/IINA+.xcodeproj/project.pbxproj b/IINA+.xcodeproj/project.pbxproj index 5baf7046..9d4a4b58 100644 --- a/IINA+.xcodeproj/project.pbxproj +++ b/IINA+.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 0199489E2742852E00C71708 /* SupportSites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0199489D2742852E00C71708 /* SupportSites.swift */; }; 019948BF2743DF3600C71708 /* ObjMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019948BE2743DF3600C71708 /* ObjMenuItem.swift */; }; 019F584F2AF1450800235BAE /* JSPlayerURLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019F584E2AF1450800235BAE /* JSPlayerURLSchemeHandler.swift */; }; + 01A183DB2B789728006FA874 /* HuyaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A183DA2B789728006FA874 /* HuyaUrl.swift */; }; 01AEC8AB20EDFD01001406E8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AEC8AA20EDFD01001406E8 /* AppDelegate.swift */; }; 01AEC8AD20EDFD01001406E8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AEC8AC20EDFD01001406E8 /* MainViewController.swift */; }; 01AEC8AF20EDFD02001406E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01AEC8AE20EDFD02001406E8 /* Assets.xcassets */; }; @@ -248,6 +249,7 @@ 019948BE2743DF3600C71708 /* ObjMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjMenuItem.swift; sourceTree = ""; }; 019AFB5F27380DEA00B4E458 /* Bookmark v0.3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Bookmark v0.3.xcdatamodel"; sourceTree = ""; }; 019F584E2AF1450800235BAE /* JSPlayerURLSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSPlayerURLSchemeHandler.swift; sourceTree = ""; }; + 01A183DA2B789728006FA874 /* HuyaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuyaUrl.swift; sourceTree = ""; }; 01AEC8A720EDFD01001406E8 /* IINA+.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "IINA+.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 01AEC8AA20EDFD01001406E8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 01AEC8AC20EDFD01001406E8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; @@ -478,6 +480,7 @@ 01892C7727C0CDA400494AFD /* DouYin.swift */, 018F4F652812C3EB0045B67C /* Douyu.swift */, 018F4F6028126FFD0045B67C /* Huya.swift */, + 01A183DA2B789728006FA874 /* HuyaUrl.swift */, 018F4F6B2817FFF30045B67C /* BiliLive.swift */, 01683DDA2118905D0016A886 /* Bilibili.swift */, 018739ED2852135200156F3F /* QQLive.swift */, @@ -765,6 +768,7 @@ files = ( 0189533F279FBC470011BAAC /* ColorPickButton.swift in Sources */, 01DCD2F9259710D800D286C8 /* BilibiliDMMessage.swift in Sources */, + 01A183DB2B789728006FA874 /* HuyaUrl.swift in Sources */, 010F0F1920FE4F0900F33553 /* DataModel.xcdatamodeld in Sources */, 0120934227A40869002C7FD3 /* JSPlayerWindowController.swift in Sources */, 013CE8F62185D992000271FB /* HttpServer.swift in Sources */, diff --git a/IINA+/Utils/VideoDecoder/Huya.swift b/IINA+/Utils/VideoDecoder/Huya.swift index bd3f0a32..4afdc96f 100644 --- a/IINA+/Utils/VideoDecoder/Huya.swift +++ b/IINA+/Utils/VideoDecoder/Huya.swift @@ -22,16 +22,20 @@ class Huya: NSObject, SupportSiteProtocol { return Session(configuration: configuration) }() - private var huyaUid = 0 + private let huyaUid = Int.random(in: Int(1e12).. Promise { - getHuyaInfoM(url).map { - $0.0 - } + getHuyaInfoM(url).map { + $0 + } } func decodeUrl(_ url: String) -> Promise { - getHuyaInfo(url) + getHuyaInfoM(url).map { + var yougetJson = YouGetJSON(rawUrl: url) + yougetJson.title = $0.title + return $0.write(to: yougetJson, uid: self.huyaUid) + } } // MARK: - Huya @@ -66,10 +70,7 @@ class Huya: NSObject, SupportSiteProtocol { func getHuyaInfo(_ url: String) -> Promise { AF.request(url).responseString().map { - if self.huyaUid == 0 { - self.huyaUid = Int.random(in: Int(1e12).. Promise<(HuyaInfoM, [(String, Stream)])> { + func getHuyaInfoM(_ url: String) -> Promise { pSession.request(url).responseString().map { guard let jsonData = $0.string.subString(from: "").data(using: .utf8) else { @@ -111,7 +112,7 @@ class Huya: NSObject, SupportSiteProtocol { let info: HuyaInfoM = try HuyaInfoM(object: jsonObj) - return (info, info.urls) + return info } } } @@ -236,103 +237,9 @@ struct HuyaStream: Unmarshaling { } func url(_ uid: Int) -> String { - - func now() -> Int { - Int(Date().timeIntervalSince1970 * 1000) - } - - let seqid = uid + now() - let sid = now() - - guard let convertUid = rotUid(uid), - let wsSecret = wsSecret(sFlvAntiCode, convertUid: convertUid, seqid: seqid, streamName: sStreamName) else { return "" } - - let newAntiCode: String = { - var s = sFlvAntiCode.split(separator: "&") - .filter { - !$0.contains("fm=") && - !$0.contains("wsSecret=") - } - s.append("wsSecret=\(wsSecret)") - return s.joined(separator: "&") - }() - - - return sFlvUrl.replacingOccurrences(of: "http://", with: "https://") - + "/" - + sStreamName - + "." - + sFlvUrlSuffix - + "?" - + newAntiCode - + "&ver=1" - + "&seqid=\(seqid)" - + "&ratio=0" - + "&dMod=mseh-32" - + "&sdkPcdn=1_1" - + "&u=\(convertUid)" - + "&t=100" - + "&sv=2401310322" - + "&sdk_sid=\(sid)" - + "&https=1" -// + "&codec=av1" - } - - private func turnStr(_ e: Int, _ t: Int, _ i: Int) -> String { - var s = String(e, radix: t) - while s.count < i { - s = "0" + s - } - return s - } - - private func rotUid(_ t: Int) -> Int? { - let i = 8 - - let s = turnStr(t, 2, 64) - let si = s.index(s.startIndex, offsetBy: 32) - let a = s[s.startIndex.. 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 let fm = d["fm"]?.removingPercentEncoding, - let fmData = Data(base64Encoded: fm), - var u = String(data: fmData, encoding: .utf8), - let l = d["wsTime"] else { return nil } - - let s = "\(seqid)|huya_live|100".md5() - - 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() + HuyaUrl.format(uid, sStreamName: sStreamName, sFlvUrl: sFlvUrl, sFlvUrlSuffix: sFlvUrlSuffix, sFlvAntiCode: sFlvAntiCode) } } - - } @@ -348,9 +255,10 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { var isSeeTogetherRoom = false - - var urls: [(String, Stream)] - + + let defaultCDN: String + let streamInfos: [StreamInfo] + let bitRateInfos: [BitRateInfo] struct StreamInfo: Unmarshaling { let sFlvUrl: String @@ -360,23 +268,6 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { let sCdnType: String - var url: String? { - get { - let u = sFlvUrl - + "/" - + sStreamName - + "." - + sFlvUrlSuffix - + "?" - + sFlvAntiCode - -// return formatURL(u) - - - return huyaUrlFormatter(u) - } - } - init(object: MarshaledObject) throws { sFlvUrl = try object.value(for: "sFlvUrl") sStreamName = try object.value(for: "sStreamName") @@ -385,8 +276,6 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { sCdnType = try object.value(for: "sCdnType") } - - } struct BitRateInfo: Unmarshaling { @@ -400,8 +289,6 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { } - - init(object: MarshaledObject) throws { name = try object.value(for: "roomInfo.tProfileInfo.sNick") @@ -429,79 +316,48 @@ struct HuyaInfoM: Unmarshaling, LiveInfo { cover = try object.value(for: "roomInfo.tLiveInfo.sScreenshot") - let defaultCDN: String = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.sDefaultLiveStreamLine") + defaultCDN = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.sDefaultLiveStreamLine") - let streamInfos: [StreamInfo] = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.vStreamInfo.value") + streamInfos = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.vStreamInfo.value") - let bitRateInfos: [BitRateInfo] = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.vBitRateInfo.value") - - let urls = streamInfos.sorted { i1, i2 -> Bool in + bitRateInfos = try object.value(for: "roomInfo.tLiveInfo.tLiveStreamInfo.vBitRateInfo.value") + } + + func write(to yougetJson: YouGetJSON, uid: Int) -> YouGetJSON { + var yougetJson = yougetJson + + let urls = streamInfos.sorted { i1, i2 -> Bool in i1.sCdnType == defaultCDN }.sorted { i1, i2 -> Bool in !i1.sFlvUrl.contains("txdirect.flv.huya.com") }.compactMap { - $0.url - } - - guard urls.count > 0 else { - self.urls = [] - return - } - - self.urls = bitRateInfos.map { - ($0.sDisplayName, $0.iBitRate) - }.map { (name, rate) -> (String, Stream) in - var us = urls.map { - $0 + "&ratio=\(rate)" - } - var s = Stream(url: us.removeFirst()) - s.src = us - s.quality = rate == 0 ? 9999999 : rate - return (name, s) - } - } -} - -fileprivate func huyaUrlFormatter(_ u: String) -> String? { - let ib = u.split(separator: "?").map(String.init) - guard ib.count == 2 else { return nil } - let i = ib[0] - let b = ib[1] - guard let s = i.components(separatedBy: "/").last?.subString(to: ".") else { return nil } - let d = b.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 - } - - let n = "\(Int(Date().timeIntervalSince1970 * 10000000))" - - guard let fm = d["fm"]?.removingPercentEncoding, - let fmData = Data(base64Encoded: fm), - var u = String(data: fmData, encoding: .utf8), - let l = d["wsTime"] else { return nil } - - u = u.replacingOccurrences(of: "$0", with: "0") - u = u.replacingOccurrences(of: "$1", with: s) - u = u.replacingOccurrences(of: "$2", with: n) - u = u.replacingOccurrences(of: "$3", with: l) - - let m = u.md5() - - let y = b.split(separator: "&").map(String.init).filter { - $0.contains("txyp=") || - $0.contains("fs=") || - $0.contains("sphdcdn=") || - $0.contains("sphdDC=") || - $0.contains("sphd=") - }.joined(separator: "&") - - let url = "\(i)?wsSecret=\(m)&wsTime=\(l)&seqid=\(n)&\(y)&ratio=0&u=0&t=100&sv=" - - .replacingOccurrences(of: "http://", with: "https://") - return url + 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 + } } diff --git a/IINA+/Utils/VideoDecoder/HuyaUrl.swift b/IINA+/Utils/VideoDecoder/HuyaUrl.swift new file mode 100644 index 00000000..c71ba7cf --- /dev/null +++ b/IINA+/Utils/VideoDecoder/HuyaUrl.swift @@ -0,0 +1,112 @@ +// +// HuyaUrl.swift +// IINA+ +// +// Created by xjbeta on 2024/2/11. +// Copyright © 2024 xjbeta. All rights reserved. +// + +import Cocoa + +class HuyaUrl: NSObject { + static func format(_ uid: Int, + sStreamName: String, + sFlvUrl: String, + sFlvUrlSuffix: String, + sFlvAntiCode: String) -> String { + + func now() -> Int { + Int(Date().timeIntervalSince1970 * 1000) + } + + let seqid = uid + now() + let sid = now() + + guard let convertUid = rotUid(uid), + let wsSecret = wsSecret(sFlvAntiCode, convertUid: convertUid, seqid: seqid, streamName: sStreamName) else { return "" } + + let newAntiCode: String = { + var s = sFlvAntiCode.split(separator: "&") + .filter { + !$0.contains("fm=") && + !$0.contains("wsSecret=") + } + s.append("wsSecret=\(wsSecret)") + return s.joined(separator: "&") + }() + + + return sFlvUrl.replacingOccurrences(of: "http://", with: "https://") + + "/" + + sStreamName + + "." + + sFlvUrlSuffix + + "?" + + newAntiCode + + "&ver=1" + + "&seqid=\(seqid)" + + "&ratio=0" + + "&dMod=mseh-32" + + "&sdkPcdn=1_1" + + "&u=\(convertUid)" + + "&t=100" + + "&sv=2401310322" + + "&sdk_sid=\(sid)" + + "&https=1" +// + "&codec=av1" + } + + private static func turnStr(_ e: Int, _ t: Int, _ i: Int) -> String { + var s = String(e, radix: t) + while s.count < i { + s = "0" + s + } + return s + } + + private static func rotUid(_ t: Int) -> Int? { + let i = 8 + + let s = turnStr(t, 2, 64) + let si = s.index(s.startIndex, offsetBy: 32) + let a = s[s.startIndex.. 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 let fm = d["fm"]?.removingPercentEncoding, + let fmData = Data(base64Encoded: fm), + var u = String(data: fmData, encoding: .utf8), + let l = d["wsTime"], + let ctype = d["ctype"] else { return nil } + + let s = "\(seqid)|\(ctype)|100".md5() + + 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() + } +}