feat: add tunneled playback setting + frame watchdog for HDR black screen

Adds a user-facing toggle for MediaCodec tunneling (Android/ExoPlayer)
and a rendered-frame watchdog that auto-falls back to MPV when ExoPlayer
produces 0 video frames after 8 seconds of audio playback.
This commit is contained in:
edde746
2026-03-01 04:02:36 +01:00
parent 1d739c74ec
commit d0f2122944
24 changed files with 171 additions and 5 deletions
+17
View File
@@ -78,6 +78,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
static const _kExternalPlayer = 'external_player';
static const _kHardwareDecoding = 'hardware_decoding';
static const _kMatchContentFrameRate = 'match_content_frame_rate';
static const _kTunneledPlayback = 'tunneled_playback';
static const _kBufferSize = 'buffer_size';
static const _kSubtitleStyling = 'subtitle_styling';
static const _kMpvConfig = 'mpv_config';
@@ -121,6 +122,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
int _maxVolume = 100;
bool _enableDiscordRPC = false;
bool _matchContentFrameRate = false;
bool _tunneledPlayback = true;
bool _useExoPlayer = true; // Android only: ExoPlayer vs MPV
bool _requireProfileSelectionOnOpen = false;
bool _useExternalPlayer = false;
@@ -195,6 +197,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
_maxVolume = _settingsService.getMaxVolume();
_enableDiscordRPC = _settingsService.getEnableDiscordRPC();
_matchContentFrameRate = _settingsService.getMatchContentFrameRate();
_tunneledPlayback = _settingsService.getTunneledPlayback();
_useExoPlayer = _settingsService.getUseExoPlayer();
_requireProfileSelectionOnOpen = _settingsService.getRequireProfileSelectionOnOpen();
_useExternalPlayer = _settingsService.getUseExternalPlayer();
@@ -498,6 +501,20 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
await _settingsService.setMatchContentFrameRate(value);
},
),
if (Platform.isAndroid && _useExoPlayer)
SwitchListTile(
focusNode: _focusTracker.get(_kTunneledPlayback),
secondary: const AppIcon(Symbols.tv_options_input_settings_rounded, fill: 1),
title: Text(t.settings.tunneledPlayback),
subtitle: Text(t.settings.tunneledPlaybackDescription),
value: _tunneledPlayback,
onChanged: (value) async {
setState(() {
_tunneledPlayback = value;
});
await _settingsService.setTunneledPlayback(value);
},
),
ListTile(
focusNode: _focusTracker.get(_kBufferSize),
leading: const AppIcon(Symbols.memory_rounded, fill: 1),