Skip to content

Commit

Permalink
feat: 字幕支持
Browse files Browse the repository at this point in the history
  • Loading branch information
MiaoMint committed Aug 10, 2023
1 parent b80896f commit 6e85eab
Showing 10 changed files with 229 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ Miru App
- [x] 漫画小说设置
- [x] 漫画小说历史记录
- [x] TMDB 元数据
- [ ] 字幕支持
- [x] 字幕支持
- [ ] BT 种子播放
- [ ] 数据同步
- [ ] 自动搜寻字幕
5 changes: 4 additions & 1 deletion assets/i18n/en.json
Original file line number Diff line number Diff line change
@@ -93,7 +93,10 @@
"watch-now": "Watch Now",
"no-episodes": "No episodes",
"play-complete": "Playback complete",
"resume-last-playback": "Resume last playback"
"resume-last-playback": "Resume last playback",
"subtitle": "Subtitle",
"subtitle-change": "Change Subtitle {title}",
"subtitle-file": "Subtitle File"
},

"comic-settings": {
6 changes: 5 additions & 1 deletion assets/i18n/zh.json
Original file line number Diff line number Diff line change
@@ -98,7 +98,11 @@
"watch-now": "立即观看",
"no-episodes": "暂无剧集",
"play-complete": "播放完成",
"resume-last-playback": "恢复上次播放位置"
"resume-last-playback": "恢复上次播放位置",
"subtitle-none": "不使用字幕",
"subtitle": "字幕",
"subtitle-change": "切换字幕 {title}",
"subtitle-file": "选择字幕文件"
},

