diff --git a/assets/app_version.json b/assets/app_version.json
index cecd8918..accbd3b4 100644
--- a/assets/app_version.json
+++ b/assets/app_version.json
@@ -1,7 +1,7 @@
{
- "version": "1.3.0",
- "version_num": 10300,
- "version_desc": "1. 优化播放器 #78,#113\n2. 支持画面尺寸调整 #61\n3. 支持锁定播放器 #53\n4. 取消关注增加提示框 #120\n5. 修复抖音直播问题 #121\n6. 优化哔哩哔哩直播弹幕获取\n7. 播放器支持截图",
+ "version": "1.3.1",
+ "version_num": 10301,
+ "version_desc": "1. 支持直播间内设置定时关闭 #124\n2. 支持主页与平台自定义排序 #16,#76\n3. 支持跳转至原APP中打开直播间 #118\n4. 斗鱼增加录播判断 #6,#103\n5. 修复抖音直播问题 #121\n6. 播放失败尝试自动重连",
"prerelease":false,
"download_url": "https://github.com/xiaoyaocz/dart_simple_live/releases"
}
\ No newline at end of file
diff --git a/simple_live_app/android/app/src/main/AndroidManifest.xml b/simple_live_app/android/app/src/main/AndroidManifest.xml
index ac102b77..1fef7abd 100644
--- a/simple_live_app/android/app/src/main/AndroidManifest.xml
+++ b/simple_live_app/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
+
allHomePages = {
+ "recommend": HomePageItem(
+ iconData: Remix.home_smile_line,
+ title: "首页",
+ index: 0,
+ ),
+ "follow": HomePageItem(
+ iconData: Remix.heart_line,
+ title: "关注",
+ index: 1,
+ ),
+ "category": HomePageItem(
+ iconData: Remix.apps_line,
+ title: "分类",
+ index: 2,
+ ),
+ "user": HomePageItem(
+ iconData: Remix.user_smile_line,
+ title: "我的",
+ index: 3,
+ ),
+ };
+}
+
+class HomePageItem {
+ final IconData iconData;
+ final String title;
+ final int index;
+ HomePageItem({
+ required this.iconData,
+ required this.title,
+ required this.index,
+ });
}
diff --git a/simple_live_app/lib/app/controller/app_settings_controller.dart b/simple_live_app/lib/app/controller/app_settings_controller.dart
index cd54548e..26f0e932 100644
--- a/simple_live_app/lib/app/controller/app_settings_controller.dart
+++ b/simple_live_app/lib/app/controller/app_settings_controller.dart
@@ -1,3 +1,5 @@
+import 'package:simple_live_app/app/constant.dart';
+import 'package:simple_live_app/app/sites.dart';
import 'package:simple_live_app/services/local_storage_service.dart';
import 'package:flutter/material.dart';
@@ -63,9 +65,51 @@ class AppSettingsController extends GetxController {
0,
);
+ initSiteSort();
+ initHomeSort();
super.onInit();
}
+ void initSiteSort() {
+ var sort = LocalStorageService.instance
+ .getValue(
+ LocalStorageService.kSiteSort,
+ Sites.allSites.keys.join(","),
+ )
+ .split(",");
+ //如果数量与allSites的数量不一致,将缺失的添加上
+ if (sort.length != Sites.allSites.length) {
+ var keys = Sites.allSites.keys.toList();
+ for (var i = 0; i < keys.length; i++) {
+ if (!sort.contains(keys[i])) {
+ sort.add(keys[i]);
+ }
+ }
+ }
+
+ siteSort.value = sort;
+ }
+
+ void initHomeSort() {
+ var sort = LocalStorageService.instance
+ .getValue(
+ LocalStorageService.kHomeSort,
+ Constant.allHomePages.keys.join(","),
+ )
+ .split(",");
+ //如果数量与allSites的数量不一致,将缺失的添加上
+ if (sort.length != Constant.allHomePages.length) {
+ var keys = Constant.allHomePages.keys.toList();
+ for (var i = 0; i < keys.length; i++) {
+ if (!sort.contains(keys[i])) {
+ sort.add(keys[i]);
+ }
+ }
+ }
+
+ homeSort.value = sort;
+ }
+
void setNoFirstRun() {
LocalStorageService.instance.setValue(LocalStorageService.kFirstRun, false);
}
@@ -224,4 +268,22 @@ class AppSettingsController extends GetxController {
value,
);
}
+
+ RxList siteSort = RxList();
+ void setSiteSort(List e) {
+ siteSort.value = e;
+ LocalStorageService.instance.setValue(
+ LocalStorageService.kSiteSort,
+ siteSort.join(","),
+ );
+ }
+
+ RxList homeSort = RxList();
+ void setHomeSort(List e) {
+ homeSort.value = e;
+ LocalStorageService.instance.setValue(
+ LocalStorageService.kHomeSort,
+ homeSort.join(","),
+ );
+ }
}
diff --git a/simple_live_app/lib/app/sites.dart b/simple_live_app/lib/app/sites.dart
index b058a0ec..938a44f6 100644
--- a/simple_live_app/lib/app/sites.dart
+++ b/simple_live_app/lib/app/sites.dart
@@ -1,32 +1,39 @@
+import 'package:simple_live_app/app/controller/app_settings_controller.dart';
import 'package:simple_live_core/simple_live_core.dart';
class Sites {
- static List supportSites = [
- Site(
+ static final Map allSites = {
+ "bilibili": Site(
id: "bilibili",
logo: "assets/images/bilibili_2.png",
name: "哔哩哔哩",
liveSite: BiliBiliSite(),
),
- Site(
+ "douyu": Site(
id: "douyu",
logo: "assets/images/douyu.png",
name: "斗鱼直播",
liveSite: DouyuSite(),
),
- Site(
+ "huya": Site(
id: "huya",
logo: "assets/images/huya.png",
name: "虎牙直播",
liveSite: HuyaSite(),
),
- Site(
+ "douyin": Site(
id: "douyin",
logo: "assets/images/douyin.png",
name: "抖音直播",
liveSite: DouyinSite(),
),
- ];
+ };
+
+ static List get supportSites {
+ return AppSettingsController.instance.siteSort
+ .map((key) => allSites[key]!)
+ .toList();
+ }
}
class Site {
diff --git a/simple_live_app/lib/app/utils.dart b/simple_live_app/lib/app/utils.dart
index 8026c142..8dfcebb4 100644
--- a/simple_live_app/lib/app/utils.dart
+++ b/simple_live_app/lib/app/utils.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -443,6 +444,38 @@ class Utils {
}
}
+ static final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
+
+ /// 检查文件权限
+ static Future checkStorgePermission() async {
+ try {
+ if (!Platform.isAndroid) {
+ return true;
+ }
+ Permission permission = Permission.storage;
+ var androidIndo = await deviceInfo.androidInfo;
+ if (androidIndo.version.sdkInt >= 33) {
+ permission = Permission.manageExternalStorage;
+ }
+
+ var status = await permission.status;
+ if (status == PermissionStatus.granted) {
+ return true;
+ }
+ status = await permission.request();
+ if (status.isGranted) {
+ return true;
+ } else {
+ SmartDialog.showToast(
+ "请授予文件访问权限",
+ );
+ return false;
+ }
+ } catch (e) {
+ return false;
+ }
+ }
+
///16进制颜色转换
static Color convertHexColor(String hexColor) {
hexColor = hexColor.replaceAll("#", "");
diff --git a/simple_live_app/lib/modules/indexed/indexed_controller.dart b/simple_live_app/lib/modules/indexed/indexed_controller.dart
index 73ced7ae..db0f6810 100644
--- a/simple_live_app/lib/modules/indexed/indexed_controller.dart
+++ b/simple_live_app/lib/modules/indexed/indexed_controller.dart
@@ -1,28 +1,36 @@
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
+import 'package:simple_live_app/app/constant.dart';
import 'package:simple_live_app/app/controller/app_settings_controller.dart';
import 'package:simple_live_app/app/event_bus.dart';
import 'package:simple_live_app/app/utils.dart';
import 'package:simple_live_app/modules/category/category_controller.dart';
import 'package:simple_live_app/modules/category/category_page.dart';
+import 'package:simple_live_app/modules/home/home_controller.dart';
import 'package:simple_live_app/modules/home/home_page.dart';
import 'package:simple_live_app/modules/user/follow_user/follow_user_controller.dart';
import 'package:simple_live_app/modules/user/follow_user/follow_user_page.dart';
import 'package:simple_live_app/modules/user/user_page.dart';
class IndexedController extends GetxController {
+ RxList items = RxList([]);
+
var index = 0.obs;
RxList pages = RxList([
- const HomePage(),
+ const SizedBox(),
const SizedBox(),
const SizedBox(),
const SizedBox(),
]);
- void setIndex(i) {
+ void setIndex(int i) {
if (pages[i] is SizedBox) {
- switch (i) {
+ switch (items[i].index) {
+ case 0:
+ Get.put(HomeController());
+ pages[i] = const HomePage();
+ break;
case 1:
Get.put(FollowUserController());
pages[i] = const FollowUserPage();
@@ -31,22 +39,28 @@ class IndexedController extends GetxController {
Get.put(CategoryController());
pages[i] = const CategoryPage();
break;
-
case 3:
pages[i] = const UserPage();
break;
default:
}
+ } else {
+ if (index.value == i) {
+ EventBus.instance
+ .emit(EventBus.kBottomNavigationBarClicked, items[i].index);
+ }
}
- if (index.value == i) {
- EventBus.instance.emit(EventBus.kBottomNavigationBarClicked, i);
- }
+
index.value = i;
}
@override
void onInit() {
Future.delayed(Duration.zero, showFirstRun);
+ items.value = AppSettingsController.instance.homeSort
+ .map((key) => Constant.allHomePages[key]!)
+ .toList();
+ setIndex(0);
super.onInit();
}
diff --git a/simple_live_app/lib/modules/indexed/indexed_page.dart b/simple_live_app/lib/modules/indexed/indexed_page.dart
index 6535713a..675ff6c3 100644
--- a/simple_live_app/lib/modules/indexed/indexed_page.dart
+++ b/simple_live_app/lib/modules/indexed/indexed_page.dart
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-import 'package:remixicon/remixicon.dart';
import 'indexed_controller.dart';
@@ -22,62 +21,16 @@ class IndexedPage extends GetView {
onDestinationSelected: controller.setIndex,
height: 56,
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
- destinations: const [
- NavigationDestination(
- icon: Icon(Remix.home_smile_line),
- label: "首页",
- ),
- NavigationDestination(
- icon: Icon(Remix.heart_line),
- label: "关注",
- ),
- NavigationDestination(
- icon: Icon(Remix.apps_line),
- label: "分类",
- ),
- NavigationDestination(
- icon: Icon(Remix.user_smile_line),
- label: "我的",
- ),
- ],
+ destinations: controller.items
+ .map(
+ (item) => NavigationDestination(
+ icon: Icon(item.iconData),
+ label: item.title,
+ ),
+ )
+ .toList(),
),
),
- // bottomNavigationBar: Obx(
- // () => BottomNavigationBar(
- // currentIndex: controller.index.value,
- // onTap: controller.setIndex,
- // selectedFontSize: 12,
- // unselectedFontSize: 12,
- // iconSize: 24,
- // type: BottomNavigationBarType.fixed,
- // showSelectedLabels: true,
- // showUnselectedLabels: true,
- // elevation: 4,
- // landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
- // items: const [
- // BottomNavigationBarItem(
- // icon: Icon(Remix.home_smile_line),
- // activeIcon: Icon(Remix.home_smile_fill),
- // label: "首页",
- // ),
- // BottomNavigationBarItem(
- // icon: Icon(Remix.apps_line),
- // activeIcon: Icon(Remix.apps_fill),
- // label: "分类",
- // ),
- // BottomNavigationBarItem(
- // icon: Icon(Remix.tools_line),
- // activeIcon: Icon(Remix.tools_fill),
- // label: "工具箱",
- // ),
- // BottomNavigationBarItem(
- // icon: Icon(Remix.user_smile_line),
- // activeIcon: Icon(Remix.user_smile_fill),
- // label: "我的",
- // ),
- // ],
- // ),
- // ),
);
}
}
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 0339d486..a420b8af 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
@@ -19,6 +19,7 @@ import 'package:simple_live_app/models/db/history.dart';
import 'package:simple_live_app/modules/live_room/player/player_controller.dart';
import 'package:simple_live_app/services/db_service.dart';
import 'package:simple_live_core/simple_live_core.dart';
+import 'package:url_launcher/url_launcher_string.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
class LiveRoomController extends PlayerController {
@@ -54,37 +55,55 @@ class LiveRoomController extends PlayerController {
RxList playUrls = RxList();
/// 当前线路
- var currentUrl = -1;
- var currentUrlInfo = "".obs;
+ var currentLineIndex = -1;
+ var currentLineInfo = "".obs;
/// 退出倒计时
var countdown = 60.obs;
Timer? autoExitTimer;
+ /// 设置的自动关闭时间(分钟)
+ var autoExitMinutes = 60.obs;
+
+ /// 是否启用自动关闭
+ var autoExitEnable = false.obs;
+
@override
void onInit() {
initAutoExit();
showDanmakuState.value = AppSettingsController.instance.danmuEnable.value;
followed.value = DBService.instance.getFollowExist("${site.id}_$roomId");
loadData();
+
super.onInit();
}
/// 初始化自动关闭倒计时
void initAutoExit() {
if (AppSettingsController.instance.autoExitEnable.value) {
- countdown.value =
- AppSettingsController.instance.autoExitDuration.value * 60;
- autoExitTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
- countdown.value -= 1;
- if (countdown.value <= 0) {
- timer.cancel();
- await WakelockPlus.disable();
- exit(0);
- }
- });
+ autoExitEnable.value = true;
+ autoExitMinutes.value =
+ AppSettingsController.instance.autoExitDuration.value;
+ setAutoExit();
+ }
+ }
+
+ void setAutoExit() {
+ if (!autoExitEnable.value) {
+ autoExitTimer?.cancel();
+ return;
}
+ autoExitTimer?.cancel();
+ countdown.value = autoExitMinutes.value * 60;
+ autoExitTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
+ countdown.value -= 1;
+ if (countdown.value <= 0) {
+ timer.cancel();
+ await WakelockPlus.disable();
+ exit(0);
+ }
+ });
}
void refreshRoom() {
@@ -182,11 +201,13 @@ class LiveRoomController extends PlayerController {
addHistory();
online.value = detail.value!.online;
- liveStatus.value = detail.value!.status;
+ liveStatus.value = detail.value!.status || detail.value!.isRecord;
if (liveStatus.value) {
getPlayQualites();
}
-
+ if (detail.value!.isRecord) {
+ addSysMsg("当前主播未开播,正在轮播录像");
+ }
addSysMsg("开始连接弹幕服务器");
initDanmau();
liveDanmaku.start(detail.value?.danmakuData);
@@ -233,8 +254,8 @@ class LiveRoomController extends PlayerController {
void getPlayUrl() async {
playUrls.clear();
currentQualityInfo.value = qualites[currentQuality].quality;
- currentUrlInfo.value = "";
- currentUrl = -1;
+ currentLineInfo.value = "";
+ currentLineIndex = -1;
var playUrl = await site.liveSite
.getPlayUrls(detail: detail.value!, quality: qualites[currentQuality]);
if (playUrl.isEmpty) {
@@ -242,13 +263,22 @@ class LiveRoomController extends PlayerController {
return;
}
playUrls.value = playUrl;
- currentUrl = 0;
- currentUrlInfo.value = "线路${currentUrl + 1}";
+ currentLineIndex = 0;
+ currentLineInfo.value = "线路${currentLineIndex + 1}";
+ //重置错误次数
+ mediaErrorRetryCount = 0;
+ setPlayer();
+ }
+
+ void changePlayLine(int index) {
+ currentLineIndex = index;
+ //重置错误次数
+ mediaErrorRetryCount = 0;
setPlayer();
}
void setPlayer() async {
- currentUrlInfo.value = "线路${currentUrl + 1}";
+ currentLineInfo.value = "线路${currentLineIndex + 1}";
errorMsg.value = "";
Map headers = {};
if (site.id == "bilibili") {
@@ -261,33 +291,61 @@ class LiveRoomController extends PlayerController {
player.open(
Media(
- playUrls[currentUrl],
+ playUrls[currentLineIndex],
httpHeaders: headers,
),
);
- Log.d("播放链接\r\n:${playUrls[currentUrl]}");
+ Log.d("播放链接\r\n:${playUrls[currentLineIndex]}");
}
@override
- void mediaEnd() {
+ void mediaEnd() async {
+ if (mediaErrorRetryCount < 2) {
+ Log.d("播放结束,尝试第${mediaErrorRetryCount + 1}次刷新");
+ if (mediaErrorRetryCount == 1) {
+ //延迟一秒再刷新
+ await Future.delayed(const Duration(seconds: 1));
+ }
+ mediaErrorRetryCount += 1;
+ //刷新一次
+ setPlayer();
+ return;
+ }
+
+ Log.d("播放结束");
// 遍历线路,如果全部链接都断开就是直播结束了
- if (playUrls.length - 1 == currentUrl) {
+ if (playUrls.length - 1 == currentLineIndex) {
liveStatus.value = false;
} else {
- currentUrl += 1;
- setPlayer();
+ changePlayLine(currentLineIndex + 1);
+
+ //setPlayer();
}
}
+ int mediaErrorRetryCount = 0;
@override
- void mediaError(String error) {
- if (playUrls.length - 1 == currentUrl) {
+ void mediaError(String error) async {
+ if (mediaErrorRetryCount < 2) {
+ Log.d("播放失败,尝试第${mediaErrorRetryCount + 1}次刷新");
+ if (mediaErrorRetryCount == 1) {
+ //延迟一秒再刷新
+ await Future.delayed(const Duration(seconds: 1));
+ }
+ mediaErrorRetryCount += 1;
+ //刷新一次
+ setPlayer();
+ return;
+ }
+
+ if (playUrls.length - 1 == currentLineIndex) {
errorMsg.value = "播放失败";
SmartDialog.showToast("播放失败:$error");
} else {
- currentUrl += 1;
- setPlayer();
+ //currentLineIndex += 1;
+ //setPlayer();
+ changePlayLine(currentLineIndex + 1);
}
}
@@ -513,15 +571,16 @@ class LiveRoomController extends PlayerController {
itemBuilder: (_, i) {
return RadioListTile(
value: i,
- groupValue: currentUrl,
+ groupValue: currentLineIndex,
title: Text("线路${i + 1}"),
secondary: Text(
playUrls[i].contains(".flv") ? "FLV" : "HLS",
),
onChanged: (e) {
Get.back();
- currentUrl = i;
- setPlayer();
+ //currentLineIndex = i;
+ //setPlayer();
+ changePlayLine(i);
},
);
},
@@ -587,6 +646,105 @@ class LiveRoomController extends PlayerController {
);
}
+ void showAutoExitSheet() {
+ if (AppSettingsController.instance.autoExitEnable.value) {
+ SmartDialog.showToast("已设置了全局定时关闭");
+ return;
+ }
+ Utils.showBottomSheet(
+ title: "定时关闭",
+ child: ListView(
+ children: [
+ Obx(
+ () => SwitchListTile(
+ title: Text(
+ "启用定时关闭",
+ style: Get.textTheme.titleMedium,
+ ),
+ value: autoExitEnable.value,
+ onChanged: (e) {
+ autoExitEnable.value = e;
+
+ setAutoExit();
+ //controller.setAutoExitEnable(e);
+ },
+ ),
+ ),
+ Obx(
+ () => ListTile(
+ enabled: autoExitEnable.value,
+ title: Text(
+ "自动关闭时间:${autoExitMinutes.value ~/ 60}小时${autoExitMinutes.value % 60}分钟",
+ style: Get.textTheme.titleMedium,
+ ),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () async {
+ var value = await showTimePicker(
+ context: Get.context!,
+ initialTime: TimeOfDay(
+ hour: autoExitMinutes.value ~/ 60,
+ minute: autoExitMinutes.value % 60,
+ ),
+ initialEntryMode: TimePickerEntryMode.inputOnly,
+ builder: (_, child) {
+ return Theme(
+ data: Get.theme.copyWith(
+ useMaterial3: false,
+ ),
+ child: MediaQuery(
+ data: Get.mediaQuery.copyWith(
+ alwaysUse24HourFormat: true,
+ ),
+ child: child!,
+ ),
+ );
+ },
+ );
+ if (value == null || (value.hour == 0 && value.minute == 0)) {
+ return;
+ }
+ var duration =
+ Duration(hours: value.hour, minutes: value.minute);
+ autoExitMinutes.value = duration.inMinutes;
+ //setAutoExitDuration(duration.inMinutes);
+ setAutoExit();
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ void openNaviteAPP() async {
+ var naviteUrl = "";
+ var webUrl = "";
+ if (site.id == "bilibili") {
+ naviteUrl = "bilibili://live/${detail.value?.roomId}";
+ webUrl = "https://live.bilibili.com/${detail.value?.roomId}";
+ } else if (site.id == "douyin") {
+ var args = detail.value?.danmakuData as DouyinDanmakuArgs;
+ naviteUrl = "snssdk1128://webcast_room?room_id=${args.roomId}";
+ webUrl = "https://www.douyu.com/${args.webRid}";
+ } else if (site.id == "huya") {
+ var args = detail.value?.danmakuData as HuyaDanmakuArgs;
+ naviteUrl =
+ "yykiwi://homepage/index.html?banneraction=https%3A%2F%2Fdiy-front.cdn.huya.com%2Fzt%2Ffrontpage%2Fcc%2Fupdate.html%3Fhyaction%3Dlive%26channelid%3D${args.subSid}%26subid%3D${args.subSid}%26liveuid%3D${args.subSid}%26screentype%3D1%26sourcetype%3D0%26fromapp%3Dhuya_wap%252Fclick%252Fopen_app_guide%26&fromapp=huya_wap/click/open_app_guide";
+ webUrl = "https://www.huya.com/${detail.value?.roomId}";
+ } else if (site.id == "douyu") {
+ naviteUrl =
+ "douyulink://?type=90001&schemeUrl=douyuapp%3A%2F%2Froom%3FliveType%3D0%26rid%3D${detail.value?.roomId}";
+ webUrl = "https://www.douyu.com/${detail.value?.roomId}";
+ }
+ try {
+ await launchUrlString(naviteUrl, mode: LaunchMode.externalApplication);
+ } catch (e) {
+ Log.logPrint(e);
+ SmartDialog.showToast("无法打开APP,将使用浏览器打开");
+ await launchUrlString(webUrl, mode: LaunchMode.externalApplication);
+ }
+ }
+
@override
void onClose() {
autoExitTimer?.cancel();
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 b6d10bd4..d6615ca3 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
@@ -7,6 +7,7 @@ import 'package:simple_live_app/app/controller/app_settings_controller.dart';
import 'package:simple_live_app/app/utils.dart';
import 'package:simple_live_app/modules/live_room/live_room_controller.dart';
import 'package:simple_live_app/modules/live_room/player/player_controls.dart';
+import 'package:simple_live_app/widgets/keep_alive_wrapper.dart';
import 'package:simple_live_app/widgets/net_image.dart';
import 'package:simple_live_app/widgets/superchat_card.dart';
import 'package:simple_live_core/simple_live_core.dart';
@@ -383,22 +384,7 @@ class LiveRoomPage extends GetView {
},
),
),
- Obx(
- () => ListView.separated(
- padding: AppStyle.edgeInsetsA12,
- itemCount: controller.superChats.length,
- separatorBuilder: (_, i) => AppStyle.vGap12,
- itemBuilder: (_, i) {
- var item = controller.superChats[i];
- return SuperChatCard(
- item,
- onExpire: () {
- controller.removeSuperChats();
- },
- );
- },
- ),
- ),
+ buildSuperChats(),
buildSettings(),
],
),
@@ -452,11 +438,41 @@ class LiveRoomPage extends GetView {
);
}
+ Widget buildSuperChats() {
+ return KeepAliveWrapper(
+ child: Obx(
+ () => ListView.separated(
+ padding: AppStyle.edgeInsetsA12,
+ itemCount: controller.superChats.length,
+ separatorBuilder: (_, i) => AppStyle.vGap12,
+ itemBuilder: (_, i) {
+ var item = controller.superChats[i];
+ return SuperChatCard(
+ item,
+ onExpire: () {
+ controller.removeSuperChats();
+ },
+ );
+ },
+ ),
+ ),
+ );
+ }
+
Widget buildSettings() {
return Obx(
() => ListView(
padding: AppStyle.edgeInsetsA12,
children: [
+ Obx(
+ () => Visibility(
+ visible: controller.autoExitEnable.value,
+ child: ListTile(
+ leading: const Icon(Icons.timer_outlined),
+ title: Text("${controller.countdown.value}秒后自动关闭"),
+ ),
+ ),
+ ),
Padding(
padding: AppStyle.edgeInsetsH12.copyWith(top: 12),
child: Text(
@@ -566,15 +582,33 @@ class LiveRoomPage extends GetView {
controller.saveScreenshot();
},
),
+ ListTile(
+ leading: const Icon(Icons.timer_outlined),
+ title: const Text("定时关闭"),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () {
+ Get.back();
+ controller.showAutoExitSheet();
+ },
+ ),
ListTile(
leading: const Icon(Icons.share_sharp),
- title: const Text("分享"),
+ title: const Text("分享链接"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Get.back();
controller.share();
},
),
+ ListTile(
+ leading: const Icon(Icons.open_in_new),
+ title: const Text("APP中打开"),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () {
+ Get.back();
+ controller.openNaviteAPP();
+ },
+ ),
],
),
),
diff --git a/simple_live_app/lib/modules/live_room/player/player_controls.dart b/simple_live_app/lib/modules/live_room/player/player_controls.dart
index ffffb1b7..3fc78636 100644
--- a/simple_live_app/lib/modules/live_room/player/player_controls.dart
+++ b/simple_live_app/lib/modules/live_room/player/player_controls.dart
@@ -225,7 +225,7 @@ Widget buildFullControls(
showLinesInfo(controller);
},
child: Text(
- controller.currentUrlInfo.value,
+ controller.currentLineInfo.value,
style: const TextStyle(color: Colors.white, fontSize: 15),
),
),
@@ -439,7 +439,7 @@ Widget buildControls(
controller.showPlayUrlsSheet();
},
child: Text(
- controller.currentUrlInfo.value,
+ controller.currentLineInfo.value,
style: const TextStyle(color: Colors.white, fontSize: 15),
),
),
@@ -511,7 +511,7 @@ void showLinesInfo(LiveRoomController controller) {
itemCount: controller.playUrls.length,
itemBuilder: (_, i) {
return ListTile(
- selected: controller.currentUrl == i,
+ selected: controller.currentLineIndex == i,
title: Text.rich(
TextSpan(
text: "线路${i + 1}",
@@ -540,8 +540,9 @@ void showLinesInfo(LiveRoomController controller) {
minLeadingWidth: 16,
onTap: () {
Utils.hideRightDialog();
- controller.currentUrl = i;
- controller.setPlayer();
+ //controller.currentLineIndex = i;
+ //controller.setPlayer();
+ controller.changePlayLine(i);
},
);
},
diff --git a/simple_live_app/lib/modules/toolbox/toolbox_controller.dart b/simple_live_app/lib/modules/toolbox/toolbox_controller.dart
index d1f97a92..83cd9b77 100644
--- a/simple_live_app/lib/modules/toolbox/toolbox_controller.dart
+++ b/simple_live_app/lib/modules/toolbox/toolbox_controller.dart
@@ -144,7 +144,7 @@ class ToolBoxController extends GetxController {
followRedirects: false,
),
);
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response!.statusCode == 302) {
var redirectUrl = e.response!.headers.value("Location");
if (redirectUrl != null) {
diff --git a/simple_live_app/lib/modules/user/follow_user/follow_user_controller.dart b/simple_live_app/lib/modules/user/follow_user/follow_user_controller.dart
index a4cad74a..fcf93c3e 100644
--- a/simple_live_app/lib/modules/user/follow_user/follow_user_controller.dart
+++ b/simple_live_app/lib/modules/user/follow_user/follow_user_controller.dart
@@ -80,6 +80,12 @@ class FollowUserController extends BasePageController {
}
try {
+ var status = await Utils.checkStorgePermission();
+ if (!status) {
+ SmartDialog.showToast("无权限");
+ return;
+ }
+
var dir = "";
if (Platform.isIOS) {
dir = (await getApplicationDocumentsDirectory()).path;
@@ -114,14 +120,19 @@ class FollowUserController extends BasePageController {
}
void inputList() async {
- var file = await FilePicker.platform.pickFiles(
- type: FileType.custom,
- allowedExtensions: ['json'],
- );
- if (file == null) {
- return;
- }
try {
+ var status = await Utils.checkStorgePermission();
+ if (!status) {
+ SmartDialog.showToast("无权限");
+ return;
+ }
+ var file = await FilePicker.platform.pickFiles(
+ type: FileType.custom,
+ allowedExtensions: ['json'],
+ );
+ if (file == null) {
+ return;
+ }
var jsonFile = File(file.files.single.path!);
var data = jsonDecode(await jsonFile.readAsString());
diff --git a/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_controller.dart b/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_controller.dart
new file mode 100644
index 00000000..a4d399c9
--- /dev/null
+++ b/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_controller.dart
@@ -0,0 +1,33 @@
+import 'package:get/get.dart';
+import 'package:simple_live_app/app/controller/app_settings_controller.dart';
+
+class IndexedSettingsController extends GetxController {
+ RxList siteSort = RxList();
+ RxList homeSort = RxList();
+ @override
+ void onInit() {
+ siteSort = AppSettingsController.instance.siteSort;
+ homeSort = AppSettingsController.instance.homeSort;
+ super.onInit();
+ }
+
+ void updateSiteSort(int oldIndex, int newIndex) {
+ if (oldIndex < newIndex) {
+ newIndex -= 1;
+ }
+ final String item = siteSort.removeAt(oldIndex);
+ siteSort.insert(newIndex, item);
+ // ignore: invalid_use_of_protected_member
+ AppSettingsController.instance.setSiteSort(siteSort.value);
+ }
+
+ void updateHomeSort(int oldIndex, int newIndex) {
+ if (oldIndex < newIndex) {
+ newIndex -= 1;
+ }
+ final String item = homeSort.removeAt(oldIndex);
+ homeSort.insert(newIndex, item);
+ // ignore: invalid_use_of_protected_member
+ AppSettingsController.instance.setHomeSort(homeSort.value);
+ }
+}
diff --git a/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_page.dart b/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_page.dart
new file mode 100644
index 00000000..e5fdb81a
--- /dev/null
+++ b/simple_live_app/lib/modules/user/indexed_settings/indexed_settings_page.dart
@@ -0,0 +1,80 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:simple_live_app/app/constant.dart';
+import 'package:simple_live_app/app/sites.dart';
+import 'package:simple_live_app/modules/user/indexed_settings/indexed_settings_controller.dart';
+
+class IndexedSettingsPage extends GetView {
+ const IndexedSettingsPage({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text("主页设置"),
+ ),
+ body: ListView(
+ children: [
+ ListTile(
+ title: Text(
+ "主页排序",
+ style: Get.textTheme.titleSmall,
+ ),
+ visualDensity: VisualDensity.compact,
+ subtitle: const Text("拖动进行排序,重启APP后生效"),
+ ),
+ Obx(
+ () => ReorderableListView(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ onReorder: controller.updateHomeSort,
+ children: controller.homeSort.map(
+ (key) {
+ var e = Constant.allHomePages[key]!;
+ return ListTile(
+ key: ValueKey(e.title),
+ title: Text(e.title),
+ visualDensity: VisualDensity.compact,
+ leading: Icon(e.iconData),
+ trailing: const Icon(Icons.drag_handle),
+ );
+ },
+ ).toList(),
+ ),
+ ),
+ ListTile(
+ title: Text(
+ "平台排序",
+ style: Get.textTheme.titleSmall,
+ ),
+ visualDensity: VisualDensity.compact,
+ subtitle: const Text("拖动进行排序,重启APP后生效"),
+ ),
+ Obx(
+ () => ReorderableListView(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ onReorder: controller.updateSiteSort,
+ children: controller.siteSort.map(
+ (key) {
+ var e = Sites.allSites[key]!;
+ return ListTile(
+ key: ValueKey(e.id),
+ visualDensity: VisualDensity.compact,
+ title: Text(e.name),
+ leading: Image.asset(
+ e.logo,
+ width: 24,
+ height: 24,
+ ),
+ trailing: const Icon(Icons.drag_handle),
+ );
+ },
+ ).toList(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/simple_live_app/lib/modules/user/user_page.dart b/simple_live_app/lib/modules/user/user_page.dart
index 331e1e2a..8b156938 100644
--- a/simple_live_app/lib/modules/user/user_page.dart
+++ b/simple_live_app/lib/modules/user/user_page.dart
@@ -106,6 +106,17 @@ class UserPage extends StatelessWidget {
),
onTap: Get.find().changeTheme,
),
+ ListTile(
+ leading: const Icon(Remix.home_2_line),
+ title: const Text("主页设置"),
+ trailing: const Icon(
+ Icons.chevron_right,
+ color: Colors.grey,
+ ),
+ onTap: () {
+ Get.toNamed(RoutePath.kSettingsIndexed);
+ },
+ ),
ListTile(
leading: const Icon(Remix.play_circle_line),
title: const Text("播放设置"),
diff --git a/simple_live_app/lib/routes/app_pages.dart b/simple_live_app/lib/routes/app_pages.dart
index 3d4d65bc..ef834d62 100644
--- a/simple_live_app/lib/routes/app_pages.dart
+++ b/simple_live_app/lib/routes/app_pages.dart
@@ -3,7 +3,6 @@
import 'package:get/get.dart';
import 'package:simple_live_app/modules/categoty_detail/category_detail_controller.dart';
import 'package:simple_live_app/modules/categoty_detail/category_detail_page.dart';
-import 'package:simple_live_app/modules/home/home_controller.dart';
import 'package:simple_live_app/modules/indexed/indexed_controller.dart';
import 'package:simple_live_app/modules/live_room/live_room_controller.dart';
import 'package:simple_live_app/modules/live_room/live_room_page.dart';
@@ -19,6 +18,8 @@ import 'package:simple_live_app/modules/user/follow_user/follow_user_controller.
import 'package:simple_live_app/modules/user/follow_user/follow_user_page.dart';
import 'package:simple_live_app/modules/user/history/history_controller.dart';
import 'package:simple_live_app/modules/user/history/history_page.dart';
+import 'package:simple_live_app/modules/user/indexed_settings/indexed_settings_controller.dart';
+import 'package:simple_live_app/modules/user/indexed_settings/indexed_settings_page.dart';
import 'package:simple_live_app/modules/user/play_settings_page.dart';
import '../modules/indexed/indexed_page.dart';
@@ -33,7 +34,7 @@ class AppPages {
page: () => const IndexedPage(),
bindings: [
BindingsBuilder.put(() => IndexedController()),
- BindingsBuilder.put(() => HomeController()),
+ //BindingsBuilder.put(() => HomeController()),
],
),
// 观看记录
@@ -113,5 +114,13 @@ class AppPages {
BindingsBuilder.put(() => DanmuShieldController()),
],
),
+ //主页设置
+ GetPage(
+ name: RoutePath.kSettingsIndexed,
+ page: () => const IndexedSettingsPage(),
+ bindings: [
+ BindingsBuilder.put(() => IndexedSettingsController()),
+ ],
+ ),
];
}
diff --git a/simple_live_app/lib/routes/route_path.dart b/simple_live_app/lib/routes/route_path.dart
index bed04da5..805ee1fc 100644
--- a/simple_live_app/lib/routes/route_path.dart
+++ b/simple_live_app/lib/routes/route_path.dart
@@ -35,4 +35,7 @@ class RoutePath {
/// 工具箱
static const kTools = "/other/tools";
+
+ /// 主页设置
+ static const kSettingsIndexed = "/settings/indexed";
}
diff --git a/simple_live_app/lib/services/local_storage_service.dart b/simple_live_app/lib/services/local_storage_service.dart
index 701b1e98..e2a35c22 100644
--- a/simple_live_app/lib/services/local_storage_service.dart
+++ b/simple_live_app/lib/services/local_storage_service.dart
@@ -11,6 +11,12 @@ class LocalStorageService extends GetxService {
/// 缩放模式
static const String kPlayerScaleMode = "ScaleMode";
+ /// 网站排序
+ static const String kSiteSort = "SiteSort";
+
+ /// 首页排序
+ static const String kHomeSort = "HomeSort";
+
/// 显示模式
/// * [0] 跟随系统
/// * [1] 浅色模式
diff --git a/simple_live_app/pubspec.yaml b/simple_live_app/pubspec.yaml
index 2ad59e83..33819aca 100644
--- a/simple_live_app/pubspec.yaml
+++ b/simple_live_app/pubspec.yaml
@@ -1,5 +1,5 @@
name: simple_live_app
-version: 1.3.0+10300
+version: 1.3.1+10301
publish_to: none
description: "Simple Live APP"
environment:
@@ -14,7 +14,7 @@ dependencies:
# 框架、工具
get: 4.6.5 #状态管理、路由管理、国际化
- dio: 4.0.6 #网络请求
+ dio: ^5.3.2 #网络请求
hive: 2.2.3 #持久化存储
hive_flutter: 1.1.0 #持久化存储
logger: 1.1.0 #日志
diff --git a/simple_live_core/.gitignore b/simple_live_core/.gitignore
index 3cceda55..8b315161 100644
--- a/simple_live_core/.gitignore
+++ b/simple_live_core/.gitignore
@@ -5,3 +5,5 @@
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
+
+.fvm/
\ No newline at end of file
diff --git a/simple_live_core/example/simple_live_core_example.dart b/simple_live_core/example/simple_live_core_example.dart
index ddb719ed..fcae195c 100644
--- a/simple_live_core/example/simple_live_core_example.dart
+++ b/simple_live_core/example/simple_live_core_example.dart
@@ -2,7 +2,7 @@ import 'package:simple_live_core/simple_live_core.dart';
void main() async {
CoreLog.enableLog = true;
- LiveSite site = BiliBiliSite();
+ LiveSite site = DouyinSite();
var danmaku = site.getDanmaku();
danmaku.onMessage = (event) {
if (event.type == LiveMessageType.chat) {
@@ -17,13 +17,18 @@ void main() async {
danmaku.onClose = (event) {
print(event);
};
- var detail = await site.getRoomDetail(roomId: "4245963");
+ var categores = await site.getCategores();
+ print(categores.length);
+ //var detail = await site.getRoomDetail(roomId: "639709145929");
// var playQualites = await site.getPlayQualites(detail: detail);
// var playUrls =
// await site.getPlayUrls(detail: detail, quality: playQualites.first);
// for (var element in playUrls) {
// print(element);
// }
- danmaku.start(detail.danmakuData);
+ //print(detail);
+
+ //danmaku.start(detail.danmakuData);
+
await Future.wait({});
}
diff --git a/simple_live_core/lib/src/common/custom_interceptor.dart b/simple_live_core/lib/src/common/custom_interceptor.dart
index 4d413f9f..5b860577 100644
--- a/simple_live_core/lib/src/common/custom_interceptor.dart
+++ b/simple_live_core/lib/src/common/custom_interceptor.dart
@@ -11,7 +11,7 @@ class CustomInterceptor extends Interceptor {
}
@override
- void onError(DioError err, ErrorInterceptorHandler handler) {
+ void onError(DioException err, ErrorInterceptorHandler handler) {
var time =
DateTime.now().millisecondsSinceEpoch - err.requestOptions.extra["ts"];
CoreLog.e('''【HTTP请求错误-${err.type}】 耗时:${time}ms
@@ -24,7 +24,7 @@ Request Query:${err.requestOptions.queryParameters}
Request Data:${err.requestOptions.data}
Request Headers:${err.requestOptions.headers}
Response Headers:${err.response?.headers.map}
-Response Data:${err.response?.data}''', err.stackTrace ?? StackTrace.current);
+Response Data:${err.response?.data}''', err.stackTrace);
super.onError(err, handler);
}
diff --git a/simple_live_core/lib/src/common/http_client.dart b/simple_live_core/lib/src/common/http_client.dart
index 8dd45c06..1fd7b925 100644
--- a/simple_live_core/lib/src/common/http_client.dart
+++ b/simple_live_core/lib/src/common/http_client.dart
@@ -15,9 +15,9 @@ class HttpClient {
HttpClient() {
dio = Dio(
BaseOptions(
- connectTimeout: 20 * 1000,
- receiveTimeout: 20 * 1000,
- sendTimeout: 20 * 1000,
+ connectTimeout: Duration(seconds: 20),
+ receiveTimeout: Duration(seconds: 20),
+ sendTimeout: Duration(seconds: 20),
),
);
dio.interceptors.add(CustomInterceptor());
@@ -47,8 +47,9 @@ class HttpClient {
);
return result.data;
} catch (e) {
- if (e is DioError && e.type == DioErrorType.response) {
- throw CoreError(e.message, statusCode: e.response?.statusCode ?? 0);
+ if (e is DioException && e.type == DioExceptionType.badResponse) {
+ throw CoreError(e.message ?? "",
+ statusCode: e.response?.statusCode ?? 0);
} else {
throw CoreError("发送GET请求失败");
}
@@ -79,8 +80,9 @@ class HttpClient {
);
return result.data;
} catch (e) {
- if (e is DioError && e.type == DioErrorType.response) {
- throw CoreError(e.message, statusCode: e.response?.statusCode ?? 0);
+ if (e is DioException && e.type == DioExceptionType.badResponse) {
+ throw CoreError(e.message ?? "",
+ statusCode: e.response?.statusCode ?? 0);
} else {
throw CoreError("发送GET请求失败");
}
@@ -118,8 +120,9 @@ class HttpClient {
);
return result.data;
} catch (e) {
- if (e is DioError && e.type == DioErrorType.response) {
- throw CoreError(e.message, statusCode: e.response?.statusCode ?? 0);
+ if (e is DioException && e.type == DioExceptionType.badResponse) {
+ throw CoreError(e.message ?? "",
+ statusCode: e.response?.statusCode ?? 0);
} else {
throw CoreError("发送POST请求失败");
}
@@ -150,7 +153,7 @@ class HttpClient {
);
return result;
} catch (e) {
- if (e is DioError && e.type == DioErrorType.response) {
+ if (e is DioException && e.type == DioExceptionType.badResponse) {
//throw CoreError(e.message, statusCode: e.response?.statusCode ?? 0);
return e.response!;
} else {
diff --git a/simple_live_core/lib/src/danmaku/bilibili_danmaku.dart b/simple_live_core/lib/src/danmaku/bilibili_danmaku.dart
index dfeedec6..4934d501 100644
--- a/simple_live_core/lib/src/danmaku/bilibili_danmaku.dart
+++ b/simple_live_core/lib/src/danmaku/bilibili_danmaku.dart
@@ -21,6 +21,15 @@ class BiliBiliDanmakuArgs {
required this.serverHost,
required this.buvid,
});
+ @override
+ String toString() {
+ return json.encode({
+ "roomId": roomId,
+ "token": token,
+ "serverHost": serverHost,
+ "buvid": buvid,
+ });
+ }
}
class BiliBiliDanmaku implements LiveDanmaku {
diff --git a/simple_live_core/lib/src/danmaku/douyin_danmaku.dart b/simple_live_core/lib/src/danmaku/douyin_danmaku.dart
index 66be4ad1..a227c233 100644
--- a/simple_live_core/lib/src/danmaku/douyin_danmaku.dart
+++ b/simple_live_core/lib/src/danmaku/douyin_danmaku.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:convert';
import 'dart:io';
import 'package:simple_live_core/simple_live_core.dart';
@@ -17,6 +18,15 @@ class DouyinDanmakuArgs {
required this.userId,
required this.cookie,
});
+ @override
+ String toString() {
+ return json.encode({
+ "webRid": webRid,
+ "roomId": roomId,
+ "userId": userId,
+ "cookie": cookie,
+ });
+ }
}
class DouyinDanmaku implements LiveDanmaku {
diff --git a/simple_live_core/lib/src/danmaku/huya_danmaku.dart b/simple_live_core/lib/src/danmaku/huya_danmaku.dart
index 3017d835..d6b18b31 100644
--- a/simple_live_core/lib/src/danmaku/huya_danmaku.dart
+++ b/simple_live_core/lib/src/danmaku/huya_danmaku.dart
@@ -19,6 +19,14 @@ class HuyaDanmakuArgs {
required this.topSid,
required this.subSid,
});
+ @override
+ String toString() {
+ return json.encode({
+ "ayyuid": ayyuid,
+ "topSid": topSid,
+ "subSid": subSid,
+ });
+ }
}
class HuyaDanmaku implements LiveDanmaku {
diff --git a/simple_live_core/lib/src/douyin_site.dart b/simple_live_core/lib/src/douyin_site.dart
index c11b0fe3..44be5529 100644
--- a/simple_live_core/lib/src/douyin_site.dart
+++ b/simple_live_core/lib/src/douyin_site.dart
@@ -56,11 +56,16 @@ class DouyinSite implements LiveSite {
},
);
- var renderData = RegExp(r'9:\[\\"\$\\",\\"\$L13\\",null,(.*?)\]\\n')
- .firstMatch(result)
- ?.group(1) ??
- "";
- var renderDataJson = json.decode(renderData.trim().replaceAll("\\", ""));
+ var renderData =
+ RegExp(r'\{\\"pathname\\":\\"\/hot_live\\",\\"categoryData.*?\]\\n')
+ .firstMatch(result)
+ ?.group(0) ??
+ "";
+ var renderDataJson = json.decode(renderData
+ .trim()
+ .replaceAll('\\"', '"')
+ .replaceAll(r"\\", r"\")
+ .replaceAll(']\\n', ""));
for (var item in renderDataJson["categoryData"]) {
List subs = [];
@@ -238,11 +243,15 @@ class DouyinSite implements LiveSite {
},
);
- var renderData = RegExp(r'c:\[\\"\$\\",\\"\$L13\\",null,(.*?)\]\\n')
+ var renderData = RegExp(r'\{\\"state\\":\{\\"isLiveModal.*?\]\\n')
.firstMatch(result)
- ?.group(1) ??
+ ?.group(0) ??
"";
- var str = renderData.trim().replaceAll('\\"', '"').replaceAll(r"\\", r"\");
+ var str = renderData
+ .trim()
+ .replaceAll('\\"', '"')
+ .replaceAll(r"\\", r"\")
+ .replaceAll(']\\n', "");
var renderDataJson = json.decode(str);
return renderDataJson["state"];
diff --git a/simple_live_core/lib/src/douyu_site.dart b/simple_live_core/lib/src/douyu_site.dart
index 8974aada..2ead73c0 100644
--- a/simple_live_core/lib/src/douyu_site.dart
+++ b/simple_live_core/lib/src/douyu_site.dart
@@ -175,14 +175,19 @@ class DouyuSite implements LiveSite {
@override
Future getRoomDetail({required String roomId}) async {
var result = await HttpClient.instance.getJson(
- "https://m.douyu.com/$roomId/index.pageContext.json",
+ "https://www.douyu.com/betard/$roomId",
queryParameters: {},
header: {
- 'referer': 'https://m.douyu.com/$roomId',
+ 'referer': 'https://www.douyu.com/$roomId',
'user-agent':
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/114.0.0.0',
+ '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',
});
- var roomInfo = result["pageProps"]["room"]["roomInfo"]["roomInfo"];
+ Map roomInfo;
+ if (result is String) {
+ roomInfo = json.decode(result)["room"];
+ } else {
+ roomInfo = result["room"];
+ }
var jsEncResult = await HttpClient.instance.getText(
"https://www.douyu.com/swf_api/homeH5Enc?rids=$roomId",
@@ -195,18 +200,19 @@ class DouyuSite implements LiveSite {
var crptext = json.decode(jsEncResult)["data"]["room$roomId"].toString();
return LiveRoomDetail(
- cover: roomInfo["roomSrc"].toString(),
- online: parseHotNum(roomInfo["hn"].toString()),
- roomId: roomInfo["rid"].toString(),
- title: roomInfo["roomName"].toString(),
- userName: roomInfo["nickname"].toString(),
- userAvatar: roomInfo["avatar"].toString(),
- introduction: "",
- notice: roomInfo["notice"].toString(),
- status: roomInfo["isLive"] == 1,
- danmakuData: roomInfo["rid"].toString(),
- data: await getPlayArgs(crptext, roomInfo["rid"].toString()),
+ cover: roomInfo["room_pic"].toString(),
+ online: int.tryParse(roomInfo["room_biz_all"]["hot"].toString()) ?? 0,
+ roomId: roomInfo["room_id"].toString(),
+ title: roomInfo["room_name"].toString(),
+ userName: roomInfo["owner_name"].toString(),
+ userAvatar: roomInfo["owner_avatar"].toString(),
+ introduction: roomInfo["show_details"].toString(),
+ notice: "",
+ status: roomInfo["show_status"] == 1 && roomInfo["videoLoop"] != 1,
+ danmakuData: roomInfo["room_id"].toString(),
+ data: await getPlayArgs(crptext, roomInfo["room_id"].toString()),
url: "https://www.douyu.com/$roomId",
+ isRecord: roomInfo["videoLoop"] == 1,
);
}
@@ -279,12 +285,15 @@ class DouyuSite implements LiveSite {
var items = [];
for (var item in result["data"]["relateUser"]) {
+ var liveStatus =
+ (int.tryParse(item["anchorInfo"]["isLive"].toString()) ?? 0) == 1;
+ var roomType =
+ (int.tryParse(item["anchorInfo"]["roomType"].toString()) ?? 0);
var roomItem = LiveAnchorItem(
roomId: item["anchorInfo"]["rid"].toString(),
avatar: item["anchorInfo"]["avatar"].toString(),
userName: item["anchorInfo"]["nickName"].toString(),
- liveStatus:
- (int.tryParse(item["anchorInfo"]["isLive"].toString()) ?? 0) == 1,
+ liveStatus: liveStatus && roomType == 0,
);
items.add(roomItem);
}
diff --git a/simple_live_core/lib/src/model/live_room_detail.dart b/simple_live_core/lib/src/model/live_room_detail.dart
index cfa7bd08..65226d19 100644
--- a/simple_live_core/lib/src/model/live_room_detail.dart
+++ b/simple_live_core/lib/src/model/live_room_detail.dart
@@ -34,6 +34,9 @@ class LiveRoomDetail {
/// 弹幕附加信息
final dynamic danmakuData;
+ /// 是否录播
+ final bool isRecord;
+
/// 链接
final String url;
LiveRoomDetail({
@@ -49,6 +52,7 @@ class LiveRoomDetail {
this.data,
this.danmakuData,
required this.url,
+ this.isRecord = false,
});
@override
@@ -64,8 +68,9 @@ class LiveRoomDetail {
"notice": notice,
"status": status,
"data": data,
- "danmakuData": danmakuData,
+ "danmakuData": danmakuData.toString(),
"url": url,
+ "isRecord": isRecord,
});
}
}
diff --git a/simple_live_core/pubspec.yaml b/simple_live_core/pubspec.yaml
index 31e6dc52..48b35557 100644
--- a/simple_live_core/pubspec.yaml
+++ b/simple_live_core/pubspec.yaml
@@ -7,12 +7,12 @@ environment:
sdk: '>=2.19.1 <3.0.0'
dependencies:
- dio: 4.0.6
+ dio: ^5.3.2
logger: ^1.1.0
- web_socket_channel: ^2.3.0
+ web_socket_channel: ^2.4.0
html_unescape: ^2.0.0
- protobuf: ^3.0.0
- crypto: ^3.0.2
+ protobuf: ^3.1.0
+ crypto: ^3.0.3
brotli: ^0.6.0
dart_tars_protocol:
git: https://github.com/xiaoyaocz/dart_tars_protocol.git