feat: implement sub-visibility toggle

Closes #597
This commit is contained in:
edde746
2026-03-02 19:07:56 +01:00
parent c372920965
commit 94395feaba
4 changed files with 66 additions and 5 deletions
@@ -12,6 +12,9 @@ class PlayerAndroid extends PlayerBase {
int? _bufferSizeBytes;
bool _tunnelingEnabled = true;
/// Stored subtitle track ID when subtitles are hidden via sub-visibility.
String? _hiddenSubtitleTrackId;
@override
MethodChannel get methodChannel => _methodChannel;
@@ -193,6 +196,29 @@ class PlayerAndroid extends PlayerBase {
case 'tunneled-playback':
_tunnelingEnabled = value != 'no';
break;
case 'sub-visibility':
if (value == 'no') {
// Store current subtitle track and disable
final current = state.track.subtitle;
if (current != null && current.id != 'no') {
_hiddenSubtitleTrackId = current.id;
await selectSubtitleTrack(SubtitleTrack.off);
}
} else {
// Restore previously hidden subtitle track
final storedId = _hiddenSubtitleTrackId;
if (storedId != null) {
_hiddenSubtitleTrackId = null;
final track = state.tracks.subtitle.cast<SubtitleTrack?>().firstWhere(
(t) => t?.id == storedId,
orElse: () => null,
);
if (track != null) {
await selectSubtitleTrack(track);
}
}
}
break;
// Other properties are no-ops for ExoPlayer
}
}
@@ -96,6 +96,9 @@ class DesktopVideoControls extends StatefulWidget {
/// Called to toggle ambient lighting (passed to settings sheet)
final VoidCallback? onToggleAmbientLighting;
/// Whether subtitles are currently visible (false = hidden via sub-visibility toggle)
final bool subtitlesVisible;
/// Whether to show the queue button
final bool showQueueButton;
@@ -151,6 +154,7 @@ class DesktopVideoControls extends StatefulWidget {
this.liveChannelName,
this.isAmbientLightingEnabled = false,
this.onToggleAmbientLighting,
this.subtitlesVisible = true,
this.showQueueButton = false,
this.onQueueItemSelected,
});
@@ -697,6 +701,7 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
onNavigateLeft: navigateFromTrackToVolume,
canControl: widget.canControl,
isLive: widget.isLive,
subtitlesVisible: widget.subtitlesVisible,
showQueueButton: widget.showQueueButton,
onQueueItemSelected: widget.onQueueItemSelected,
shaderService: widget.shaderService,
+28 -4
View File
@@ -256,6 +256,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
bool _showPerformanceOverlay = false;
// Long-press 2x speed state
bool _isLongPressing = false;
// Subtitle visibility toggle state
bool _subtitlesVisible = true;
// Skip marker button focus node (for TV D-pad navigation)
late final FocusNode _skipMarkerFocusNode;
double? _rateBeforeLongPress;
@@ -551,8 +553,28 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
}
}
// ignore: no-empty-block - stub for future subtitle toggle implementation
void _toggleSubtitles() {}
void _toggleSubtitles() {
final currentTrack = widget.player.state.track.subtitle;
// No-op if no subtitle track is selected
if (currentTrack == null || currentTrack.id == 'no') return;
final newVisible = !_subtitlesVisible;
widget.player.setProperty('sub-visibility', newVisible ? 'yes' : 'no');
setState(() {
_subtitlesVisible = newVisible;
});
}
void _onSubtitleTrackChanged(SubtitleTrack track) {
// Reset visibility when user explicitly picks a new subtitle track
if (track.id != 'no' && !_subtitlesVisible) {
widget.player.setProperty('sub-visibility', 'yes');
setState(() {
_subtitlesVisible = true;
});
}
widget.onSubtitleTrackChanged?.call(track);
}
void _toggleShader() {
final shaderService = widget.shaderService;
@@ -982,7 +1004,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
onToggleFullscreen: _toggleFullscreen,
onSwitchVersion: _switchMediaVersion,
onAudioTrackChanged: widget.onAudioTrackChanged,
onSubtitleTrackChanged: widget.onSubtitleTrackChanged,
onSubtitleTrackChanged: _onSubtitleTrackChanged,
subtitlesVisible: _subtitlesVisible,
onLoadSeekTimes: () async {
if (mounted) {
await _loadSeekTimes();
@@ -2009,7 +2032,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
onToggleAlwaysOnTop: Platform.isMacOS ? null : _toggleAlwaysOnTop,
onSwitchVersion: _switchMediaVersion,
onAudioTrackChanged: widget.onAudioTrackChanged,
onSubtitleTrackChanged: widget.onSubtitleTrackChanged,
onSubtitleTrackChanged: _onSubtitleTrackChanged,
subtitlesVisible: _subtitlesVisible,
onLoadSeekTimes: () async {
if (mounted) {
await _loadSeekTimes();
@@ -73,6 +73,9 @@ class TrackChapterControls extends StatelessWidget {
/// Whether this is a live TV stream (hides speed settings).
final bool isLive;
/// Whether subtitles are currently visible (false = hidden via sub-visibility toggle)
final bool subtitlesVisible;
/// Whether to show the queue button
final bool showQueueButton;
@@ -110,6 +113,7 @@ class TrackChapterControls extends StatelessWidget {
this.onNavigateLeft,
this.canControl = true,
this.isLive = false,
this.subtitlesVisible = true,
this.showQueueButton = false,
this.onQueueItemSelected,
this.shaderService,
@@ -267,10 +271,12 @@ class TrackChapterControls extends StatelessWidget {
// Subtitles button
if (_hasSubtitles(tracks)) {
final currentIndex = buttonIndex;
final hasActiveSubtitle = tracks?.track.subtitle != null && tracks!.track.subtitle!.id != 'no';
final isHidden = hasActiveSubtitle && !subtitlesVisible;
buttons.add(
_buildTrackButton(
buttonIndex: currentIndex,
icon: Symbols.subtitles_rounded,
icon: isHidden ? Symbols.subtitles_off_rounded : Symbols.subtitles_rounded,
tooltip: t.videoControls.subtitlesButton,
semanticLabel: t.videoControls.subtitlesButton,
tracks: tracks,