"reader": {
19 changes: 19 additions & 0 deletions lib/models/extension.dart
Original file line number Diff line number Diff line change
@@ -125,16 +125,35 @@ class ExtensionBangumiWatch {
ExtensionBangumiWatch({
required this.type,
required this.url,
this.subtitles,
});
final ExtensionWatchBangumiType type;
final String url;
final List<ExtensionBangumiWatchSubtitle>? subtitles;

factory ExtensionBangumiWatch.fromJson(Map<String, dynamic> json) =>
_$ExtensionBangumiWatchFromJson(json);

Map<String, dynamic> toJson() => _$ExtensionBangumiWatchToJson(this);
}

@JsonSerializable()
class ExtensionBangumiWatchSubtitle {
final String? language;
final String title;
final String url;
ExtensionBangumiWatchSubtitle({
required this.title,
required this.url,
this.language,
});

factory ExtensionBangumiWatchSubtitle.fromJson(Map<String, dynamic> json) =>
_$ExtensionBangumiWatchSubtitleFromJson(json);

Map<String, dynamic> toJson() => _$ExtensionBangumiWatchSubtitleToJson(this);
}

@JsonSerializable()
class ExtensionMangaWatch {
final List<String> urls;
21 changes: 21 additions & 0 deletions lib/models/extension.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/models/index.dart
Original file line number Diff line number Diff line change
@@ -4,3 +4,4 @@ export 'history.dart';
export 'extension_setting.dart';
export 'manga_setting.dart';
export 'miru_detail.dart';
export 'tmdb.dart';
71 changes: 67 additions & 4 deletions lib/pages/watch/video_controller.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'dart:async';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:miru_app/models/index.dart';
import 'package:miru_app/pages/home/controller.dart';
import 'package:miru_app/router/router.dart';
import 'package:miru_app/utils/database.dart';
import 'package:miru_app/utils/extension_runtime.dart';
import 'package:miru_app/utils/i18n.dart';
@@ -38,6 +41,9 @@ class VideoPlayerController extends GetxController {
final isOpenSidebar = false.obs;
final isFullScreen = false.obs;
late final index = playIndex.obs;
final List<ExtensionBangumiWatchSubtitle> subtitles =
<ExtensionBangumiWatchSubtitle>[].obs;
final selectedSubtitle = 0.obs;

// 是否已经自动跳转到上次播放进度
bool _isAutoSeekPosition = false;
@@ -55,14 +61,69 @@ class VideoPlayerController extends GetxController {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
play();

// 切换剧集
ever(index, (callback) {
play();
});

// 显示剧集列表
ever(showPlayList, (callback) {
if (!showPlayList.value) {
isOpenSidebar.value = false;
}
});

// 切换字幕
ever(selectedSubtitle, (callback) {
if (callback == -1) {
player.setSubtitleTrack(SubtitleTrack.no());
return;
}
if (callback == -2) {
// 选择文件 srt 或者 vtt
FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['srt', 'vtt'],
).then((value) {
if (value == null) {
selectedSubtitle.value = -1;
return;
}

// 读取文件
final data = File(value.files.first.path!).readAsStringSync();
player.setSubtitleTrack(SubtitleTrack.data(data));
sendMessage(
Message(
Text(
FlutterI18n.translate(
cuurentContext,
"video.subtitle-change",
translationParams: {"title": value.files.first.name},
),
),
),
);
});
return;
}
player.setSubtitleTrack(
SubtitleTrack.uri(subtitles[callback].url),
);
sendMessage(
Message(
Text(
FlutterI18n.translate(
cuurentContext,
"video.subtitle-change",
translationParams: {"title": subtitles[callback].title},
),
),
),
);
});

// 自动切换下一集
player.stream.completed.listen((event) {
if (index.value == playList.length - 1 && event) {
@@ -84,6 +145,7 @@ class VideoPlayerController extends GetxController {
runtime.extension.package,
detailUrl,
);

if (history != null &&
history.progress.isNotEmpty &&
history.episodeId == index.value &&
@@ -104,10 +166,12 @@ class VideoPlayerController extends GetxController {

play() async {
try {
subtitles.clear();
selectedSubtitle.value = -1;
final playUrl = playList[index.value].url;
final m3u8Url =
(await runtime.watch(playUrl) as ExtensionBangumiWatch).url;
player.open(Media(m3u8Url));
final watchData = await runtime.watch(playUrl) as ExtensionBangumiWatch;
player.open(Media(watchData.url));
subtitles.addAll(watchData.subtitles ?? []);
} catch (e) {
debugPrint(e.toString());
sendMessage(
@@ -203,6 +267,5 @@ class VideoPlayerController extends GetxController {
class Message {
final Widget child;
final Duration time;

Message(this.child, {this.time = const Duration(seconds: 3)});
}
92 changes: 92 additions & 0 deletions lib/pages/watch/widgets/video/video_player_content.dart
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:miru_app/pages/watch/video_controller.dart';
import 'package:miru_app/utils/i18n.dart';
import 'package:miru_app/utils/router.dart';
import 'package:miru_app/widgets/platform_widget.dart';
import 'package:window_manager/window_manager.dart';
@@ -88,6 +89,52 @@ class _VideoPlayerContenState extends State<VideoPlayerConten> {
const MaterialDesktopVolumeButton(),
const MaterialDesktopPositionIndicator(),
const Spacer(),
PopupMenuButton(
icon: const Icon(
Icons.subtitles,
color: Colors.white,
),
itemBuilder: (context) {
return [
// 是否显示字幕
PopupMenuItem(
child: Obx(
() => CheckboxListTile(
value: _c.selectedSubtitle.value == -1,
onChanged: (value) {
_c.selectedSubtitle.value = -1;
},
title: Text('video.subtitle-none'.i18n),
),
),
),
// 选择文件
PopupMenuItem(
child: Obx(
() => CheckboxListTile(
value: _c.selectedSubtitle.value == -2,
onChanged: (value) {
_c.selectedSubtitle.value = -2;
},
title: Text("video.subtitle-file".i18n),
),
),
),
for (int i = 0; i < _c.subtitles.length; i++)
PopupMenuItem(
child: Obx(
() => CheckboxListTile(
value: _c.selectedSubtitle.value == i,
onChanged: (value) {
_c.selectedSubtitle.value = i;
},
title: Text(_c.subtitles[i].title),
),
),
),
];
},
),
MaterialDesktopCustomButton(
onPressed: () {
_c.showPlayList.value = !_c.showPlayList.value;
@@ -164,6 +211,51 @@ class _VideoPlayerContenState extends State<VideoPlayerConten> {
}),
const MaterialPositionIndicator(),
const Spacer(),
PopupMenuButton(
icon: const Icon(
Icons.subtitles,
color: Colors.white,
),
itemBuilder: (context) {
return [
// 是否显示字幕
PopupMenuItem(
value: -1,
child: Obx(
() => CheckboxListTile(
value: _c.selectedSubtitle.value == -1,
onChanged: (value) {
_c.selectedSubtitle.value = -1;
},
title: Text('video.subtitle-none'.i18n),
),
),
), // 选择文件
PopupMenuItem(
child: CheckboxListTile(
value: _c.selectedSubtitle.value == -2,
onChanged: (value) {
_c.selectedSubtitle.value = -2;
},
title: Text("video.subtitle-file".i18n),
),
),
for (int i = 0; i < _c.subtitles.length; i++)
PopupMenuItem(
value: i,
child: Obx(
() => CheckboxListTile(
value: _c.selectedSubtitle.value == i,
onChanged: (value) {
_c.selectedSubtitle.value = i;
},
title: Text(_c.subtitles[i].title),
),
),
),
];
},
),
MaterialCustomButton(
onPressed: () {
_c.showPlayList.value = !_c.showPlayList.value;
16 changes: 16 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
@@ -305,6 +305,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a"
url: "https://pub.dev"
source: hosted
version: "5.3.3"
fixnum:
dependency: transitive
description:
@@ -403,6 +411,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.17+1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
url: "https://pub.dev"
source: hosted
version: "2.0.15"
flutter_test:
dependency: "direct dev"
description: flutter
Loading

0 comments on commit 6e85eab

Please sign in to comment.