From 12b799a7fdc48324676fd2dc4febd0cd0e9b817d Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 9 Mar 2026 14:18:51 +0100 Subject: [PATCH] feat: introduce LitePlayerView for video playback with custom zapping controls and mobile fullscreen support. --- .../iptv/widgets/lite_player_view.dart | 108 +++++++++++------- task.md | 1 + 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/lib/features/iptv/widgets/lite_player_view.dart b/lib/features/iptv/widgets/lite_player_view.dart index afdface..3974c7d 100644 --- a/lib/features/iptv/widgets/lite_player_view.dart +++ b/lib/features/iptv/widgets/lite_player_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -38,34 +39,51 @@ class _LitePlayerViewState extends ConsumerState { void didUpdateWidget(LitePlayerView oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.streamUrl != widget.streamUrl) { - _disposeControllers(); - _initializePlayer(); + _disposeControllers(); + _initializePlayer(); } } Future _initializePlayer() async { try { setState(() => _error = false); - _videoController = VideoPlayerController.networkUrl(Uri.parse(widget.streamUrl)); + _videoController = + VideoPlayerController.networkUrl(Uri.parse(widget.streamUrl)); await _videoController.initialize(); - + _chewieController = ChewieController( videoPlayerController: _videoController, autoPlay: true, looping: false, isLive: widget.isLive, - aspectRatio: _videoController.value.aspectRatio > 0 ? _videoController.value.aspectRatio : 16/9, + aspectRatio: _videoController.value.aspectRatio > 0 + ? _videoController.value.aspectRatio + : 16 / 9, materialProgressColors: ChewieProgressColors( playedColor: AppColors.primary, handleColor: Colors.white, backgroundColor: Colors.white24, bufferedColor: Colors.white54, ), - + + // Fullscreen configuration for Mobile + allowFullScreen: true, + allowMuting: true, + showControls: true, + deviceOrientationsOnEnterFullScreen: [ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ], + deviceOrientationsAfterFullScreen: [ + DeviceOrientation.portraitUp, + ], + systemOverlaysOnEnterFullScreen: [], // Full immersive + systemOverlaysAfterFullScreen: SystemUiOverlay.values, + // Custom Controls to include Zapping additionalOptions: (context) { return [ - if (widget.onNext != null) + if (widget.onNext != null) OptionItem( onTap: (context) { widget.onNext!(); @@ -74,7 +92,7 @@ class _LitePlayerViewState extends ConsumerState { iconData: Icons.skip_next, title: 'Next Channel', ), - if (widget.onPrevious != null) + if (widget.onPrevious != null) OptionItem( onTap: (context) { widget.onPrevious!(); @@ -86,6 +104,11 @@ class _LitePlayerViewState extends ConsumerState { ]; }, ); + + _chewieController!.addListener(() { + if (mounted) setState(() {}); + }); + setState(() {}); } catch (e) { debugPrint('Error initializing Lite Player: $e'); @@ -114,12 +137,13 @@ class _LitePlayerViewState extends ConsumerState { children: [ const Icon(Icons.error_outline, color: Colors.red, size: 48), const SizedBox(height: 16), - const Text('Error loading video', style: TextStyle(color: Colors.white)), + const Text('Error loading video', + style: TextStyle(color: Colors.white)), const SizedBox(height: 16), ElevatedButton( onPressed: () { - _disposeControllers(); - _initializePlayer(); + _disposeControllers(); + _initializePlayer(); }, child: const Text('Retry'), ), @@ -128,40 +152,46 @@ class _LitePlayerViewState extends ConsumerState { ); } - if (_chewieController != null && _chewieController!.videoPlayerController.value.isInitialized) { + if (_chewieController != null && + _chewieController!.videoPlayerController.value.isInitialized) { return Stack( children: [ Chewie(controller: _chewieController!), - + // Overlay Zapping Controls for easy access (since Chewie hides controls) - if (widget.onPrevious != null || widget.onNext != null) - Positioned( - bottom: 20, - right: 80, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.onPrevious != null) ...[ - IconButton( - onPressed: widget.onPrevious, - icon: const Icon(Icons.navigate_before, color: Colors.white, size: 32), - tooltip: 'Previous Channel', - ), - const SizedBox(width: 16), - ], - if (widget.onNext != null) - IconButton( - onPressed: widget.onNext, - icon: const Icon(Icons.navigate_next, color: Colors.white, size: 32), - tooltip: 'Next Channel', - ), - ], - ), - ), + // Hide them during fullscreen to avoid clutter + if ((widget.onPrevious != null || widget.onNext != null) && + !(_chewieController?.isFullScreen ?? false)) + Positioned( + bottom: 20, + right: 80, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.onPrevious != null) ...[ + IconButton( + onPressed: widget.onPrevious, + icon: const Icon(Icons.navigate_before, + color: Colors.white, size: 32), + tooltip: 'Previous Channel', + ), + const SizedBox(width: 16), + ], + if (widget.onNext != null) + IconButton( + onPressed: widget.onNext, + icon: const Icon(Icons.navigate_next, + color: Colors.white, size: 32), + tooltip: 'Next Channel', + ), + ], + ), + ), ], ); } - - return const Center(child: CircularProgressIndicator(color: AppColors.primary)); + + return const Center( + child: CircularProgressIndicator(color: AppColors.primary)); } } diff --git a/task.md b/task.md index b4099d4..76c0b6d 100644 --- a/task.md +++ b/task.md @@ -24,6 +24,7 @@ Refonte du Guide TV pour inclure les catégories et corriger l'EPG. - [x] Correction Chemins HLS Relatifs (Bug chemins absolus FFmpeg) - [x] Résolution Erreur 502 Proxy (Nettoyage Headers + API Buffer) - [x] Optimisation HLS iOS (Force Keyframes + Anti-Empty Playlist) +- [x] Support Plein Écran Mobile (Orientations Landscape + UI Auto-hide) ## Progress Log