diff --git a/assets/app_version.json b/assets/app_version.json index 68643e01..3c9fff90 100644 --- a/assets/app_version.json +++ b/assets/app_version.json @@ -1,7 +1,7 @@ { - "version": "1.6.0", - "version_num": 10600, - "version_desc": "- 修复MacOS打开同步失败 #351\n- 修复Windows返回时亮度调节至最高 #332\n- 修复虎牙分类加载失败 #366\n- 修复虎牙无法播放问题 #409\n- 修复链接跳转时虚拟导航条显示错误 #373\n- 修复播放器锁定时依旧触发长按事件\n- 支持调整弹幕字重 #372\n- 支持日志记录\n- 支持抖音手机端分享链接解析 #376\n- 支持复制直播间链接\n- 支持滑动删除历史记录 #231\n- 支持自定义视频输出驱动\n- PC页面增加刷新按钮\n- 优化桌面小窗播放\n- 优化关注列表加载\n- 优化直播间加载错误的处理\n- 统一全平台图标,安卓支持主题图标 #140 #112\n- 尝试使用WebView实现抖音搜索 #379", + "version": "1.6.3", + "version_num": 10603, + "version_desc": "- 修复抖音无法读取直播状态 #431\n- 修复抖音无法读取原画 #429\n- 优化直播状态读取\n-斗鱼默认不使用PCDN链接", "prerelease":false, "download_url": "https://github.com/xiaoyaocz/dart_simple_live/releases" } \ No newline at end of file diff --git a/assets/tv_app_version.json b/assets/tv_app_version.json index 6edbdec2..d9136f8d 100644 --- a/assets/tv_app_version.json +++ b/assets/tv_app_version.json @@ -1,7 +1,7 @@ { - "version": "1.0.8", - "version_num": 10008, - "version_desc": "- 修复虎牙播放问题\n- TV增加Banner图标\n- 优化使用体验", + "version": "1.1.0", + "version_num": 10100, + "version_desc": "- 修复抖音无法读取原画\n- 优化直播状态读取\n-斗鱼默认不使用PCDN链接", "prerelease":true, "download_url": "https://github.com/xiaoyaocz/dart_simple_live/releases" } \ No newline at end of file diff --git a/simple_live_app/lib/modules/live_room/live_room_controller.dart b/simple_live_app/lib/modules/live_room/live_room_controller.dart index ea7e6621..e7e85119 100644 --- a/simple_live_app/lib/modules/live_room/live_room_controller.dart +++ b/simple_live_app/lib/modules/live_room/live_room_controller.dart @@ -281,7 +281,9 @@ class LiveRoomController extends PlayerController with WidgetsBindingObserver { detail.value = await site.liveSite.getRoomDetail(roomId: roomId); if (site.id == Constant.kDouyin) { - // 如果是抖音,且收藏的是Rid,需要转换roomID + // 1.6.0之前收藏的WebRid + // 1.6.0收藏的RoomID + // 1.6.0之后改回WebRid if (detail.value!.roomId != roomId) { var oldId = roomId; rxRoomId.value = detail.value!.roomId; diff --git a/simple_live_app/lib/modules/live_room/live_room_page.dart b/simple_live_app/lib/modules/live_room/live_room_page.dart index e87e0151..e73131a4 100644 --- a/simple_live_app/lib/modules/live_room/live_room_page.dart +++ b/simple_live_app/lib/modules/live_room/live_room_page.dart @@ -847,6 +847,15 @@ class LiveRoomPage extends GetView { controller.openNaviteAPP(); }, ), + ListTile( + leading: const Icon(Icons.info_outline_rounded), + title: const Text("播放信息"), + trailing: const Icon(Icons.chevron_right), + onTap: () { + Get.back(); + controller.showDebugInfo(); + }, + ), ], ), ), diff --git a/simple_live_app/lib/modules/live_room/player/player_controller.dart b/simple_live_app/lib/modules/live_room/player/player_controller.dart index 63b48fb1..7e53417d 100644 --- a/simple_live_app/lib/modules/live_room/player/player_controller.dart +++ b/simple_live_app/lib/modules/live_room/player/player_controller.dart @@ -282,11 +282,19 @@ mixin PlayerSystemMixin on PlayerMixin, PlayerStateMixin, PlayerDanmakuMixin { //danmakuController?.clear(); } + Size? _lastWindowSize; + Offset? _lastWindowPosition; + ///小窗模式() - void enterSmallWindow() { + void enterSmallWindow() async { if (!(Platform.isAndroid || Platform.isIOS)) { fullScreenState.value = true; smallWindowState.value = true; + + // 读取窗口大小 + _lastWindowSize = await windowManager.getSize(); + _lastWindowPosition = await windowManager.getPosition(); + windowManager.setTitleBarStyle(TitleBarStyle.hidden); // 获取视频窗口大小 var width = player.state.width ?? 16; @@ -311,9 +319,10 @@ mixin PlayerSystemMixin on PlayerMixin, PlayerStateMixin, PlayerDanmakuMixin { fullScreenState.value = false; smallWindowState.value = false; windowManager.setTitleBarStyle(TitleBarStyle.normal); - windowManager.setSize(const Size(1280, 720)); + windowManager.setSize(_lastWindowSize!); + windowManager.setPosition(_lastWindowPosition!); windowManager.setAlwaysOnTop(false); - windowManager.setAlignment(Alignment.center); + //windowManager.setAlignment(Alignment.center); } } @@ -690,24 +699,10 @@ class PlayerController extends BaseController void mediaError(String error) {} void showDebugInfo() { - if (lockControlsState.value && fullScreenState.value) { - return; - } Utils.showBottomSheet( title: "播放信息", child: ListView( children: [ - ListTile( - title: const Text("Media"), - subtitle: Text(player.state.playlist.toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: "Media\n${player.state.playlist}", - ), - ); - }, - ), ListTile( title: const Text("Resolution"), subtitle: Text('${player.state.width}x${player.state.height}'), @@ -742,6 +737,17 @@ class PlayerController extends BaseController ); }, ), + ListTile( + title: const Text("Media"), + subtitle: Text(player.state.playlist.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: "Media\n${player.state.playlist}", + ), + ); + }, + ), ListTile( title: const Text("AudioTrack"), subtitle: Text(player.state.track.audio.toString()), @@ -775,6 +781,17 @@ class PlayerController extends BaseController ); }, ), + ListTile( + title: const Text("Volume"), + subtitle: Text(player.state.volume.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: "Volume\n${player.state.volume}", + ), + ); + }, + ), ], ), ); diff --git a/simple_live_app/lib/modules/user/user_page.dart b/simple_live_app/lib/modules/user/user_page.dart index 9d8ef331..64b1c77f 100644 --- a/simple_live_app/lib/modules/user/user_page.dart +++ b/simple_live_app/lib/modules/user/user_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -193,6 +194,18 @@ class UserPage extends StatelessWidget { Get.toNamed(RoutePath.kSettingsOther); }, ), + if (kDebugMode) + ListTile( + leading: const Icon(Remix.apps_line), + title: const Text("测试"), + trailing: const Icon( + Icons.chevron_right, + color: Colors.grey, + ), + onTap: () { + Get.toNamed(RoutePath.kTest); + }, + ), ], ), Divider( diff --git a/simple_live_app/lib/routes/route_path.dart b/simple_live_app/lib/routes/route_path.dart index 5a06d958..a961e052 100644 --- a/simple_live_app/lib/routes/route_path.dart +++ b/simple_live_app/lib/routes/route_path.dart @@ -62,4 +62,7 @@ class RoutePath { /// 同步设备 static const kSyncDevice = "/sync/device"; + + /// 测试页面 + static const kTest = "/test"; } diff --git a/simple_live_app/pubspec.yaml b/simple_live_app/pubspec.yaml index ee44d344..eab097f2 100644 --- a/simple_live_app/pubspec.yaml +++ b/simple_live_app/pubspec.yaml @@ -1,5 +1,5 @@ name: simple_live_app -version: 1.6.0+10600 +version: 1.6.3+10603 publish_to: none description: "Simple Live APP" environment: diff --git a/simple_live_core/example/simple_live_core_example.dart b/simple_live_core/example/simple_live_core_example.dart index 626809fa..28332974 100644 --- a/simple_live_core/example/simple_live_core_example.dart +++ b/simple_live_core/example/simple_live_core_example.dart @@ -3,7 +3,7 @@ import 'package:simple_live_core/simple_live_core.dart'; void main() async { CoreLog.enableLog = true; CoreLog.requestLogType = RequestLogType.short; - LiveSite site = DouyinSite(); + LiveSite site = DouyuSite(); var danmaku = site.getDanmaku(); danmaku.onMessage = (event) { if (event.type == LiveMessageType.chat) { @@ -20,13 +20,14 @@ void main() async { }; //var categores = await site.getCategores(); //print(categores.length); - var detail = await site.getRoomDetail(roomId: "7375009979071236915"); - // var playQualites = await site.getPlayQualites(detail: detail); - // var playUrls = - // await site.getPlayUrls(detail: detail, quality: playQualites.first); - // for (var element in playUrls) { - // print(element); - // } + var detail = await site.getRoomDetail(roomId: "687423"); + var playQualites = await site.getPlayQualites(detail: detail); + print(playQualites); + var playUrls = + await site.getPlayUrls(detail: detail, quality: playQualites.first); + for (var element in playUrls) { + print(element); + } //print(detail); danmaku.start(detail.danmakuData); diff --git a/simple_live_core/lib/src/douyin_site.dart b/simple_live_core/lib/src/douyin_site.dart index 80979165..2b15b9c0 100644 --- a/simple_live_core/lib/src/douyin_site.dart +++ b/simple_live_core/lib/src/douyin_site.dart @@ -174,32 +174,53 @@ class DouyinSite implements LiveSite { @override Future getRoomDetail({required String roomId}) async { - // 检查roomId是否为webRid - if (roomId.length < 15) { - return await getRoomDetailByWebRid(roomId); + // 有两种roomId,一种是webRid,一种是roomId + // roomId是一次性的,用户每次重新开播都会生成一个新的roomId + // roomId一般长度为19位,例如:7376429659866598196 + // webRid是固定的,用户每次开播都是同一个webRid + // webRid一般长度为11-12位,例如:416144012050 + // 这里简单进行判断,如果roomId长度小于15,则认为是webRid + if (roomId.length <= 16) { + var webRid = roomId; + return await getRoomDetailByWebRid(webRid); } + return await getRoomDetailByRoomId(roomId); + } + + /// 通过roomId获取直播间信息 + /// - [roomId] 直播间ID + /// - 返回直播间信息 + Future getRoomDetailByRoomId(String roomId) async { // 读取房间信息 - var roomInfo = await _getRoomInfo(roomId); + var roomData = await _getRoomDataByRoomId(roomId); // 通过房间信息获取WebRid - var webRid = roomInfo["data"]["room"]["owner"]["web_rid"].toString(); + var webRid = roomData["data"]["room"]["owner"]["web_rid"].toString(); // 读取用户唯一ID,用于弹幕连接 // 似乎这个参数不是必须的,先随机生成一个 //var userUniqueId = await _getUserUniqueId(webRid); var userUniqueId = generateRandomNumber(12).toString(); - var room = roomInfo["data"]["room"]; + var room = roomData["data"]["room"]; var owner = room["owner"]; - var roomStatus = (asT(room["status"]) ?? 0) == 2; + var status = asT(room["status"]) ?? 0; + // roomId是一次性的,用户每次重新开播都会生成一个新的roomId + // 所以如果roomId对应的直播间状态不是直播中,就通过webRid获取直播间信息 + if (status == 4) { + var result = await getRoomDetailByWebRid(webRid); + return result; + } + + var roomStatus = status == 2; // 主要是为了获取cookie,用于弹幕websocket连接 var headers = await getRequestHeaders(); return LiveRoomDetail( - roomId: roomId, + roomId: webRid, title: room["title"].toString(), cover: roomStatus ? room["cover"]["url_list"][0].toString() : "", userName: owner["nickname"].toString(), @@ -221,35 +242,100 @@ class DouyinSite implements LiveSite { ); } - /// 通过webRid获取直播间信息,用于兼容旧版本 + /// 通过WebRid获取直播间信息 /// - [webRid] 直播间RID + /// - 返回直播间信息 Future getRoomDetailByWebRid(String webRid) async { - var webRoomInfo = await _getWebRoomInfo(webRid); - var roomId = - webRoomInfo["roomStore"]["roomInfo"]["room"]["id_str"].toString(); + try { + var result = await _getRoomDetailByWebRidApi(webRid); + return result; + } catch (e) { + CoreLog.error(e); + } + return await _getRoomDetailByWebRidHtml(webRid); + } + + /// 通过WebRid访问直播间API,从API中获取直播间信息 + /// - [webRid] 直播间RID + /// - 返回直播间信息 + Future _getRoomDetailByWebRidApi(String webRid) async { + // 读取房间信息 + var data = await _getRoomDataByApi(webRid); + var roomData = data["data"][0]; + var userData = data["user"]; + var roomId = roomData["id_str"].toString(); + + // 读取用户唯一ID,用于弹幕连接 + // 似乎这个参数不是必须的,先随机生成一个 + //var userUniqueId = await _getUserUniqueId(webRid); + var userUniqueId = generateRandomNumber(12).toString(); + + var owner = roomData["owner"]; + + var roomStatus = (asT(roomData["status"]) ?? 0) == 2; + + // 主要是为了获取cookie,用于弹幕websocket连接 + var headers = await getRequestHeaders(); + return LiveRoomDetail( + roomId: webRid, + title: roomData["title"].toString(), + cover: roomStatus ? roomData["cover"]["url_list"][0].toString() : "", + userName: roomStatus + ? owner["nickname"].toString() + : userData["nickname"].toString(), + userAvatar: roomStatus + ? owner["avatar_thumb"]["url_list"][0].toString() + : userData["avatar_thumb"]["url_list"][0].toString(), + online: roomStatus + ? asT(roomData["room_view_stats"]["display_value"]) ?? 0 + : 0, + status: roomStatus, + url: "https://live.douyin.com/$webRid", + introduction: owner?["signature"]?.toString() ?? "", + notice: "", + danmakuData: DouyinDanmakuArgs( + webRid: webRid, + roomId: roomId, + userId: userUniqueId, + cookie: headers["cookie"], + ), + data: roomStatus ? roomData["stream_url"] : {}, + ); + } + + /// 通过WebRid访问直播间网页,从网页HTML中获取直播间信息 + /// - [webRid] 直播间RID + /// - 返回直播间信息 + Future _getRoomDetailByWebRidHtml(String webRid) async { + var roomData = await _getRoomDataByHtml(webRid); + var roomId = roomData["roomStore"]["roomInfo"]["room"]["id_str"].toString(); var userUniqueId = - webRoomInfo["userStore"]["odin"]["user_unique_id"].toString(); + roomData["userStore"]["odin"]["user_unique_id"].toString(); - var roomInfo = await _getRoomInfo(roomId); - var room = roomInfo["data"]["room"]; + var room = roomData["roomStore"]["roomInfo"]["room"]; var owner = room["owner"]; + var anchor = roomData["roomStore"]["roomInfo"]["anchor"]; var roomStatus = (asT(room["status"]) ?? 0) == 2; // 主要是为了获取cookie,用于弹幕websocket连接 var headers = await getRequestHeaders(); return LiveRoomDetail( - roomId: roomId, + roomId: webRid, title: room["title"].toString(), cover: roomStatus ? room["cover"]["url_list"][0].toString() : "", - userName: owner["nickname"].toString(), - userAvatar: owner["avatar_thumb"]["url_list"][0].toString(), + userName: roomStatus + ? owner["nickname"].toString() + : anchor["nickname"].toString(), + userAvatar: roomStatus + ? owner["avatar_thumb"]["url_list"][0].toString() + : anchor["avatar_thumb"]["url_list"][0].toString(), online: roomStatus ? asT(room["room_view_stats"]["display_value"]) ?? 0 : 0, status: roomStatus, url: "https://live.douyin.com/$webRid", - introduction: owner["signature"].toString(), + introduction: owner?["signature"]?.toString() ?? "", notice: "", danmakuData: DouyinDanmakuArgs( webRid: webRid, @@ -257,15 +343,15 @@ class DouyinSite implements LiveSite { userId: userUniqueId, cookie: headers["cookie"], ), - data: room["stream_url"], + data: roomStatus ? room["stream_url"] : {}, ); } - /// 读取用户名的唯一ID + /// 读取用户的唯一ID /// - [webRid] 直播间RID // ignore: unused_element Future _getUserUniqueId(String webRid) async { - var webInfo = await _getWebRoomInfo(webRid); + var webInfo = await _getRoomDataByHtml(webRid); return webInfo["userStore"]["odin"]["user_unique_id"].toString(); } @@ -285,12 +371,16 @@ class DouyinSite implements LiveSite { if (cookie.contains("__ac_nonce")) { dyCookie += "$cookie;"; } + if (cookie.contains("msToken")) { + dyCookie += "$cookie;"; + } }); return dyCookie; } /// 通过webRid获取直播间Web信息 - Future _getWebRoomInfo(String webRid) async { + /// - [webRid] 直播间RID + Future _getRoomDataByHtml(String webRid) async { var dyCookie = await _getWebCookie(webRid); var result = await HttpClient.instance.getText( "https://live.douyin.com/$webRid", @@ -313,13 +403,43 @@ class DouyinSite implements LiveSite { .replaceAll(r"\\", r"\") .replaceAll(']\\n', ""); var renderDataJson = json.decode(str); - return renderDataJson["state"]; } + /// 通过webRid获取直播间Web信息 + /// - [webRid] 直播间RID + Future _getRoomDataByApi(String webRid) async { + var requestHeader = await getRequestHeaders(); + var result = await HttpClient.instance.getJson( + "https://live.douyin.com/webcast/room/web/enter/", + queryParameters: { + "aid": 6383, + "app_name": "douyin_web", + "live_id": 1, + "device_platform": "web", + "enter_from": "web_live", + "web_rid": webRid, + "room_id_str": "", + "enter_source": "", + "Room-Enter-User-Login-Ab": 0, + "is_need_double_stream": false, + "cookie_enabled": true, + "screen_width": 1980, + "screen_height": 1080, + "browser_language": "zh-CN", + "browser_platform": "Win32", + "browser_name": "Edge", + "browser_version": "125.0.0.0" + }, + header: requestHeader, + ); + + return result["data"]; + } + /// 通过roomId获取直播间信息 /// - [roomId] 直播间ID - Future _getRoomInfo(String roomId) async { + Future _getRoomDataByRoomId(String roomId) async { var result = await HttpClient.instance.getJson( 'https://webcast.amemv.com/webcast/room/reflow/info/', queryParameters: { @@ -328,7 +448,7 @@ class DouyinSite implements LiveSite { "room_id": roomId, "sec_user_id": "", "version_code": "99.99.99", - "app_id": 1128, + "app_id": 6383, }, header: await getRequestHeaders(), ); @@ -339,21 +459,68 @@ class DouyinSite implements LiveSite { Future> getPlayQualites( {required LiveRoomDetail detail}) async { List qualities = []; - var qualityData = json.decode( - detail.data["live_core_sdk_data"]["pull_data"]["stream_data"])["data"]; + var qulityList = detail.data["live_core_sdk_data"]["pull_data"]["options"]["qualities"]; - for (var quality in qulityList) { - var qualityItem = LivePlayQuality( - quality: quality["name"], - sort: quality["level"], - data: [ - qualityData[quality["sdk_key"]]["main"]["flv"].toString(), - qualityData[quality["sdk_key"]]["main"]["hls"].toString(), - ], - ); - qualities.add(qualityItem); + var streamData = detail.data["live_core_sdk_data"]["pull_data"] + ["stream_data"] + .toString(); + + if (!streamData.startsWith('{')) { + var flvList = + (detail.data["flv_pull_url"] as Map).values.cast().toList(); + var hlsList = (detail.data["hls_pull_url_map"] as Map) + .values + .cast() + .toList(); + for (var quality in qulityList) { + int level = quality["level"]; + List urls = []; + var flvIndex = flvList.length - level; + if (flvIndex >= 0 && flvIndex < flvList.length) { + urls.add(flvList[flvIndex]); + } + var hlsIndex = hlsList.length - level; + if (hlsIndex >= 0 && hlsIndex < hlsList.length) { + urls.add(hlsList[hlsIndex]); + } + var qualityItem = LivePlayQuality( + quality: quality["name"], + sort: level, + data: urls, + ); + if (urls.isNotEmpty) { + qualities.add(qualityItem); + } + } + } else { + var qualityData = json.decode(streamData)["data"] as Map; + for (var quality in qulityList) { + List urls = []; + var flvUrl = + qualityData[quality["sdk_key"]]?["main"]?["flv"]?.toString(); + + if (flvUrl != null && flvUrl.isNotEmpty) { + urls.add(flvUrl); + } + var hlsUrl = + qualityData[quality["sdk_key"]]?["main"]?["hls"]?.toString(); + if (hlsUrl != null && hlsUrl.isNotEmpty) { + urls.add(hlsUrl); + } + var qualityItem = LivePlayQuality( + quality: quality["name"], + sort: quality["level"], + data: urls, + ); + if (urls.isNotEmpty) { + qualities.add(qualityItem); + } + } } + // var qualityData = json.decode( + // detail.data["live_core_sdk_data"]["pull_data"]["stream_data"])["data"]; + qualities.sort((a, b) => b.sort.compareTo(a.sort)); return qualities; } @@ -457,12 +624,8 @@ class DouyinSite implements LiveSite { @override Future getLiveStatus({required String roomId}) async { - if (roomId.length < 15) { - var result = await _getWebRoomInfo(roomId); - return result["roomStore"]["roomInfo"]["room"]["status"] == 2; - } - var result = await _getRoomInfo(roomId); - return (asT(result["data"]["room"]["status"]) ?? 0) == 2; + var result = await getRoomDetail(roomId: roomId); + return result.status; } @override diff --git a/simple_live_core/lib/src/douyu_site.dart b/simple_live_core/lib/src/douyu_site.dart index 2ead73c0..0d23d3c3 100644 --- a/simple_live_core/lib/src/douyu_site.dart +++ b/simple_live_core/lib/src/douyu_site.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -104,6 +105,17 @@ class DouyuSite implements LiveSite { for (var item in result["data"]["cdnsWithName"]) { cdns.add(item["cdn"].toString()); } + + // 如果cdn以scdn开头,将其放到最后 + cdns.sort((a, b) { + if (a.startsWith("scdn") && !b.startsWith("scdn")) { + return 1; + } else if (!a.startsWith("scdn") && b.startsWith("scdn")) { + return -1; + } + return 0; + }); + for (var item in result["data"]["multirates"]) { qualities.add(LivePlayQuality( quality: item["name"].toString(), @@ -174,20 +186,7 @@ class DouyuSite implements LiveSite { @override Future getRoomDetail({required String roomId}) async { - var result = await HttpClient.instance.getJson( - "https://www.douyu.com/betard/$roomId", - queryParameters: {}, - header: { - 'referer': 'https://www.douyu.com/$roomId', - 'user-agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.43', - }); - Map roomInfo; - if (result is String) { - roomInfo = json.decode(result)["room"]; - } else { - roomInfo = result["room"]; - } + Map roomInfo = await _getRoomInfo(roomId); var jsEncResult = await HttpClient.instance.getText( "https://www.douyu.com/swf_api/homeH5Enc?rids=$roomId", @@ -252,6 +251,24 @@ class DouyuSite implements LiveSite { return LiveSearchRoomResult(hasMore: hasMore, items: items); } + Future _getRoomInfo(String roomId) async { + var result = await HttpClient.instance.getJson( + "https://www.douyu.com/betard/$roomId", + queryParameters: {}, + header: { + 'referer': 'https://www.douyu.com/$roomId', + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.43', + }); + Map roomInfo; + if (result is String) { + roomInfo = json.decode(result)["room"]; + } else { + roomInfo = result["room"]; + } + return roomInfo; + } + //生成指定长度的16进制随机字符串 String generateRandomString(int length) { var random = Random.secure(); @@ -303,8 +320,8 @@ class DouyuSite implements LiveSite { @override Future getLiveStatus({required String roomId}) async { - var detail = await getRoomDetail(roomId: roomId); - return detail.status; + var roomInfo = await _getRoomInfo(roomId); + return roomInfo["show_status"] == 1 && roomInfo["videoLoop"] != 1; } Future getPlayArgs(String html, String rid) async { diff --git a/simple_live_core/lib/src/huya_site.dart b/simple_live_core/lib/src/huya_site.dart index 9f7ac303..df36f77f 100644 --- a/simple_live_core/lib/src/huya_site.dart +++ b/simple_live_core/lib/src/huya_site.dart @@ -219,37 +219,18 @@ class HuyaSite implements LiveSite { @override Future getRoomDetail({required String roomId}) async { - var resultText = await HttpClient.instance.getText( - "https://m.huya.com/$roomId", - queryParameters: {}, - header: { - "user-agent": kUserAgent, - }, - ); - var text = RegExp( - r"window\.HNF_GLOBAL_INIT.=.\{[\s\S]*?\}[\s\S]*?", - multiLine: false) - .firstMatch(resultText) - ?.group(0); - var jsonText = text! - .replaceAll(RegExp(r"window\.HNF_GLOBAL_INIT.=."), '') - .replaceAll("", "") - .replaceAllMapped(RegExp(r'function.*?\(.*?\).\{[\s\S]*?\}'), (match) { - return '""'; - }); - - var jsonObj = json.decode(jsonText); + var roomInfo = await _getRoomInfo(roomId); + var tLiveInfo = roomInfo["roomInfo"]["tLiveInfo"]; + var tProfileInfo = roomInfo["roomInfo"]["tProfileInfo"]; - var title = - jsonObj["roomInfo"]["tLiveInfo"]["sIntroduction"]?.toString() ?? ""; + var title = tLiveInfo["sIntroduction"]?.toString() ?? ""; if (title.isEmpty) { - title = jsonObj["roomInfo"]["tLiveInfo"]["sRoomName"]?.toString() ?? ""; + title = tLiveInfo["sRoomName"]?.toString() ?? ""; } var huyaLines = []; var huyaBiterates = []; //读取可用线路 - var lines = jsonObj["roomInfo"]["tLiveInfo"]["tLiveStreamInfo"] - ["vStreamInfo"]["value"]; + var lines = tLiveInfo["tLiveStreamInfo"]["vStreamInfo"]["value"]; for (var item in lines) { if ((item["sFlvUrl"]?.toString() ?? "").isNotEmpty) { huyaLines.add(HuyaLineModel( @@ -263,8 +244,7 @@ class HuyaSite implements LiveSite { } //清晰度 - var biterates = jsonObj["roomInfo"]["tLiveInfo"]["tLiveStreamInfo"] - ["vBitRateInfo"]["value"]; + var biterates = tLiveInfo["tLiveStreamInfo"]["vBitRateInfo"]["value"]; for (var item in biterates) { var name = item["sDisplayName"].toString(); if (name.contains("HDR")) { @@ -276,38 +256,65 @@ class HuyaSite implements LiveSite { )); } + var topSid = roomInfo["topSid"]; + var subSid = roomInfo["subSid"]; + + return LiveRoomDetail( + cover: tLiveInfo["sScreenshot"].toString(), + online: tLiveInfo["lTotalCount"], + roomId: tLiveInfo["lProfileRoom"].toString(), + title: title, + userName: tProfileInfo["sNick"].toString(), + userAvatar: tProfileInfo["sAvatar180"].toString(), + introduction: tLiveInfo["sIntroduction"].toString(), + notice: roomInfo["welcomeText"].toString(), + status: roomInfo["roomInfo"]["eLiveStatus"] == 2, + data: HuyaUrlDataModel( + url: + "https:${utf8.decode(base64.decode(roomInfo["roomProfile"]["liveLineUrl"].toString()))}", + lines: huyaLines, + bitRates: huyaBiterates, + uid: getUid(t: 13, e: 10), + ), + danmakuData: HuyaDanmakuArgs( + ayyuid: tLiveInfo["lYyid"] ?? 0, + topSid: topSid ?? 0, + subSid: subSid ?? 0, + ), + url: "https://www.huya.com/$roomId", + ); + } + + Future _getRoomInfo(String roomId) async { + var resultText = await HttpClient.instance.getText( + "https://m.huya.com/$roomId", + queryParameters: {}, + header: { + "user-agent": kUserAgent, + }, + ); + var text = RegExp( + r"window\.HNF_GLOBAL_INIT.=.\{[\s\S]*?\}[\s\S]*?", + multiLine: false) + .firstMatch(resultText) + ?.group(0); + var jsonText = text! + .replaceAll(RegExp(r"window\.HNF_GLOBAL_INIT.=."), '') + .replaceAll("", "") + .replaceAllMapped(RegExp(r'function.*?\(.*?\).\{[\s\S]*?\}'), (match) { + return '""'; + }); + + var jsonObj = json.decode(jsonText); var topSid = int.tryParse( RegExp(r'lChannelId":([0-9]+)').firstMatch(resultText)?.group(1) ?? "0"); var subSid = int.tryParse( RegExp(r'lSubChannelId":([0-9]+)').firstMatch(resultText)?.group(1) ?? "0"); - - return LiveRoomDetail( - cover: jsonObj["roomInfo"]["tLiveInfo"]["sScreenshot"].toString(), - online: jsonObj["roomInfo"]["tLiveInfo"]["lTotalCount"], - roomId: jsonObj["roomInfo"]["tLiveInfo"]["lProfileRoom"].toString(), - title: title, - userName: jsonObj["roomInfo"]["tProfileInfo"]["sNick"].toString(), - userAvatar: - jsonObj["roomInfo"]["tProfileInfo"]["sAvatar180"].toString(), - introduction: - jsonObj["roomInfo"]["tLiveInfo"]["sIntroduction"].toString(), - notice: jsonObj["welcomeText"].toString(), - status: jsonObj["roomInfo"]["eLiveStatus"] == 2, - data: HuyaUrlDataModel( - url: - "https:${utf8.decode(base64.decode(jsonObj["roomProfile"]["liveLineUrl"].toString()))}", - lines: huyaLines, - bitRates: huyaBiterates, - uid: getUid(t: 13, e: 10), - ), - danmakuData: HuyaDanmakuArgs( - ayyuid: jsonObj["roomInfo"]["tLiveInfo"]["lYyid"] ?? 0, - topSid: topSid ?? 0, - subSid: subSid ?? 0, - ), - url: "https://www.huya.com/$roomId"); + jsonObj["topSid"] = topSid; + jsonObj["subSid"] = subSid; + return jsonObj; } @override @@ -387,23 +394,8 @@ class HuyaSite implements LiveSite { @override Future getLiveStatus({required String roomId}) async { - var resultText = await HttpClient.instance - .getText("https://m.huya.com/$roomId", queryParameters: {}, header: { - "user-agent": kUserAgent, - }); - var text = RegExp( - r"window\.HNF_GLOBAL_INIT.=.\{[\s\S]*?\}[\s\S]*?", - multiLine: false) - .firstMatch(resultText) - ?.group(0); - var jsonText = text! - .replaceAll(RegExp(r"window\.HNF_GLOBAL_INIT.=."), '') - .replaceAll("", "") - .replaceAllMapped(RegExp(r'function.*?\(.*?\).\{[\s\S]*?\}'), (match) { - return '""'; - }); - var jsonObj = json.decode(jsonText); - return jsonObj["roomInfo"]["eLiveStatus"] == 2; + var roomInfo = await _getRoomInfo(roomId); + return roomInfo["roomInfo"]["eLiveStatus"] == 2; } /// 匿名登录获取uid diff --git a/simple_live_core/pubspec.yaml b/simple_live_core/pubspec.yaml index 94524c70..0c2d7721 100644 --- a/simple_live_core/pubspec.yaml +++ b/simple_live_core/pubspec.yaml @@ -1,5 +1,5 @@ name: simple_live_core -version: 1.0.2 +version: 1.0.3 description: "聚合直播核心库" repository: https://github.com/xiaoyaocz/simple_live_client publish_to: "none" diff --git a/simple_live_tv_app/pubspec.lock b/simple_live_tv_app/pubspec.lock index df46591a..5ed6ec1f 100644 --- a/simple_live_tv_app/pubspec.lock +++ b/simple_live_tv_app/pubspec.lock @@ -712,7 +712,7 @@ packages: path: "../simple_live_core" relative: true source: path - version: "1.0.2" + version: "1.0.3" sky_engine: dependency: transitive description: flutter diff --git a/simple_live_tv_app/pubspec.yaml b/simple_live_tv_app/pubspec.yaml index 29bd1b79..bb81dded 100644 --- a/simple_live_tv_app/pubspec.yaml +++ b/simple_live_tv_app/pubspec.yaml @@ -1,7 +1,7 @@ name: simple_live_tv_app description: A new Flutter project. publish_to: 'none' -version: 1.0.8+10008 +version: 1.1.0+10100 environment: sdk: '>=3.1.2 <4.0.0'