diff --git a/lib/http/video.dart b/lib/http/video.dart index b2b59fb8..89c05b26 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -1005,23 +1005,18 @@ class VideoHttp { */ return { 'status': true, - 'data': data['subtitle']['subtitles'], + 'subtitles': data['subtitle']['subtitles'], 'view_points': data['view_points'], // 'last_play_time': data['last_play_time'], 'last_play_cid': data['last_play_cid'], 'interaction': data['interaction'], }; } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; + return {'status': false, 'msg': res.data['message']}; } } - static Future vttSubtitles(List subtitlesJson) async { - if (subtitlesJson.isEmpty) { - return []; - } - List> subtitlesVtt = []; - + static Future vttSubtitles(subtile) async { String subtitleTimecode(num seconds) { int h = (seconds / 3600).floor(); int m = ((seconds % 3600) / 60).floor(); @@ -1039,56 +1034,12 @@ class VideoHttp { }); } - for (var i in subtitlesJson) { - var res = - await Request().get("https://${i['subtitle_url'].split('//')[1]}"); - /* - { - "font_size": 0.4, - "font_color": "#FFFFFF", - "background_alpha": 0.5, - "background_color": "#9C27B0", - "Stroke": "none", - "type": "AIsubtitle", - "lang": "zh", - "version": "v1.6.0.4", - "body": [ - { - "from": 0.5, - "to": 1.58, - "sid": 1, - "location": 2, - "content": "很多人可能不知道", - "music": 0.0 - }, - ……, - { - "from": 558.629, - "to": 560.22, - "sid": 280, - "location": 2, - "content": "我们下期再见", - "music": 0.0 - } - ] - } - */ - if (res.data != null && res.data?['body'] is List) { - String vttData = await compute(processList, res.data['body'] as List); - subtitlesVtt.add({ - 'language': i['lan'], - 'title': i['lan_doc'], - 'text': vttData, - }); - } else { - // SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}"); - debugPrint('字幕${i['lan_doc']}加载失败, ${res.data['message']}'); - } + var res = await Request().get("https:${subtile['subtitle_url']}"); + + if (res.data?['body'] is List) { + return await compute(processList, res.data['body'] as List); } - if (subtitlesVtt.isNotEmpty) { - subtitlesVtt.insert(0, {'language': '', 'title': '关闭字幕', 'text': ""}); - } - return subtitlesVtt; + return null; } // 视频排行 diff --git a/lib/models/video/play/subtitle.dart b/lib/models/video/play/subtitle.dart index 0c92b544..75243d06 100644 --- a/lib/models/video/play/subtitle.dart +++ b/lib/models/video/play/subtitle.dart @@ -11,7 +11,7 @@ extension SubtitlePreferenceDesc on SubtitlePreference { extension SubtitlePreferenceCode on SubtitlePreference { static final List _codeList = ['off', 'on', 'withoutAi']; - get code => _codeList[index]; + String get code => _codeList[index]; static SubtitlePreference? fromCode(String code) { final index = _codeList.indexOf(code); diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index aa93a36d..ef50b9b2 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -41,6 +41,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/video_utils.dart'; import 'package:get/get_navigation/src/dialog/dialog_route.dart'; import 'package:hive/hive.dart'; +import 'package:media_kit/media_kit.dart'; import '../../../utils/id_utils.dart'; import 'widgets/header_control.dart'; @@ -1068,8 +1069,6 @@ class VideoDetailController extends GetxController ), segmentList: segmentProgressList, viewPointList: viewPointList, - vttSubtitles: _vttSubtitles, - vttSubtitlesIndex: vttSubtitlesIndex, showVP: showVP, dmTrend: dmTrend, // 硬解 @@ -1092,12 +1091,13 @@ class VideoDetailController extends GetxController if (videoState.value is! Success) { videoState.value = LoadingState.success(null); } + setSubtitle(vttSubtitlesIndex.value); }, ); initSkip(); - if (vttSubtitlesIndex == null) { + if (vttSubtitlesIndex.value == -1) { _getSubtitle(); } @@ -1358,43 +1358,47 @@ class VideoDetailController extends GetxController } } - dynamic subtitles; - late List> _vttSubtitles = >[]; - int? vttSubtitlesIndex; + RxList subtitles = [].obs; + late final Map _vttSubtitles = {}; + late final RxInt vttSubtitlesIndex = (-1).obs; late bool showVP = true; void _getSubtitle() { _vttSubtitles.clear(); viewPointList.clear(); - _querySubtitles().then((value) { - if (_vttSubtitles.isNotEmpty) { - String preference = setting.get( - SettingBoxKey.subtitlePreference, - defaultValue: SubtitlePreference.values.first.code, - ); - vttSubtitlesIndex = 0; - if (preference == 'on') { - vttSubtitlesIndex = 1; - } else if (preference == 'withoutAi') { - for (int i = 1; i < _vttSubtitles.length; i++) { - if (_vttSubtitles[i]['language']!.startsWith('ai')) { - continue; - } - vttSubtitlesIndex = i; - break; - } - } - if (plPlayerController.vttSubtitles.isEmpty) { - plPlayerController.vttSubtitles.value = _vttSubtitles; - if (vttSubtitlesIndex != null) { - plPlayerController.vttSubtitlesIndex.value = vttSubtitlesIndex!; - if (vttSubtitlesIndex != 0) { - plPlayerController.setSubtitle(vttSubtitlesIndex!); - } - } - } + _querySubtitles(); + } + + // 设定字幕轨道 + setSubtitle(int index) async { + if (index <= 0) { + plPlayerController.videoPlayerController + ?.setSubtitleTrack(SubtitleTrack.no()); + vttSubtitlesIndex.value = index; + return; + } + + void setSub(subtitle) { + plPlayerController.videoPlayerController?.setSubtitleTrack( + SubtitleTrack.data( + subtitle, + title: subtitles[index - 1]['lan_doc'], + language: subtitles[index - 1]['lan'], + ), + ); + vttSubtitlesIndex.value = index; + } + + String? subtitle = _vttSubtitles[index - 1]; + if (subtitle != null) { + setSub(subtitle); + } else { + var result = await VideoHttp.vttSubtitles(subtitles[index - 1]); + if (result != null) { + _vttSubtitles[index - 1] = result; + setSub(result); } - }); + } } // interactive video @@ -1484,15 +1488,26 @@ class VideoDetailController extends GetxController } } - if (res["data"] is List && res["data"].isNotEmpty) { - subtitles = res["data"]; - var result = await VideoHttp.vttSubtitles(res["data"]); - if (result != null) { - _vttSubtitles = result; + if (res["subtitles"] is List && res["subtitles"].isNotEmpty) { + vttSubtitlesIndex.value = 0; + subtitles.value = res["subtitles"]; + + String preference = setting.get( + SettingBoxKey.subtitlePreference, + defaultValue: SubtitlePreference.values.first.code, + ); + if (preference == 'on') { + vttSubtitlesIndex.value = 1; + } else if (preference == 'withoutAi') { + for (int i = 0; i < subtitles.length; i++) { + if (subtitles[i]['lan']!.startsWith('ai')) { + continue; + } + vttSubtitlesIndex.value = i + 1; + break; + } } - // if (_vttSubtitles.isEmpty) { - // SmartDialog.showToast('字幕均加载失败'); - // } + setSubtitle(vttSubtitlesIndex.value); } } } @@ -1556,8 +1571,8 @@ class VideoDetailController extends GetxController savedDanmaku = null; // subtitle - subtitles = null; - vttSubtitlesIndex = null; + subtitles.clear(); + vttSubtitlesIndex.value = -1; _vttSubtitles.clear(); // view point diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 90cec48e..7378d3ce 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -410,8 +410,6 @@ class _VideoDetailPageState extends State // } if (plPlayerController != null) { videoDetailController.makeHeartBeat(); - videoDetailController.vttSubtitlesIndex = - plPlayerController!.vttSubtitlesIndex.value; videoDetailController.showVP = plPlayerController!.showVP.value; plPlayerController!.removeStatusLister(playerListener); plPlayerController!.removePositionListener(positionListener); diff --git a/lib/pages/video/detail/view_v.dart b/lib/pages/video/detail/view_v.dart index 3af7b970..9faf36df 100644 --- a/lib/pages/video/detail/view_v.dart +++ b/lib/pages/video/detail/view_v.dart @@ -449,8 +449,6 @@ class _VideoDetailPageVState extends State // } if (plPlayerController != null) { videoDetailController.makeHeartBeat(); - videoDetailController.vttSubtitlesIndex = - plPlayerController!.vttSubtitlesIndex.value; videoDetailController.showVP = plPlayerController!.showVP.value; plPlayerController!.removeStatusLister(playerListener); plPlayerController!.removePositionListener(positionListener); diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index a85df478..45cf2d42 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -401,8 +401,7 @@ class HeaderControlState extends State { leading: const Icon(Icons.subtitles_outlined, size: 20), title: const Text('字幕设置', style: titleStyle), ), - if (videoDetailCtr.subtitles is List && - videoDetailCtr.subtitles.isNotEmpty) + if (videoDetailCtr.subtitles.isNotEmpty) ListTile( dense: true, onTap: () => {Get.back(), onExportSubtitle()}, @@ -1103,7 +1102,7 @@ class HeaderControlState extends State { title: const Text('保存字幕'), content: SingleChildScrollView( child: Column( - children: (videoDetailCtr.subtitles as List) + children: videoDetailCtr.subtitles .map( (item) => ListTile( dense: true, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index af937309..1d19464f 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -111,9 +111,6 @@ class PlPlayerController { bool _enableHeart = true; late DataSource dataSource; - // 视频字幕 - final RxList> vttSubtitles = >[].obs; - final RxInt vttSubtitlesIndex = 0.obs; Timer? _timer; Timer? _timerForSeek; @@ -471,8 +468,6 @@ class PlPlayerController { DataSource dataSource, { List? segmentList, List? viewPointList, - List>? vttSubtitles, - int? vttSubtitlesIndex, bool? showVP, List? dmTrend, bool autoplay = true, @@ -504,8 +499,6 @@ class PlPlayerController { this.dataSource = dataSource; this.segmentList.value = segmentList ?? []; this.viewPointList.value = viewPointList ?? []; - this.vttSubtitles.value = vttSubtitles ?? >[]; - this.vttSubtitlesIndex.value = vttSubtitlesIndex ?? 0; this.showVP.value = showVP ?? true; this.dmTrend.value = dmTrend ?? []; _autoPlay = autoplay; @@ -555,7 +548,6 @@ class PlPlayerController { startListeners(); } await _initializePlayer(); - setSubtitle(this.vttSubtitlesIndex.value); } catch (err, stackTrace) { dataStatus.status.value = DataStatus.error; debugPrint(stackTrace.toString()); @@ -1574,22 +1566,6 @@ class PlPlayerController { } } - // 设定字幕轨道 - setSubtitle(int index) { - if (index == 0) { - _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no()); - vttSubtitlesIndex.value = 0; - return; - } - Map s = vttSubtitles[index]; - _videoPlayerController?.setSubtitleTrack(SubtitleTrack.data( - s['text']!, - title: s['title']!, - language: s['language']!, - )); - vttSubtitlesIndex.value = index; - } - static void updatePlayCount() { if (_instance?._playerCount.value == 1) { _instance?.dispose(); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 6fa4f2e6..082c0b7f 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -550,40 +550,52 @@ class _PLVideoPlayerState extends State /// 字幕 BottomControlType.subtitle: Obx( - () => plPlayerController.vttSubtitles.isEmpty + () => widget.videoDetailController?.subtitles.isEmpty == true ? const SizedBox.shrink() : SizedBox( width: widgetWidth, height: 30, child: PopupMenuButton( - initialValue: plPlayerController.vttSubtitles.length < - plPlayerController.vttSubtitlesIndex.value - ? 0 - : plPlayerController.vttSubtitlesIndex.value, + initialValue: widget + .videoDetailController!.vttSubtitlesIndex.value + .clamp(0, widget.videoDetailController!.subtitles.length), color: Colors.black.withOpacity(0.8), itemBuilder: (BuildContext context) { - return plPlayerController.vttSubtitles - .asMap() - .entries - .map((entry) { - return PopupMenuItem( - value: entry.key, + return [ + PopupMenuItem( + value: 0, onTap: () { - plPlayerController.setSubtitle(entry.key); + widget.videoDetailController!.setSubtitle(0); }, child: Text( - "${entry.value['title']}", + "关闭字幕", style: const TextStyle(color: Colors.white), ), - ); - }).toList(); + ), + ...widget.videoDetailController!.subtitles + .asMap() + .entries + .map((entry) { + return PopupMenuItem( + value: entry.key + 1, + onTap: () { + widget.videoDetailController! + .setSubtitle(entry.key + 1); + }, + child: Text( + "${entry.value['lan_doc']}", + style: const TextStyle(color: Colors.white), + ), + ); + }) + ]; }, child: Container( width: 35, height: 30, alignment: Alignment.center, child: Icon( - plPlayerController.vttSubtitlesIndex.value == 0 + widget.videoDetailController!.vttSubtitlesIndex.value == 0 ? Icons.closed_caption_off_outlined : Icons.closed_caption_off_rounded, size: 22,