diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg new file mode 100644 index 000000000..9f48027b0 --- /dev/null +++ b/assets/images/video/danmu_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/video/danmu_open.svg b/assets/images/video/danmu_open.svg new file mode 100644 index 000000000..24e8d7a99 --- /dev/null +++ b/assets/images/video/danmu_open.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/lib/http/api.dart b/lib/http/api.dart index 8aa62233b..445f61028 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -484,6 +484,9 @@ class Api { /// 激活buvid3 static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi'; + /// 获取字幕配置 + static const getSubtitleConfig = '/x/player/v2'; + /// 我的订阅 static const userSubFolder = '/x/v3/fav/folder/collected/list'; diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 362ff17ad..a53592830 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -45,10 +45,13 @@ class ApiInterceptor extends Interceptor { void onError(DioException err, ErrorInterceptorHandler handler) async { // 处理网络请求错误 // handler.next(err); - SmartDialog.showToast( - await dioError(err), - displayType: SmartToastType.onlyRefresh, - ); + String url = err.requestOptions.uri.toString(); + if (!url.contains('heartBeat')) { + SmartDialog.showToast( + await dioError(err), + displayType: SmartToastType.onlyRefresh, + ); + } super.onError(err, handler); } @@ -75,23 +78,24 @@ class ApiInterceptor extends Interceptor { } static Future checkConnect() async { - final ConnectivityResult connectivityResult = + final List connectivityResult = await Connectivity().checkConnectivity(); - switch (connectivityResult) { - case ConnectivityResult.mobile: - return '正在使用移动流量'; - case ConnectivityResult.wifi: - return '正在使用wifi'; - case ConnectivityResult.ethernet: - return '正在使用局域网'; - case ConnectivityResult.vpn: - return '正在使用代理网络'; - case ConnectivityResult.other: - return '正在使用其他网络'; - case ConnectivityResult.none: - return '未连接到任何网络'; - default: - return ''; + if (connectivityResult.contains(ConnectivityResult.mobile)) { + return '正在使用移动流量'; + } else if (connectivityResult.contains(ConnectivityResult.wifi)) { + return '正在使用wifi'; + } else if (connectivityResult.contains(ConnectivityResult.ethernet)) { + return '正在使用局域网'; + } else if (connectivityResult.contains(ConnectivityResult.vpn)) { + return '正在使用代理网络'; + } else if (connectivityResult.contains(ConnectivityResult.bluetooth)) { + return '正在使用蓝牙网络'; + } else if (connectivityResult.contains(ConnectivityResult.other)) { + return '正在使用其他网络'; + } else if (connectivityResult.contains(ConnectivityResult.none)) { + return '未连接到任何网络'; + } else { + return ''; } } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 73e8b6988..d43656b28 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -8,9 +8,11 @@ import '../models/model_rec_video_item.dart'; import '../models/user/fav_folder.dart'; import '../models/video/ai.dart'; import '../models/video/play/url.dart'; +import '../models/video/subTitile/result.dart'; import '../models/video_detail_res.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; +import '../utils/subtitle.dart'; import '../utils/wbi_sign.dart'; import 'api.dart'; import 'init.dart'; @@ -476,6 +478,25 @@ class VideoHttp { } } + static Future getSubtitle({int? cid, String? bvid}) async { + var res = await Request().get(Api.getSubtitleConfig, data: { + 'cid': cid, + 'bvid': bvid, + }); + try { + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SubTitlteModel.fromJson(res.data['data']), + }; + } else { + return {'status': false, 'data': [], 'msg': res.data['msg']}; + } + } catch (err) { + print(err); + } + } + // 视频排行 static Future getRankVideoList(int rid) async { try { @@ -498,4 +519,12 @@ class VideoHttp { return {'status': false, 'data': [], 'msg': err}; } } + + // 获取字幕内容 + static Future> getSubtitleContent(url) async { + var res = await Request().get('https:$url'); + final String content = SubTitleUtils.convertToWebVTT(res.data['body']); + final List body = res.data['body']; + return {'content': content, 'body': body}; + } } diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart new file mode 100644 index 000000000..117163517 --- /dev/null +++ b/lib/models/common/subtitle_type.dart @@ -0,0 +1,47 @@ +enum SubtitleType { + // 中文(中国) + zhCN, + // 中文(自动翻译) + aizh, + // 英语(自动生成) + aien, +} + +extension SubtitleTypeExtension on SubtitleType { + String get description { + switch (this) { + case SubtitleType.zhCN: + return '中文(中国)'; + case SubtitleType.aizh: + return '中文(自动翻译)'; + case SubtitleType.aien: + return '英语(自动生成)'; + } + } +} + +extension SubtitleIdExtension on SubtitleType { + String get id { + switch (this) { + case SubtitleType.zhCN: + return 'zh-CN'; + case SubtitleType.aizh: + return 'ai-zh'; + case SubtitleType.aien: + return 'ai-en'; + } + } +} + +extension SubtitleCodeExtension on SubtitleType { + int get code { + switch (this) { + case SubtitleType.zhCN: + return 1; + case SubtitleType.aizh: + return 2; + case SubtitleType.aien: + return 3; + } + } +} diff --git a/lib/models/video/subTitile/content.dart b/lib/models/video/subTitile/content.dart new file mode 100644 index 000000000..b18098a49 --- /dev/null +++ b/lib/models/video/subTitile/content.dart @@ -0,0 +1,20 @@ +class SubTitileContentModel { + double? from; + double? to; + int? location; + String? content; + + SubTitileContentModel({ + this.from, + this.to, + this.location, + this.content, + }); + + SubTitileContentModel.fromJson(Map json) { + from = json['from']; + to = json['to']; + location = json['location']; + content = json['content']; + } +} diff --git a/lib/models/video/subTitile/result.dart b/lib/models/video/subTitile/result.dart new file mode 100644 index 000000000..d3e32e558 --- /dev/null +++ b/lib/models/video/subTitile/result.dart @@ -0,0 +1,89 @@ +import 'package:get/get.dart'; +import '../../common/subtitle_type.dart'; + +class SubTitlteModel { + SubTitlteModel({ + this.aid, + this.bvid, + this.cid, + this.loginMid, + this.loginMidHash, + this.isOwner, + this.name, + this.subtitles, + }); + + int? aid; + String? bvid; + int? cid; + int? loginMid; + String? loginMidHash; + bool? isOwner; + String? name; + List? subtitles; + + factory SubTitlteModel.fromJson(Map json) => SubTitlteModel( + aid: json["aid"], + bvid: json["bvid"], + cid: json["cid"], + loginMid: json["login_mid"], + loginMidHash: json["login_mid_hash"], + isOwner: json["is_owner"], + name: json["name"], + subtitles: json["subtitle"] != null + ? json["subtitle"]["subtitles"] + .map((x) => SubTitlteItemModel.fromJson(x)) + .toList() + : [], + ); +} + +class SubTitlteItemModel { + SubTitlteItemModel({ + this.id, + this.lan, + this.lanDoc, + this.isLock, + this.subtitleUrl, + this.type, + this.aiType, + this.aiStatus, + this.title, + this.code, + this.content, + this.body, + }); + + int? id; + String? lan; + String? lanDoc; + bool? isLock; + String? subtitleUrl; + int? type; + int? aiType; + int? aiStatus; + String? title; + int? code; + String? content; + List? body; + + factory SubTitlteItemModel.fromJson(Map json) => + SubTitlteItemModel( + id: json["id"], + lan: json["lan"].replaceAll('-', ''), + lanDoc: json["lan_doc"], + isLock: json["is_lock"], + subtitleUrl: json["subtitle_url"], + type: json["type"], + aiType: json["ai_type"], + aiStatus: json["ai_status"], + title: json["lan_doc"], + code: SubtitleType.values + .firstWhereOrNull( + (element) => element.id.toString() == json["lan"]) + ?.index ?? + -1, + content: '', + body: [], + ); +} diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index e7822cd9c..a1f18113f 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -88,8 +88,10 @@ class HistoryController extends GetxController { // 观看历史暂停状态 Future historyStatus() async { var res = await UserHttp.historyStatus(); - pauseStatus.value = res.data['data']; - localCache.put(LocalCacheKey.historyPause, res.data['data']); + if (res.data['code'] == 0) { + pauseStatus.value = res.data['data']; + localCache.put(LocalCacheKey.historyPause, res.data['data']); + } } // 清空观看历史 diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index c3bad4b97..460c5648f 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/pages/main/index.dart'; @@ -102,7 +103,11 @@ class _MediaPageState extends State ], Obx(() => mediaController.userLogin.value ? favFolder(mediaController, context) - : const SizedBox()) + : const SizedBox()), + SizedBox( + height: MediaQuery.of(context).padding.bottom + + kBottomNavigationBarHeight, + ) ], ), ), diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index fe870873e..5c4ac14b5 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -20,6 +20,7 @@ import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/video_utils.dart'; import 'package:screen_brightness/screen_brightness.dart'; +import '../../../models/video/subTitile/content.dart'; import '../../../http/danmaku.dart'; import '../../../utils/id_utils.dart'; import 'widgets/header_control.dart'; @@ -93,7 +94,10 @@ class VideoDetailController extends GetxController late int cacheAudioQa; PersistentBottomSheetController? replyReplyBottomSheetCtr; + RxList subtitleContents = + [].obs; late bool enableRelatedVideo; + List subtitles = []; @override void onInit() { @@ -145,6 +149,7 @@ class VideoDetailController extends GetxController cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultValue: AudioQuality.hiRes.code); oid.value = IdUtils.bv2av(Get.parameters['bvid']!); + getSubtitle(); } showReplyReplyPanel() { @@ -251,6 +256,8 @@ class VideoDetailController extends GetxController /// 开启自动全屏时,在player初始化完成后立即传入headerControl plPlayerController.headerControl = headerControl; + + plPlayerController.subtitles.value = subtitles; } // 视频链接 @@ -388,6 +395,45 @@ class VideoDetailController extends GetxController : print('replyReplyBottomSheetCtr is null'); } + // 获取字幕配置 + Future getSubtitle() async { + var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value); + if (result['status']) { + if (result['data'].subtitles.isNotEmpty) { + subtitles = result['data'].subtitles; + if (subtitles.isNotEmpty) { + for (var i in subtitles) { + final Map res = await VideoHttp.getSubtitleContent( + i.subtitleUrl, + ); + i.content = res['content']; + i.body = res['body']; + } + } + } + return result['data']; + } + } + + // 获取字幕内容 + // Future getSubtitleContent(String url) async { + // var res = await Request().get('https:$url'); + // subtitleContents.value = res.data['body'].map((e) { + // return SubTitileContentModel.fromJson(e); + // }).toList(); + // setSubtitleContent(); + // } + + setSubtitleContent() { + plPlayerController.subtitleContent.value = ''; + plPlayerController.subtitles.value = subtitles; + } + + clearSubtitleContent() { + plPlayerController.subtitleContent.value = ''; + plPlayerController.subtitles.value = []; + } + /// 发送弹幕 void showShootDanmakuSheet() { final TextEditingController textController = TextEditingController(); diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 6714b8876..8114bdaf4 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -85,10 +85,9 @@ class VideoIntroController extends GetxController { if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) { lastPlayCid.value = videoDetail.value.pages!.first.cid!; } - // Get.find(tag: heroTag).tabs.value = [ - // '简介', - // '评论 ${result['data']!.stat!.reply}' - // ]; + final VideoDetailController videoDetailCtr = + Get.find(tag: heroTag); + videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}']; // 获取到粉丝数再返回 await queryUserStat(); } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 9c1b7db0d..831491f60 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -15,6 +15,7 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; +import '../widgets/expandable_section.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; @@ -137,6 +138,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late String memberHeroTag; late bool enableAi; bool isProcessing = false; + RxBool isExpand = false.obs; void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -212,13 +214,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { // 视频介绍 showIntroDetail() { feedBack(); - showBottomSheet( - context: context, - enableDrag: true, - builder: (BuildContext context) { - return IntroDetail(videoDetail: widget.videoDetail!); - }, - ); + isExpand.value = !(isExpand.value); } // 用户主页 @@ -330,6 +326,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ], ), + /// 视频简介 + Obx( + () => ExpandedSection( + expand: isExpand.value, + begin: 0, + end: 1, + child: IntroDetail(videoDetail: widget.videoDetail!), + ), + ), + /// 点赞收藏转发 actionGrid(context, videoIntroController), // 合集 @@ -438,6 +444,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { margin: const EdgeInsets.only(top: 6, bottom: 4), height: constraints.maxWidth / 5 * 0.8, child: GridView.count( + physics: const NeverScrollableScrollPhysics(), primary: false, padding: EdgeInsets.zero, crossAxisCount: 5, @@ -451,12 +458,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { selectStatus: videoIntroController.hasLike.value, text: widget.videoDetail!.stat!.like!.toString()), ), - // ActionItem( - // icon: const Icon(FontAwesomeIcons.clock), - // onTap: () => videoIntroController.actionShareVideo(), - // selectStatus: false, - // loadingStatus: loadingStatus, - // text: '稍后再看'), Obx( () => ActionItem( icon: const Icon(FontAwesomeIcons.b), @@ -477,10 +478,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), ), ActionItem( - icon: const Icon(FontAwesomeIcons.comment), - onTap: () => videoDetailCtr.tabCtr.animateTo(1), + icon: const Icon(FontAwesomeIcons.clock), + onTap: () => videoIntroController.actionShareVideo(), selectStatus: false, - text: widget.videoDetail!.stat!.reply!.toString(), + text: '稍后看', ), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index c74e27ee1..1e9bb8424 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -1,16 +1,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; -import 'package:pilipala/common/widgets/stat/danmu.dart'; -import 'package:pilipala/common/widgets/stat/view.dart'; -import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; -Box localCache = GStrorage.localCache; -late double sheetHeight; - class IntroDetail extends StatelessWidget { const IntroDetail({ super.key, @@ -20,105 +14,39 @@ class IntroDetail extends StatelessWidget { @override Widget build(BuildContext context) { - sheetHeight = localCache.get('sheetHeight'); - return Container( - color: Theme.of(context).colorScheme.background, - padding: EdgeInsets.only( - left: 14, - right: 14, - bottom: MediaQuery.of(context).padding.bottom + 20), - height: sheetHeight, + return SizedBox( + width: double.infinity, + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), child: Column( - children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); + SmartDialog.showToast('已复制'); + }, + child: Text( + videoDetail!.bvid!, + style: TextStyle( + fontSize: 13, color: Theme.of(context).colorScheme.primary), ), ), - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 6), - Row( - children: [ - StatView( - theme: 'gray', - view: videoDetail!.stat!.view, - size: 'medium', - ), - const SizedBox(width: 10), - StatDanMu( - theme: 'gray', - danmu: videoDetail!.stat!.danmaku, - size: 'medium', - ), - const SizedBox(width: 10), - Text( - Utils.dateFormat(videoDetail!.pubdate, - formatType: 'detail'), - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, - ), - ), - ], - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: SelectableRegion( - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.bvid!, - style: const TextStyle(fontSize: 13), - ), - const SizedBox(height: 4), - Text.rich( - style: const TextStyle( - height: 1.4, - // fontSize: 13, - ), - TextSpan( - children: [ - buildContent(context, videoDetail!), - ], - ), - ), - ], - ), - ), - ), - ], - ), + const SizedBox(height: 4), + Text.rich( + style: const TextStyle(height: 1.4), + TextSpan( + children: [ + buildContent(context, videoDetail!), + ], ), - ) + ), ], - )); + ), + ), + ); } InlineSpan buildContent(BuildContext context, content) { diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index b07a61687..38203f7ef 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -148,35 +148,14 @@ class _VideoReplyPanelState extends State floating: true, delegate: _MySliverPersistentHeaderDelegate( child: Container( - height: 45, - padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - border: Border( - bottom: BorderSide( - color: Theme.of(context) - .colorScheme - .outline - .withOpacity(0.1)), - ), - ), + height: 40, + padding: const EdgeInsets.fromLTRB(12, 6, 6, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Obx( - () => AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - '共${_videoReplyController.count.value}条回复', - key: ValueKey( - _videoReplyController.count.value), - ), - ), + Text( + '${_videoReplyController.sortTypeLabel.value}评论', + style: const TextStyle(fontSize: 13), ), SizedBox( height: 35, @@ -184,10 +163,12 @@ class _VideoReplyPanelState extends State onPressed: () => _videoReplyController.queryBySort(), icon: const Icon(Icons.sort, size: 16), - label: Obx(() => Text( - _videoReplyController.sortTypeLabel.value, - style: const TextStyle(fontSize: 13), - )), + label: Obx( + () => Text( + _videoReplyController.sortTypeLabel.value, + style: const TextStyle(fontSize: 13), + ), + ), ), ) ], @@ -329,8 +310,8 @@ class _VideoReplyPanelState extends State class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { _MySliverPersistentHeaderDelegate({required this.child}); - final double _minExtent = 45; - final double _maxExtent = 45; + final double _minExtent = 40; + final double _maxExtent = 40; final Widget child; @override diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index f9f695d48..e79b6159e 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -498,7 +498,7 @@ InlineSpan buildContent( return str; }); } - // content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); + content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); content.message = content.message .replaceAll('&', '&') .replaceAll('<', '<') diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 1dff90601..6958a62d7 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:floating/floating.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; @@ -211,6 +212,7 @@ class _VideoDetailPageState extends State videoIntroController.isPaused = true; plPlayerController!.removeStatusLister(playerListener); plPlayerController!.pause(); + vdCtr.clearSubtitleContent(); } setState(() => isShowing = false); super.didPushNext(); @@ -221,7 +223,10 @@ class _VideoDetailPageState extends State void didPopNext() async { if (plPlayerController != null && plPlayerController!.videoPlayerController != null) { - setState(() => isShowing = true); + setState(() { + vdCtr.setSubtitleContent(); + isShowing = true; + }); } vdCtr.isFirstTime = false; final bool autoplay = autoPlayEnable; @@ -319,62 +324,74 @@ class _VideoDetailPageState extends State ), ), ), - child: Row( - children: [ - const SizedBox(width: 20), - Expanded( - child: TabBar( - controller: vdCtr.tabCtr, - dividerColor: Colors.transparent, - tabs: vdCtr.tabs.map((String name) => Tab(text: name)).toList(), + child: Material( + child: Row( + children: [ + Flexible( + flex: 1, + child: Obx( + () => TabBar( + padding: EdgeInsets.zero, + controller: vdCtr.tabCtr, + labelStyle: const TextStyle(fontSize: 13), + labelPadding: + const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度 + dividerColor: Colors.transparent, + tabs: vdCtr.tabs + .map( + (String name) => Tab(text: name), + ) + .toList(), + ), + ), ), - ), - SizedBox( - width: 220, - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), + Flexible( + flex: 1, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => vdCtr.showShootDanmakuSheet(), + child: + const Text('发弹幕', style: TextStyle(fontSize: 12)), + ), ), - onPressed: () => vdCtr.showShootDanmakuSheet(), - child: const Text('发弹幕', style: TextStyle(fontSize: 12)), - ), - ), - const SizedBox(width: 4), - SizedBox( - width: 34, - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), + SizedBox( + width: 38, + height: 38, + child: Obx( + () => IconButton( + onPressed: () { + plPlayerController?.isOpenDanmu.value = + !(plPlayerController?.isOpenDanmu.value ?? + false); + }, + icon: !(plPlayerController?.isOpenDanmu.value ?? + false) + ? SvgPicture.asset( + 'assets/images/video/danmu_close.svg', + ) + : SvgPicture.asset( + 'assets/images/video/danmu_open.svg', + // ignore: deprecated_member_use + color: + Theme.of(context).colorScheme.primary, + ), + ), + ), ), - onPressed: () { - plPlayerController?.isOpenDanmu.value = - !(plPlayerController?.isOpenDanmu.value ?? false); - }, - child: Obx(() => Text( - '弹', - style: TextStyle( - fontSize: 12, - color: (plPlayerController?.isOpenDanmu.value ?? - false) - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - ), - )), - ), + const SizedBox(width: 14), + ], ), - const SizedBox(width: 14), - ], - ), - ), - ), - ], + )), + ], + ), ), ); diff --git a/lib/pages/video/detail/widgets/app_bar.dart b/lib/pages/video/detail/widgets/app_bar.dart index 17f4bec7d..efc0b593b 100644 --- a/lib/pages/video/detail/widgets/app_bar.dart +++ b/lib/pages/video/detail/widgets/app_bar.dart @@ -17,12 +17,16 @@ class ScrollAppBar extends StatelessWidget { Widget build(BuildContext context) { final double statusBarHeight = MediaQuery.of(context).padding.top; final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + double scrollDistance = scrollVal; + if (scrollVal > videoHeight - kToolbarHeight) { + scrollDistance = videoHeight - kToolbarHeight; + } return Positioned( - top: -videoHeight + scrollVal + kToolbarHeight + 0.5, + top: -videoHeight + scrollDistance + kToolbarHeight + 0.5, left: 0, right: 0, child: Opacity( - opacity: scrollVal / (videoHeight - kToolbarHeight), + opacity: scrollDistance / (videoHeight - kToolbarHeight), child: Container( height: statusBarHeight + kToolbarHeight, color: Theme.of(context).colorScheme.background, diff --git a/lib/pages/video/detail/widgets/expandable_section.dart b/lib/pages/video/detail/widgets/expandable_section.dart index afa68cc9f..69e73e206 100644 --- a/lib/pages/video/detail/widgets/expandable_section.dart +++ b/lib/pages/video/detail/widgets/expandable_section.dart @@ -32,28 +32,14 @@ class _ExpandedSectionState extends State _runExpandCheck(); } - ///Setting up the animation - // void prepareAnimations() { - // expandController = AnimationController( - // vsync: this, duration: const Duration(milliseconds: 500)); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); - // } - void prepareAnimations() { expandController = AnimationController( vsync: this, duration: const Duration(milliseconds: 400)); Animation curve = CurvedAnimation( parent: expandController, - curve: Curves.fastOutSlowIn, + curve: Curves.linear, ); animation = Tween(begin: widget.begin, end: widget.end).animate(curve); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); } void _runExpandCheck() { @@ -67,7 +53,9 @@ class _ExpandedSectionState extends State @override void didUpdateWidget(ExpandedSection oldWidget) { super.didUpdateWidget(oldWidget); - _runExpandCheck(); + if (widget.expand != oldWidget.expand) { + _runExpandCheck(); + } } @override diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 858ca2df5..b0b7db179 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -344,6 +344,56 @@ class _HeaderControlState extends State { ); } + /// 选择字幕 + void showSubtitleDialog() async { + int tempThemeValue = widget.controller!.subTitleCode.value; + int len = widget.videoDetailCtr!.subtitles.length; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('选择字幕'), + contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18), + content: StatefulBuilder(builder: (context, StateSetter setState) { + return len == 0 + ? const SizedBox( + height: 60, + child: Center( + child: Text('没有字幕'), + ), + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + value: -1, + title: const Text('关闭弹幕'), + groupValue: tempThemeValue, + onChanged: (value) { + tempThemeValue = value!; + widget.controller?.toggleSubtitle(value); + Get.back(); + }, + ), + ...widget.videoDetailCtr!.subtitles + .map((e) => RadioListTile( + value: e.code, + title: Text(e.title), + groupValue: tempThemeValue, + onChanged: (value) { + tempThemeValue = value!; + widget.controller?.toggleSubtitle(value); + Get.back(); + }, + )) + .toList(), + ], + ); + }), + ); + }); + } + /// 选择倍速 void showSetSpeedSheet() { final double currentSpeed = widget.controller!.playbackSpeed; @@ -1115,6 +1165,31 @@ class _HeaderControlState extends State { ), SizedBox(width: buttonSpace), ], + + /// 字幕 + // SizedBox( + // width: 34, + // height: 34, + // child: IconButton( + // style: ButtonStyle( + // padding: MaterialStateProperty.all(EdgeInsets.zero), + // ), + // onPressed: () => showSubtitleDialog(), + // icon: const Icon( + // Icons.closed_caption_off, + // size: 22, + // ), + // ), + // ), + ComBtn( + icon: const Icon( + Icons.closed_caption_off, + size: 22, + color: Colors.white, + ), + fuc: () => showSubtitleDialog(), + ), + SizedBox(width: buttonSpace), Obx( () => SizedBox( width: 45, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index aaded67d7..b385fca8c 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -21,6 +21,8 @@ import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:status_bar_control/status_bar_control.dart'; import 'package:universal_platform/universal_platform.dart'; +import '../../models/video/subTitile/content.dart'; +import '../../models/video/subTitile/result.dart'; // import 'package:wakelock_plus/wakelock_plus.dart'; Box videoStorage = GStrorage.video; @@ -73,6 +75,8 @@ class PlPlayerController { final Rx _doubleSpeedStatus = false.obs; final Rx _controlsLock = false.obs; final Rx _isFullScreen = false.obs; + final Rx _subTitleOpen = false.obs; + final Rx _subTitleCode = (-1).obs; // 默认投稿视频格式 static Rx _videoType = 'archive'.obs; @@ -118,6 +122,7 @@ class PlPlayerController { PreferredSizeWidget? headerControl; PreferredSizeWidget? bottomControl; Widget? danmuWidget; + late RxList subtitles; /// 数据加载监听 Stream get onDataStatusChanged => dataStatus.status.stream; @@ -147,6 +152,11 @@ class PlPlayerController { Rx get mute => _mute; Stream get onMuteChanged => _mute.stream; + /// 字幕开启状态 + Rx get subTitleOpen => _subTitleOpen; + Rx get subTitleCode => _subTitleCode; + // Stream get onSubTitleOpenChanged => _subTitleOpen.stream; + /// [videoPlayerController] instace of Player Player? get videoPlayerController => _videoPlayerController; @@ -231,6 +241,10 @@ class PlPlayerController { // 播放顺序相关 PlayRepeat playRepeat = PlayRepeat.pause; + RxList subtitleContents = + [].obs; + RxString subtitleContent = ''.obs; + void updateSliderPositionSecond() { int newSecond = _sliderPosition.value.inSeconds; if (sliderPositionSeconds.value != newSecond) { @@ -350,6 +364,8 @@ class PlPlayerController { bool enableHeart = true, // 是否首次加载 bool isFirstTime = true, + // 是否开启字幕 + bool enableSubTitle = false, }) async { try { _autoPlay = autoplay; @@ -364,7 +380,9 @@ class PlPlayerController { _cid = cid; _enableHeart = enableHeart; _isFirstTime = isFirstTime; - + _subTitleOpen.value = enableSubTitle; + subtitles = [].obs; + subtitleContent.value = ''; if (_videoPlayerController != null && _videoPlayerController!.state.playing) { await pause(notify: false); @@ -575,6 +593,8 @@ class PlPlayerController { _sliderPosition.value = event; updateSliderPositionSecond(); } + querySubtitleContent( + videoPlayerController!.state.position.inSeconds.toDouble()); /// 触发回调事件 for (var element in _positionListeners) { @@ -609,6 +629,10 @@ class PlPlayerController { const Duration(seconds: 1), () => videoPlayerServiceHandler.onPositionChange(event)); }), + + // onSubTitleOpenChanged.listen((bool event) { + // toggleSubtitle(event ? subTitleCode.value : -1); + // }) ], ); } @@ -1047,12 +1071,61 @@ class PlPlayerController { } } + /// 字幕 + void toggleSubtitle(int code) { + _subTitleOpen.value = code != -1; + _subTitleCode.value = code; + // if (code == -1) { + // // 关闭字幕 + // _subTitleOpen.value = false; + // _subTitleCode.value = code; + // _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no()); + // return; + // } + // final SubTitlteItemModel? subtitle = subtitles?.firstWhereOrNull( + // (element) => element.code == code, + // ); + // _subTitleOpen.value = true; + // _subTitleCode.value = code; + // _videoPlayerController?.setSubtitleTrack( + // SubtitleTrack.data( + // subtitle!.content!, + // title: subtitle.title, + // language: subtitle.lan, + // ), + // ); + } + + void querySubtitleContent(double progress) { + if (subTitleCode.value == -1) { + subtitleContent.value = ''; + return; + } + if (subtitles.isEmpty) { + return; + } + final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull( + (element) => element.code == subTitleCode.value, + ); + if (subtitle != null && subtitle.body!.isNotEmpty) { + for (var content in subtitle.body!) { + if (progress >= content['from']! && progress <= content['to']!) { + subtitleContent.value = content['content']!; + return; + } + } + } + } + setPlayRepeat(PlayRepeat type) { playRepeat = type; videoStorage.put(VideoBoxKey.playRepeat, type.value); } Future dispose({String type = 'single'}) async { + print('dispose'); + print('dispose: ${playerCount.value}'); + // 每次减1,最后销毁 if (type == 'single' && playerCount.value > 1) { _playerCount.value -= 1; @@ -1062,6 +1135,7 @@ class PlPlayerController { } _playerCount.value = 0; try { + print('dispose dispose ---------'); _timer?.cancel(); _timerForVolume?.cancel(); _timerForGettingVolume?.cancel(); diff --git a/lib/plugin/pl_player/models/bottom_control_type.dart b/lib/plugin/pl_player/models/bottom_control_type.dart index 599f6e4fe..739e1d383 100644 --- a/lib/plugin/pl_player/models/bottom_control_type.dart +++ b/lib/plugin/pl_player/models/bottom_control_type.dart @@ -7,4 +7,5 @@ enum BottomControlType { fit, speed, fullscreen, + custom, } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 4b6627b85..be24b1050 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -34,6 +35,8 @@ class PLVideoPlayer extends StatefulWidget { this.bottomControl, this.danmuWidget, this.bottomList, + this.customWidget, + this.customWidgets, super.key, }); @@ -42,6 +45,10 @@ class PLVideoPlayer extends StatefulWidget { final PreferredSizeWidget? bottomControl; final Widget? danmuWidget; final List? bottomList; + // List or Widget + + final Widget? customWidget; + final List? customWidgets; @override State createState() => _PLVideoPlayerState(); @@ -310,7 +317,7 @@ class _PLVideoPlayerState extends State ), }; final List list = []; - var userSpecifyItem = widget.bottomList ?? + List userSpecifyItem = widget.bottomList ?? [ BottomControlType.playOrPause, BottomControlType.time, @@ -319,7 +326,16 @@ class _PLVideoPlayerState extends State BottomControlType.fullscreen, ]; for (var i = 0; i < userSpecifyItem.length; i++) { - list.add(videoProgressWidgets[userSpecifyItem[i]]!); + if (userSpecifyItem[i] == BottomControlType.custom) { + if (widget.customWidget != null && widget.customWidget is Widget) { + list.add(widget.customWidget!); + } + if (widget.customWidgets != null && widget.customWidgets!.isNotEmpty) { + list.addAll(widget.customWidgets!); + } + } else { + list.add(videoProgressWidgets[userSpecifyItem[i]]!); + } } return list; } @@ -346,6 +362,7 @@ class _PLVideoPlayerState extends State children: [ Obx( () => Video( + key: ValueKey(_.videoFit.value), controller: videoController, controls: NoVideoControls, pauseUponEnteringBackgroundMode: !enableBackgroundPlay, @@ -563,6 +580,45 @@ class _PLVideoPlayerState extends State if (widget.danmuWidget != null) Positioned.fill(top: 4, child: widget.danmuWidget!), + /// 开启且有字幕时展示 + Stack( + children: [ + Positioned( + left: 0, + right: 0, + bottom: 30, + child: Align( + alignment: Alignment.center, + child: Obx( + () => Visibility( + visible: widget.controller.subTitleCode.value != -1, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: widget.controller.subtitleContent.value != '' + ? Colors.black.withOpacity(0.6) + : Colors.transparent, + ), + padding: widget.controller.subTitleCode.value != -1 + ? const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ) + : EdgeInsets.zero, + child: Text( + widget.controller.subtitleContent.value, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + )), + ), + ), + ), + ], + ), + /// 手势 Positioned.fill( left: 16, @@ -674,13 +730,16 @@ class _PLVideoPlayerState extends State _distance.value = dy; } else { // 右边区域 👈 - final double level = (_.isFullScreen.value - ? Get.size.height - : screenWidth * 9 / 16) * - 3; - final double volume = _volumeValue.value - delta / level; - final double result = volume.clamp(0.0, 1.0); - setVolume(result); + EasyThrottle.throttle( + 'setVolume', const Duration(milliseconds: 20), () { + final double level = (_.isFullScreen.value + ? Get.size.height + : screenWidth * 9 / 16); + final double volume = _volumeValue.value - + double.parse(delta.toStringAsFixed(1)) / level; + final double result = volume.clamp(0.0, 1.0); + setVolume(result); + }); } }, onVerticalDragEnd: (DragEndDetails details) {}, diff --git a/lib/utils/subtitle.dart b/lib/utils/subtitle.dart new file mode 100644 index 000000000..452be5426 --- /dev/null +++ b/lib/utils/subtitle.dart @@ -0,0 +1,32 @@ +class SubTitleUtils { + // 格式整理 + static String convertToWebVTT(List jsonData) { + String webVTTContent = 'WEBVTT FILE\n\n'; + + for (int i = 0; i < jsonData.length; i++) { + final item = jsonData[i]; + double from = item['from'] as double; + double to = item['to'] as double; + int sid = (item['sid'] ?? 0) as int; + String content = item['content'] as String; + + webVTTContent += '$sid\n'; + webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n'; + webVTTContent += '$content\n\n'; + } + + return webVTTContent; + } + + static String formatTime(num seconds) { + final String h = (seconds / 3600).floor().toString().padLeft(2, '0'); + final String m = (seconds % 3600 / 60).floor().toString().padLeft(2, '0'); + final String s = (seconds % 60).floor().toString().padLeft(2, '0'); + final String ms = + (seconds * 1000 % 1000).floor().toString().padLeft(3, '0'); + if (h == '00') { + return "$m:$s.$ms"; + } + return "$h:$m:$s.$ms"; + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3e5f82f76..8af2f922b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -24,7 +24,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) - ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 8972ef459..695505d77 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: "direct main" description: name: animations - sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5" + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.10" + version: "2.0.11" appscheme: dependency: "direct main" description: @@ -101,10 +101,10 @@ packages: dependency: "direct main" description: name: audio_video_progress_bar - sha256: "3384875247cdbea748bd9ae8330631cd06a6cabfcda4945d45c9b406da92bc66" + sha256: ccc7d7b83d2a16c52d4a7fb332faabd1baa053fb0e4c16815aefd3945ab33b81 url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" auto_orientation: dependency: "direct main" description: @@ -157,10 +157,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.7" + version: "2.4.8" build_runner_core: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: "direct main" description: name: catcher_2 - sha256: ca94d45ffb52bf4b16a425cdff6734ae8443d36d5f06c276f1c2a593120b11ed + sha256: "9cf33d2befd10058374e5fc6177577fdd938d73d9c06810de81cf91311a7ce98" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.2.3" characters: dependency: transitive description: @@ -269,18 +269,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b" + sha256: e9feae83b1849f61bad9f6f33ee00646e3410d54ce0821e02f262f9901dad3c9 url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.2" + version: "6.0.1" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.4" + version: "2.0.0" convert: dependency: transitive description: @@ -373,10 +373,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" url: "https://pub.flutter-io.cn" source: hosted - version: "5.4.0" + version: "5.4.1" dio_cookie_manager: dependency: "direct main" description: @@ -413,10 +413,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d url: "https://pub.flutter-io.cn" source: hosted - version: "1.6.8" + version: "1.7.0" easy_debounce: dependency: "direct main" description: @@ -469,10 +469,10 @@ packages: dependency: "direct main" description: name: extended_nested_scroll_view - sha256: "444a6f883e6e07effc7639e69a309e1fb491b6c19b095e9281714a51ace2b384" + sha256: "835580d40c2c62b448bd14adecd316acba469ba61f1510ef559d17668a85e777" url: "https://pub.flutter-io.cn" source: hosted - version: "6.1.2" + version: "6.2.1" fake_async: dependency: transitive description: @@ -592,10 +592,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -1067,50 +1067,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78" + sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44" url: "https://pub.flutter-io.cn" source: hosted - version: "11.1.0" + version: "11.3.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6" + sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" url: "https://pub.flutter-io.cn" source: hosted - version: "12.0.1" + version: "12.0.5" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306" + sha256: "92861b0f0c2443dd8898398c2baa4f1ae925109b5909ae4a17d0108a6a788932" url: "https://pub.flutter-io.cn" source: hosted - version: "9.2.0" + version: "9.4.2" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df" + sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.0+2" + version: "0.1.1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1 + sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78" url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.2" + version: "4.2.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004" + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.0" + version: "0.2.1" petitparser: dependency: transitive description: @@ -1536,26 +1536,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1640,10 +1640,10 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf" + sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413 url: "https://pub.flutter-io.cn" source: hosted - version: "4.4.2" + version: "4.5.0" webview_flutter_android: dependency: transitive description: @@ -1664,10 +1664,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: accdaaa49a2aca2dc3c3230907988954cdd23fed0a19525d6c9789d380f4dc76 + sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c" url: "https://pub.flutter-io.cn" source: hosted - version: "3.9.4" + version: "3.10.2" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5c25f0442..5e19b56b2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.21+1021 environment: - sdk: ">=2.19.6 <3.0.0" + sdk: ">=3.0.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -36,15 +36,15 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.5 # 动态取色 - dynamic_color: ^1.6.8 + dynamic_color: ^1.7.0 get: ^4.6.5 # 网络 - dio: ^5.3.3 + dio: ^5.4.1 cookie_jar: ^4.0.8 dio_cookie_manager: ^3.1.1 - connectivity_plus: ^4.0.1 + connectivity_plus: ^6.0.1 dio_http2_adapter: ^2.3.1+1 # 图片 @@ -58,17 +58,17 @@ dependencies: hive_flutter: ^1.1.0 # 设备信息 - device_info_plus: ^9.0.2 + device_info_plus: ^9.0.0 # 权限 - permission_handler: ^11.0.1 + permission_handler: ^11.3.0 # 分享 share_plus: ^7.0.2 # cookie 管理 webview_cookie_manager: ^2.0.6 # 浏览器 - webview_flutter: ^4.2.2 + webview_flutter: ^4.5.0 # 解决sliver滑动不同步 - extended_nested_scroll_view: ^6.1.2 + extended_nested_scroll_view: ^6.2.1 # 上拉加载 loading_more_list: ^6.0.0 # 下拉刷新 @@ -99,15 +99,15 @@ dependencies: wakelock_plus: ^1.1.1 universal_platform: ^1.0.0+1 # 进度条 - audio_video_progress_bar: ^2.0.1 + audio_video_progress_bar: ^2.0.2 auto_orientation: ^2.3.1 protobuf: ^3.0.0 - animations: ^2.0.8 + animations: ^2.0.11 # 获取appx信息 package_info_plus: ^4.1.0 url_launcher: ^6.1.14 - flutter_svg: ^2.0.7 + flutter_svg: ^2.0.10+1 # 防抖节流 easy_debounce: ^2.0.3 # 高帧率 @@ -137,7 +137,7 @@ dependencies: uuid: ^3.0.7 scrollable_positioned_list: ^0.3.8 nil: ^1.1.1 - catcher_2: ^1.1.0 + catcher_2: ^1.2.3 logger: ^2.0.2+1 path: 1.8.3 # 电池优化 @@ -159,7 +159,7 @@ dev_dependencies: # url: https://github.com/nvi9/flutter_launcher_icons.git # ref: e045d40 hive_generator: ^2.0.0 - build_runner: ^2.3.3 + build_runner: ^2.4.8 flutter_launcher_icons: android: true @@ -191,6 +191,7 @@ flutter: - assets/images/lv/ - assets/images/logo/ - assets/images/live/ + - assets/images/video/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware