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.
-
+
@@ -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"