diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 4d83c1f6..3cdbefa3 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -88,6 +88,9 @@ "theme-dark": "Dark", "nsfw": "NSFW", "nsfw-subtitle": "Show NSFW content", + "external-player": "Preferred video player", + "external-player-subtitle": "Currently, the preferred player is {player}", + "external-player-builtin": "Built-in", "language-subtitle": "Change the language of the software", "extension-log": "Extension Log Window", "extension-log-subtitle": "Used for debugging extensions", @@ -100,6 +103,8 @@ "license-subtitle": "License" }, + "external-player-launching": "Launching {player}", + "detail": { "favorite": "Favorite", "favorited": "Favorited", diff --git a/assets/i18n/zh.json b/assets/i18n/zh.json index 6651bf6c..1471d054 100644 --- a/assets/i18n/zh.json +++ b/assets/i18n/zh.json @@ -77,6 +77,9 @@ "theme-dark": "深色", "nsfw": "NSFW", "nsfw-subtitle": "显示 NSFW 内容", + "external-player": "优先使用的视频播放器", + "external-player-subtitle": "当前优先使用的是 {player}", + "external-player-builtin": "内置播放器", "language-subtitle": "选择软件的语言", "extension-log": "扩展日志窗口", "extension-log-subtitle": "用于调试扩展", @@ -89,6 +92,8 @@ "license-subtitle": "许可证" }, + "external-player-launching": "正在启动 {player}", + "detail": { "favorite": "收藏", "favorited": "已收藏", diff --git a/lib/pages/detail/controller.dart b/lib/pages/detail/controller.dart index d47111a8..59e92015 100644 --- a/lib/pages/detail/controller.dart +++ b/lib/pages/detail/controller.dart @@ -18,6 +18,7 @@ import 'package:miru_app/router/router.dart'; import 'package:miru_app/utils/database.dart'; import 'package:miru_app/utils/extension.dart'; import 'package:miru_app/utils/extension_runtime.dart'; +import 'package:miru_app/utils/external_player.dart'; import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_directory.dart'; import 'package:miru_app/utils/miru_storage.dart'; @@ -307,7 +308,7 @@ class DetailPageController extends GetxController { List urls, int index, int selectEpGroup, - ) { + ) async { if (runtime.value == null) { showPlatformSnackbar( context: cuurentContext, @@ -323,6 +324,49 @@ class DetailPageController extends GetxController { return; } + if (type == ExtensionType.bangumi) { + final player = MiruStorage.getSetting(SettingKey.videoPlayer); + + if (player != 'built-in') { + showPlatformSnackbar( + context: cuurentContext, + content: FlutterI18n.translate( + cuurentContext, + 'external-player-launching', + translationParams: { + 'player': player, + }, + ), + ); + late ExtensionBangumiWatch watchData; + try { + watchData = await runtime.value!.watch(urls[index].url) + as ExtensionBangumiWatch; + } catch (e) { + showPlatformSnackbar( + context: cuurentContext, + content: e.toString().split('\n')[0], + severity: fluent.InfoBarSeverity.error, + ); + return; + } + try { + if (GetPlatform.isMobile) { + await launchMobileExternalPlayer(watchData.url, player); + return; + } + await launchDesktopExternalPlayer(watchData.url, player); + return; + } catch (e) { + showPlatformSnackbar( + context: cuurentContext, + content: e.toString().split('\n')[0], + severity: fluent.InfoBarSeverity.error, + ); + } + } + } + Navigator.of(context, rootNavigator: true).push( PageRouteBuilder( transitionDuration: const Duration(milliseconds: 600), diff --git a/lib/pages/detail/view.dart b/lib/pages/detail/view.dart index 88e202bf..73c11419 100644 --- a/lib/pages/detail/view.dart +++ b/lib/pages/detail/view.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:miru_app/api/tmdb.dart'; import 'package:miru_app/models/extension.dart'; import 'package:miru_app/pages/detail/controller.dart'; -import 'package:miru_app/pages/detail/pages/tmdb_binding.dart'; import 'package:miru_app/pages/detail/pages/webview.dart'; import 'package:miru_app/pages/detail/widgets/detail_appbar_flexible_space.dart'; import 'package:miru_app/pages/detail/widgets/detail_appbar_title.dart'; diff --git a/lib/pages/settings/view.dart b/lib/pages/settings/view.dart index 770086c4..aab2d11d 100644 --- a/lib/pages/settings/view.dart +++ b/lib/pages/settings/view.dart @@ -204,6 +204,41 @@ class _SettingsPageState extends State { }, ), const SizedBox(height: 8), + SettingsRadiosTile( + icon: const PlatformWidget( + androidWidget: Icon(Icons.play_arrow), + desktopWidget: Icon(fluent.FluentIcons.play_resume, size: 24), + ), + title: 'settings.external-player'.i18n, + itemNameValue: () { + if (Platform.isAndroid) { + return { + "settings.external-player-builtin".i18n: "built-in", + "VLC": "vlc", + "Other": "other", + }; + } + return { + "settings.external-player-builtin".i18n: "built-in", + "VLC": "vlc", + "PotPlayer": "potplayer", + }; + }(), + buildSubtitle: () => FlutterI18n.translate( + context, + 'settings.external-player-subtitle', + translationParams: { + 'player': MiruStorage.getSetting(SettingKey.videoPlayer), + }, + ), + applyValue: (value) { + MiruStorage.setSetting(SettingKey.videoPlayer, value); + }, + buildGroupValue: () { + return MiruStorage.getSetting(SettingKey.videoPlayer); + }, + ), + const SizedBox(height: 8), if (!Platform.isAndroid) Obx( () { diff --git a/lib/utils/external_player.dart b/lib/utils/external_player.dart new file mode 100644 index 00000000..45c5b609 --- /dev/null +++ b/lib/utils/external_player.dart @@ -0,0 +1,38 @@ +import 'dart:io'; + +import 'package:android_intent_plus/android_intent.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +Future launchMobileExternalPlayer(String playUrl, String player) async { + switch (player) { + case "vlc": + await _launchExternalPlayer("vlc://$playUrl"); + break; + case "other": + await AndroidIntent( + action: 'action_view', + data: playUrl, + type: 'video/*', + ).launch(); + break; + } +} + +// desktop +Future launchDesktopExternalPlayer(String playUrl, String player) async { + switch (player) { + case "vlc": + const vlc = 'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe'; + await Process.run(vlc, [playUrl]); + break; + case "potplayer": + await _launchExternalPlayer("potplayer://$playUrl"); + break; + } +} + +_launchExternalPlayer(String url) async { + if (!await launchUrlString(url, mode: LaunchMode.externalApplication)) { + throw Exception("Failed to launch $url"); + } +} diff --git a/lib/utils/miru_storage.dart b/lib/utils/miru_storage.dart index 66253808..6e7c13c5 100644 --- a/lib/utils/miru_storage.dart +++ b/lib/utils/miru_storage.dart @@ -111,6 +111,7 @@ class MiruStorage { await _initSetting(SettingKey.novelFontSize, 18.0); await _initSetting(SettingKey.theme, 'system'); await _initSetting(SettingKey.enableNSFW, false); + await _initSetting(SettingKey.videoPlayer, 'built-in'); } static _initSetting(String key, dynamic value) async { @@ -136,5 +137,6 @@ class SettingKey { static String language = 'Language'; static String novelFontSize = 'NovelFontSize'; static String enableNSFW = 'EnableNSFW'; + static String videoPlayer = 'VideoPlayer'; static String databaseVersion = 'DatabaseVersion'; } diff --git a/pubspec.lock b/pubspec.lock index 9449ea6f..3cf1a52a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + android_intent_plus: + dependency: "direct main" + description: + name: android_intent_plus + sha256: f72ae20bb37108694f442e7ae6acbd28b453ca62ce86842f6787b784355abfe6 + url: "https://pub.dev" + source: hosted + version: "4.0.2" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ab9cdeb0..0768a6a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: http_parser: ^4.0.2 html: ^0.15.4 xpath_selector_html_parser: ^3.0.1 + android_intent_plus: ^4.0.2 dev_dependencies: flutter_test: