From b69e9d18122f46787a9852f5a43bbe4e88fb8437 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 Mar 2026 21:56:56 +0100 Subject: [PATCH] fix(mobile): optimize player UI for mobile portrait and tune HLS buffering --- lib/features/iptv/screens/player_screen.dart | 58 ++++++++++++++++++++ web/player_lite.html | 27 +++++++-- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index a5e8e2b..4a98c0a 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -811,6 +811,64 @@ class _PlayerScreenState extends ConsumerState { ); } + List _buildControlButtons(bool isSmallScreen) { + final buttonSize = isSmallScreen ? 40.0 : 44.0; + final playButtonSize = isSmallScreen ? 48.0 : 52.0; + final spacing = isSmallScreen ? 8.0 : 16.0; + + return [ + // Previous Channel + if (widget.channels != null && widget.channels!.length > 1) + _buildSimpleIconButton( + icon: Icons.skip_previous_rounded, + onTap: _previousChannel, + size: buttonSize, + ), + + if (widget.channels != null && widget.channels!.length > 1) + SizedBox(width: spacing), + + // Play/Pause (Large) + _buildSimpleIconButton( + icon: _isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded, + onTap: _togglePlayPause, + size: playButtonSize, + iconSize: isSmallScreen ? 24 : 28, + highlighted: true, + ), + + if (widget.channels != null && widget.channels!.length > 1) + SizedBox(width: spacing), + + // Next Channel + if (widget.channels != null && widget.channels!.length > 1) + _buildSimpleIconButton( + icon: Icons.skip_next_rounded, + onTap: _nextChannel, + size: buttonSize, + ), + + SizedBox(width: spacing), + + // Mute button + _buildSimpleIconButton( + icon: _isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded, + onTap: _toggleMute, + size: buttonSize, + ), + + if (isSmallScreen) ...[ + SizedBox(width: spacing), + // Fullscreen button inside the bar for mobile + _buildSimpleIconButton( + icon: Icons.fullscreen_rounded, + onTap: _toggleFullscreen, + size: buttonSize, + ), + ], + ]; + } + // Simple Icon Button for Live TV player (no BackdropFilter issues) Widget _buildSimpleIconButton({ required IconData icon, diff --git a/web/player_lite.html b/web/player_lite.html index 0cd05c3..23aefcc 100644 --- a/web/player_lite.html +++ b/web/player_lite.html @@ -214,12 +214,15 @@ } const hls = new Hls({ - maxBufferLength: 30, - maxMaxBufferLength: 60, - liveSyncDurationCount: 3, - liveMaxLatencyDurationCount: 6, + maxBufferLength: 60, // doubled for stability on mobile + maxMaxBufferLength: 120, + liveSyncDurationCount: 5, // more segments for safety + liveMaxLatencyDurationCount: 10, liveDurationInfinity: true, - enableWorker: true + enableWorker: true, + backStep: 0.2, // step back slightly on stall + nudgeOffset: 0.1, + nudgeMaxRetry: 5 }); window.hlsInstance = hls; @@ -505,10 +508,22 @@ video.currentTime = parseFloat(data.value); } } else if (data.type === 'request_fullscreen') { + log('Fullscreen requested from parent'); const el = document.documentElement; const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen; + if (requestMethod) { - requestMethod.call(el).catch(e => log('Fullscreen error: ' + e)); + // Browsers require a user gesture for fullscreen. + // This message usually comes from a Flutter button tap, so it should count as a gesture. + requestMethod.call(el).catch(e => { + log('Fullscreen API error: ' + e); + // Fallback: try element-specific request for iOS + if (video.webkitEnterFullscreen) { + video.webkitEnterFullscreen(); + } + }); + } else if (video.webkitEnterFullscreen) { + video.webkitEnterFullscreen(); } } });