diff --git a/README.md b/README.md index 7dee20c4..414656f4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

jidoujisho

A full-featured immersion language learning suite for mobile.

- + latest release @@ -34,7 +34,7 @@ ---

✨ Latest Release: - 2.8.5 + 2.8.6

diff --git a/yuuna/lib/i18n/strings.g.dart b/yuuna/lib/i18n/strings.g.dart index 6725604a..720cc17b 100644 --- a/yuuna/lib/i18n/strings.g.dart +++ b/yuuna/lib/i18n/strings.g.dart @@ -1,9 +1,9 @@ /// Generated file. Do not edit. /// /// Locales: 1 -/// Strings: 390 +/// Strings: 395 /// -/// Built on 2023-06-16 at 14:20 UTC +/// Built on 2023-07-02 at 01:09 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -534,6 +534,11 @@ class _StringsEn implements BaseTranslations { String get player_background_play => 'Background play'; String get loaded_from_cache => 'Loaded from web archive cache.'; String get player_show_subtitle_in_notification => 'Show subtitles in media notification'; + String get subtitles_processing => 'Subtitles are processing...'; + String get video_unavailable => 'Video Unavailable'; + String get video_unavailable_content => 'Cannot fetch streams. There may be restrictions in place that prevent watching this video.'; + String get video_file_error => 'Cannot Load File'; + String get video_file_error_content => 'Unable to load the video file. Please ensure this file exists and is located in a directory accessible by the application.'; } // Path: retrying_in @@ -962,6 +967,11 @@ extension on _StringsEn { case 'player_background_play': return 'Background play'; case 'loaded_from_cache': return 'Loaded from web archive cache.'; case 'player_show_subtitle_in_notification': return 'Show subtitles in media notification'; + case 'subtitles_processing': return 'Subtitles are processing...'; + case 'video_unavailable': return 'Video Unavailable'; + case 'video_unavailable_content': return 'Cannot fetch streams. There may be restrictions in place that prevent watching this video.'; + case 'video_file_error': return 'Cannot Load File'; + case 'video_file_error_content': return 'Unable to load the video file. Please ensure this file exists and is located in a directory accessible by the application.'; default: return null; } } diff --git a/yuuna/lib/i18n/strings.i18n.json b/yuuna/lib/i18n/strings.i18n.json index 51f110f5..e07bb1b5 100644 --- a/yuuna/lib/i18n/strings.i18n.json +++ b/yuuna/lib/i18n/strings.i18n.json @@ -422,5 +422,10 @@ "double_tap_seek_duration": "Double tap seek duration", "player_background_play": "Background play", "loaded_from_cache": "Loaded from web archive cache.", - "player_show_subtitle_in_notification": "Show subtitles in media notification" + "player_show_subtitle_in_notification": "Show subtitles in media notification", + "subtitles_processing": "Subtitles are processing...", + "video_unavailable": "Video Unavailable", + "video_unavailable_content": "Cannot fetch streams. There may be restrictions in place that prevent watching this video.", + "video_file_error": "Cannot Load File", + "video_file_error_content": "Unable to load the video file. Please ensure this file exists and is located in a directory accessible by the application." } \ No newline at end of file diff --git a/yuuna/lib/src/models/app_model.dart b/yuuna/lib/src/models/app_model.dart index d0354b24..1c28caa2 100644 --- a/yuuna/lib/src/models/app_model.dart +++ b/yuuna/lib/src/models/app_model.dart @@ -3335,7 +3335,7 @@ class AppModel with ChangeNotifier { /// Whether or not the player should allow background play. bool get playerBackgroundPlay { - return _preferences.get('player_background_play', defaultValue: false); + return _preferences.get('player_background_play', defaultValue: true); } /// Set whether or not the player should allow background play. diff --git a/yuuna/lib/src/pages/implementations/player_source_page.dart b/yuuna/lib/src/pages/implementations/player_source_page.dart index 5627cf51..e43f74af 100644 --- a/yuuna/lib/src/pages/implementations/player_source_page.dart +++ b/yuuna/lib/src/pages/implementations/player_source_page.dart @@ -267,32 +267,100 @@ class _PlayerSourcePageState extends BaseSourcePageState reverseDuration: const Duration(milliseconds: 400), ); - await source.prepareMediaResources( - appModel: appModel, ref: ref, item: widget.item!); - final futures = await Future.wait( - [ - source.preparePlayerController( - appModel: appModel, - ref: ref, - item: widget.item!, - ), - source.prepareSubtitles( - appModel: appModel, - ref: ref, - item: widget.item!, - ), - ], - ); + late final List futures; + + try { + await source.prepareMediaResources( + appModel: appModel, ref: ref, item: widget.item!); + + futures = await Future.wait( + [ + source.preparePlayerController( + appModel: appModel, + ref: ref, + item: widget.item!, + ), + source.prepareSubtitles( + appModel: appModel, + ref: ref, + item: widget.item!, + ), + ], + ); + } catch (e) { + if (e is VideoUnavailableException || e is VideoUnplayableException) { + if (mounted) { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return AlertDialog( + title: Text( + t.video_unavailable, + ), + content: Text( + t.video_unavailable_content, + textAlign: TextAlign.justify, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.pop(context); + }, + child: Text(t.dialog_close), + ), + ], + ); + }, + ); + } else { + rethrow; + } + } + } _playerController = futures.elementAt(0) as VlcPlayerController; _subtitleItems = futures.elementAt(1) as List; _transcriptBackgroundNotifier.value = appModel.isTranscriptOpaque; + if (source is PlayerLocalMediaSource) { + final videoFile = File(widget.item!.mediaIdentifier); + if (!videoFile.existsSync()) { + if (mounted) { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return AlertDialog( + title: Text( + t.video_file_error, + ), + content: Text( + t.video_file_error_content, + textAlign: TextAlign.justify, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.pop(context); + }, + child: Text(t.dialog_close), + ), + ], + ); + }, + ); + } + } + } + if (_subtitleItems.isNotEmpty) { _subtitleItem = _subtitleItems.first; } if (!_subtitleItem.controller.initialized) { - await _subtitleItem.controller.initial(); + _subtitleItem.controller.initial(); } _blurOptionsNotifier = ValueNotifier(appModel.blurOptions); @@ -913,6 +981,7 @@ class _PlayerSourcePageState extends BaseSourcePageState opaque: false, pageBuilder: (context, _, __) => PlayerTranscriptPage( title: widget.item!.title, + subtitleController: _subtitleItem.controller, subtitles: _subtitleItem.controller.subtitles, currentSubtitle: _currentSubtitle, autoPauseNotifier: _autoPauseNotifier, @@ -1619,6 +1688,7 @@ class _PlayerSourcePageState extends BaseSourcePageState pageBuilder: (context, _, __) => PlayerTranscriptPage( title: widget.item!.title, subtitles: _subtitleItem.controller.subtitles, + subtitleController: _subtitleItem.controller, controller: _playerController, autoPauseNotifier: _autoPauseNotifier, playingNotifier: _playingNotifier, diff --git a/yuuna/lib/src/pages/implementations/player_transcript_page.dart b/yuuna/lib/src/pages/implementations/player_transcript_page.dart index d347dfc8..c7f36753 100644 --- a/yuuna/lib/src/pages/implementations/player_transcript_page.dart +++ b/yuuna/lib/src/pages/implementations/player_transcript_page.dart @@ -35,6 +35,7 @@ class PlayerTranscriptPage extends BaseSourcePage { required this.onTap, required this.onLongPress, required this.alignMode, + required this.subtitleController, super.key, super.item, }); @@ -45,6 +46,9 @@ class PlayerTranscriptPage extends BaseSourcePage { /// All subtitles in the current video. final List subtitles; + /// Subtitle controller. + final SubtitleController subtitleController; + /// Subtitle to be highlighted. final ValueNotifier currentSubtitle; @@ -486,6 +490,10 @@ class _PlayerTranscriptPageState } Widget buildBody() { + if (!widget.subtitleController.initialized) { + return buildProcessingPlaceholder(); + } + if (widget.subtitles.isEmpty) { return buildPlaceholder(); } else { @@ -493,6 +501,34 @@ class _PlayerTranscriptPageState } } + Widget buildProcessingPlaceholder() { + return Padding( + padding: Spacing.of(context).insets.onlyBottom.extraBig, + child: Material( + color: Colors.transparent, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.subtitles_outlined, + size: 72, + ), + const Space.normal(), + Text( + t.subtitles_processing, + style: const TextStyle( + fontSize: 20, + ), + ), + const Space.big(), + ], + ), + ), + ), + ); + } + Widget buildPlaceholder() { return Padding( padding: Spacing.of(context).insets.onlyBottom.extraBig, diff --git a/yuuna/pubspec.yaml b/yuuna/pubspec.yaml index 30536a95..63ffd483 100644 --- a/yuuna/pubspec.yaml +++ b/yuuna/pubspec.yaml @@ -1,7 +1,7 @@ name: yuuna description: A full-featured immersion language learning suite for mobile. publish_to: 'none' -version: 2.8.5+100 +version: 2.8.6+101 environment: sdk: ">=3.0.0<4.0.0" flutter: "^3.10.5"