feat: add auto-play toggle

close #262
This commit is contained in:
edde746
2026-01-15 20:12:43 +01:00
parent 103653ea47
commit 0290adcb6d
11 changed files with 97 additions and 8 deletions
+30 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 7
/// Strings: 3750 (535 per locale)
/// Strings: 3764 (537 per locale)
///
/// Built on 2026-01-14 at 23:28 UTC
/// Built on 2026-01-15 at 19:06 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@@ -529,6 +529,8 @@ class _StringsVideoControlsEn {
String get timerActive => 'Timer Active';
String playbackWillPauseIn({required Object duration}) => 'Playback will pause in ${duration}';
String get sleepTimerCompleted => 'Sleep timer completed - playback paused';
String get autoPlayNext => 'Auto-Play Next';
String get playNext => 'Play Next';
String get playButton => 'Play';
String get pauseButton => 'Pause';
String seekBackwardButton({required Object seconds}) => 'Seek backward ${seconds} seconds';
@@ -1440,6 +1442,8 @@ class _StringsVideoControlsDe implements _StringsVideoControlsEn {
@override String get timerActive => 'Schlaftimer aktiv';
@override String playbackWillPauseIn({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert';
@override String get sleepTimerCompleted => 'Schlaftimer abgelaufen Wiedergabe pausiert';
@override String get autoPlayNext => 'Nächstes automatisch abspielen';
@override String get playNext => 'Nächstes abspielen';
@override String get playButton => 'Wiedergeben';
@override String get pauseButton => 'Pause';
@override String seekBackwardButton({required Object seconds}) => '${seconds} Sekunden zurück';
@@ -2352,6 +2356,8 @@ class _StringsVideoControlsIt implements _StringsVideoControlsEn {
@override String get timerActive => 'Timer attivo';
@override String playbackWillPauseIn({required Object duration}) => 'La riproduzione si interromperà tra ${duration}';
@override String get sleepTimerCompleted => 'Timer di spegnimento completato - riproduzione in pausa';
@override String get autoPlayNext => 'Riproduzione automatica successivo';
@override String get playNext => 'Riproduci successivo';
@override String get playButton => 'Riproduci';
@override String get pauseButton => 'Pausa';
@override String seekBackwardButton({required Object seconds}) => 'Riavvolgi di ${seconds} secondi';
@@ -3264,6 +3270,8 @@ class _StringsVideoControlsKo implements _StringsVideoControlsEn {
@override String get timerActive => '타이머 활성화됨';
@override String playbackWillPauseIn({required Object duration}) => '재생이 ${duration} 후에 일시 중지 됩니다';
@override String get sleepTimerCompleted => '수면 타이머 완료됨 - 재생이 일시 중지되었습니다';
@override String get autoPlayNext => '다음 자동 재생';
@override String get playNext => '다음 재생';
@override String get playButton => '재생';
@override String get pauseButton => '일시정지';
@override String seekBackwardButton({required Object seconds}) => '${seconds} 초 뒤로';
@@ -4176,6 +4184,8 @@ class _StringsVideoControlsNl implements _StringsVideoControlsEn {
@override String get timerActive => 'Timer actief';
@override String playbackWillPauseIn({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}';
@override String get sleepTimerCompleted => 'Slaaptimer voltooid - afspelen gepauzeerd';
@override String get autoPlayNext => 'Automatisch volgende afspelen';
@override String get playNext => 'Volgende afspelen';
@override String get playButton => 'Afspelen';
@override String get pauseButton => 'Pauzeren';
@override String seekBackwardButton({required Object seconds}) => 'Terugspoelen ${seconds} seconden';
@@ -5088,6 +5098,8 @@ class _StringsVideoControlsSv implements _StringsVideoControlsEn {
@override String get timerActive => 'Timer aktiv';
@override String playbackWillPauseIn({required Object duration}) => 'Uppspelningen pausas om ${duration}';
@override String get sleepTimerCompleted => 'Sovtimer slutförd - uppspelning pausad';
@override String get autoPlayNext => 'Spela nästa automatiskt';
@override String get playNext => 'Spela nästa';
@override String get playButton => 'Spela';
@override String get pauseButton => 'Pausa';
@override String seekBackwardButton({required Object seconds}) => 'Spola bakåt ${seconds} sekunder';
@@ -6000,6 +6012,8 @@ class _StringsVideoControlsZh implements _StringsVideoControlsEn {
@override String get timerActive => '定时器已激活';
@override String playbackWillPauseIn({required Object duration}) => '播放将在 ${duration} 后暂停';
@override String get sleepTimerCompleted => '睡眠定时器已完成 - 播放已暂停';
@override String get autoPlayNext => '自动播放下一集';
@override String get playNext => '播放下一集';
@override String get playButton => '播放';
@override String get pauseButton => '暂停';
@override String seekBackwardButton({required Object seconds}) => '后退 ${seconds}';
@@ -6751,6 +6765,8 @@ extension on Translations {
case 'videoControls.timerActive': return 'Timer Active';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Playback will pause in ${duration}';
case 'videoControls.sleepTimerCompleted': return 'Sleep timer completed - playback paused';
case 'videoControls.autoPlayNext': return 'Auto-Play Next';
case 'videoControls.playNext': return 'Play Next';
case 'videoControls.playButton': return 'Play';
case 'videoControls.pauseButton': return 'Pause';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Seek backward ${seconds} seconds';
@@ -7294,6 +7310,8 @@ extension on _StringsDe {
case 'videoControls.timerActive': return 'Schlaftimer aktiv';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert';
case 'videoControls.sleepTimerCompleted': return 'Schlaftimer abgelaufen Wiedergabe pausiert';
case 'videoControls.autoPlayNext': return 'Nächstes automatisch abspielen';
case 'videoControls.playNext': return 'Nächstes abspielen';
case 'videoControls.playButton': return 'Wiedergeben';
case 'videoControls.pauseButton': return 'Pause';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '${seconds} Sekunden zurück';
@@ -7838,6 +7856,8 @@ extension on _StringsIt {
case 'videoControls.timerActive': return 'Timer attivo';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'La riproduzione si interromperà tra ${duration}';
case 'videoControls.sleepTimerCompleted': return 'Timer di spegnimento completato - riproduzione in pausa';
case 'videoControls.autoPlayNext': return 'Riproduzione automatica successivo';
case 'videoControls.playNext': return 'Riproduci successivo';
case 'videoControls.playButton': return 'Riproduci';
case 'videoControls.pauseButton': return 'Pausa';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Riavvolgi di ${seconds} secondi';
@@ -8382,6 +8402,8 @@ extension on _StringsKo {
case 'videoControls.timerActive': return '타이머 활성화됨';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => '재생이 ${duration} 후에 일시 중지 됩니다';
case 'videoControls.sleepTimerCompleted': return '수면 타이머 완료됨 - 재생이 일시 중지되었습니다';
case 'videoControls.autoPlayNext': return '다음 자동 재생';
case 'videoControls.playNext': return '다음 재생';
case 'videoControls.playButton': return '재생';
case 'videoControls.pauseButton': return '일시정지';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '${seconds} 초 뒤로';
@@ -8926,6 +8948,8 @@ extension on _StringsNl {
case 'videoControls.timerActive': return 'Timer actief';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}';
case 'videoControls.sleepTimerCompleted': return 'Slaaptimer voltooid - afspelen gepauzeerd';
case 'videoControls.autoPlayNext': return 'Automatisch volgende afspelen';
case 'videoControls.playNext': return 'Volgende afspelen';
case 'videoControls.playButton': return 'Afspelen';
case 'videoControls.pauseButton': return 'Pauzeren';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Terugspoelen ${seconds} seconden';
@@ -9470,6 +9494,8 @@ extension on _StringsSv {
case 'videoControls.timerActive': return 'Timer aktiv';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Uppspelningen pausas om ${duration}';
case 'videoControls.sleepTimerCompleted': return 'Sovtimer slutförd - uppspelning pausad';
case 'videoControls.autoPlayNext': return 'Spela nästa automatiskt';
case 'videoControls.playNext': return 'Spela nästa';
case 'videoControls.playButton': return 'Spela';
case 'videoControls.pauseButton': return 'Pausa';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Spola bakåt ${seconds} sekunder';
@@ -10014,6 +10040,8 @@ extension on _StringsZh {
case 'videoControls.timerActive': return '定时器已激活';
case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => '播放将在 ${duration} 后暂停';
case 'videoControls.sleepTimerCompleted': return '睡眠定时器已完成 - 播放已暂停';
case 'videoControls.autoPlayNext': return '自动播放下一集';
case 'videoControls.playNext': return '播放下一集';
case 'videoControls.playButton': return '播放';
case 'videoControls.pauseButton': return '暂停';
case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '后退 ${seconds}';
+2
View File
@@ -262,6 +262,8 @@
"timerActive": "Timer Active",
"playbackWillPauseIn": "Playback will pause in ${duration}",
"sleepTimerCompleted": "Sleep timer completed - playback paused",
"autoPlayNext": "Auto-Play Next",
"playNext": "Play Next",
"playButton": "Play",
"pauseButton": "Pause",
"seekBackwardButton": "Seek backward ${seconds} seconds",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "Schlaftimer aktiv",
"playbackWillPauseIn": "Wiedergabe wird in ${duration} pausiert",
"sleepTimerCompleted": "Schlaftimer abgelaufen Wiedergabe pausiert",
"autoPlayNext": "Nächstes automatisch abspielen",
"playNext": "Nächstes abspielen",
"playButton": "Wiedergeben",
"pauseButton": "Pause",
"seekBackwardButton": "${seconds} Sekunden zurück",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "Timer attivo",
"playbackWillPauseIn": "La riproduzione si interromperà tra ${duration}",
"sleepTimerCompleted": "Timer di spegnimento completato - riproduzione in pausa",
"autoPlayNext": "Riproduzione automatica successivo",
"playNext": "Riproduci successivo",
"playButton": "Riproduci",
"pauseButton": "Pausa",
"seekBackwardButton": "Riavvolgi di ${seconds} secondi",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "타이머 활성화됨",
"playbackWillPauseIn": "재생이 ${duration} 후에 일시 중지 됩니다",
"sleepTimerCompleted": "수면 타이머 완료됨 - 재생이 일시 중지되었습니다",
"autoPlayNext": "다음 자동 재생",
"playNext": "다음 재생",
"playButton": "재생",
"pauseButton": "일시정지",
"seekBackwardButton": "${seconds} 초 뒤로",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "Timer actief",
"playbackWillPauseIn": "Afspelen wordt gepauzeerd over ${duration}",
"sleepTimerCompleted": "Slaaptimer voltooid - afspelen gepauzeerd",
"autoPlayNext": "Automatisch volgende afspelen",
"playNext": "Volgende afspelen",
"playButton": "Afspelen",
"pauseButton": "Pauzeren",
"seekBackwardButton": "Terugspoelen ${seconds} seconden",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "Timer aktiv",
"playbackWillPauseIn": "Uppspelningen pausas om ${duration}",
"sleepTimerCompleted": "Sovtimer slutförd - uppspelning pausad",
"autoPlayNext": "Spela nästa automatiskt",
"playNext": "Spela nästa",
"playButton": "Spela",
"pauseButton": "Pausa",
"seekBackwardButton": "Spola bakåt ${seconds} sekunder",
+2
View File
@@ -263,6 +263,8 @@
"timerActive": "定时器已激活",
"playbackWillPauseIn": "播放将在 ${duration} 后暂停",
"sleepTimerCompleted": "睡眠定时器已完成 - 播放已暂停",
"autoPlayNext": "自动播放下一集",
"playNext": "播放下一集",
"playButton": "播放",
"pauseButton": "暂停",
"seekBackwardButton": "后退 ${seconds} 秒",
+15 -6
View File
@@ -1105,15 +1105,21 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
}
}
void _onVideoCompleted(bool completed) {
void _onVideoCompleted(bool completed) async {
if (completed && _nextEpisode != null && !_showPlayNextDialog && !_completionTriggered) {
_completionTriggered = true;
final settings = await SettingsService.getInstance();
final autoPlayEnabled = settings.getAutoPlayNextEpisode();
setState(() {
_showPlayNextDialog = true;
_autoPlayCountdown = 5;
_autoPlayCountdown = autoPlayEnabled ? 5 : -1;
});
_startAutoPlayTimer();
if (autoPlayEnabled) {
_startAutoPlayTimer();
}
}
}
@@ -1720,9 +1726,12 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_autoPlayCountdown'),
const SizedBox(width: 4),
const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 18),
if (_autoPlayCountdown > 0) ...[
Text('$_autoPlayCountdown'),
const SizedBox(width: 4),
const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 18),
] else
Text(t.videoControls.playNext),
],
),
),
+11
View File
@@ -62,6 +62,7 @@ class SettingsService extends BaseSharedPreferencesService {
static const String _keyEnableDiscordRPC = 'enable_discord_rpc';
static const String _keyMatchContentFrameRate = 'match_content_frame_rate';
static const String _keyDefaultPlaybackSpeed = 'default_playback_speed';
static const String _keyAutoPlayNextEpisode = 'auto_play_next_episode';
SettingsService._();
@@ -961,6 +962,15 @@ class SettingsService extends BaseSharedPreferencesService {
return prefs.getDouble(_keyDefaultPlaybackSpeed) ?? 1.0; // Default: normal speed
}
// Auto-Play Next Episode
Future<void> setAutoPlayNextEpisode(bool enabled) async {
await prefs.setBool(_keyAutoPlayNextEpisode, enabled);
}
bool getAutoPlayNextEpisode() {
return prefs.getBool(_keyAutoPlayNextEpisode) ?? true; // Default enabled
}
// Reset all settings to defaults
Future<void> resetAllSettings() async {
await Future.wait([
@@ -1004,6 +1014,7 @@ class SettingsService extends BaseSharedPreferencesService {
prefs.remove(_keyEnableDiscordRPC),
prefs.remove(_keyMatchContentFrameRate),
prefs.remove(_keyDefaultPlaybackSpeed),
prefs.remove(_keyAutoPlayNextEpisode),
]);
}
@@ -112,6 +112,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
late int _subtitleSyncOffset;
bool _enableHDR = true;
bool _showPerformanceOverlay = false;
bool _autoPlayNextEpisode = true;
late final FocusNode _initialFocusNode;
@override
@@ -134,6 +135,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
setState(() {
_enableHDR = settings.getEnableHDR();
_showPerformanceOverlay = settings.getShowPerformanceOverlay();
_autoPlayNextEpisode = settings.getAutoPlayNextEpisode();
});
}
@@ -157,6 +159,15 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
});
}
Future<void> _toggleAutoPlayNextEpisode() async {
final newValue = !_autoPlayNextEpisode;
final settings = await SettingsService.getInstance();
await settings.setAutoPlayNextEpisode(newValue);
setState(() {
_autoPlayNextEpisode = newValue;
});
}
void _navigateTo(_SettingsView view) {
setState(() {
_currentView = view;
@@ -280,6 +291,22 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onTap: _toggleHDR,
),
// Auto-Play Next Episode Toggle
ListTile(
leading: AppIcon(
Symbols.skip_next_rounded,
fill: 1,
color: _autoPlayNextEpisode ? Colors.amber : Colors.white70,
),
title: Text(t.videoControls.autoPlayNext, style: const TextStyle(color: Colors.white)),
trailing: Switch(
value: _autoPlayNextEpisode,
onChanged: (_) => _toggleAutoPlayNextEpisode(),
activeThumbColor: Colors.amber,
),
onTap: _toggleAutoPlayNextEpisode,
),
// Audio Output Device (Desktop only)
if (isDesktop)
StreamBuilder<AudioDevice>(