feat: introduce LitePlayerView for video playback with custom zapping controls and mobile fullscreen support.

This commit is contained in:
Michael committed 2026-03-09 14:18:51 +01:00
1 parent c57273be2c
commit 12b799a7fd
2 files changed
+70 -39

No files matched your search

+69 -39
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -38,34 +39,51 @@ class _LitePlayerViewState extends ConsumerState<LitePlayerView> {
void didUpdateWidget(LitePlayerView oldWidget) { void didUpdateWidget(LitePlayerView oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.streamUrl != widget.streamUrl) { if (oldWidget.streamUrl != widget.streamUrl) {
_disposeControllers(); _disposeControllers();
_initializePlayer(); _initializePlayer();
} }
} }
Future<void> _initializePlayer() async { Future<void> _initializePlayer() async {
try { try {
setState(() => _error = false); setState(() => _error = false);
_videoController = VideoPlayerController.networkUrl(Uri.parse(widget.streamUrl)); _videoController =
VideoPlayerController.networkUrl(Uri.parse(widget.streamUrl));
await _videoController.initialize(); await _videoController.initialize();
_chewieController = ChewieController( _chewieController = ChewieController(
videoPlayerController: _videoController, videoPlayerController: _videoController,
autoPlay: true, autoPlay: true,
looping: false, looping: false,
isLive: widget.isLive, 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( materialProgressColors: ChewieProgressColors(
playedColor: AppColors.primary, playedColor: AppColors.primary,
handleColor: Colors.white, handleColor: Colors.white,
backgroundColor: Colors.white24, backgroundColor: Colors.white24,
bufferedColor: Colors.white54, 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 // Custom Controls to include Zapping
additionalOptions: (context) { additionalOptions: (context) {
return <OptionItem>[ return <OptionItem>[
if (widget.onNext != null) if (widget.onNext != null)
OptionItem( OptionItem(
onTap: (context) { onTap: (context) {
widget.onNext!(); widget.onNext!();
@@ -74,7 +92,7 @@ class _LitePlayerViewState extends ConsumerState<LitePlayerView> {
iconData: Icons.skip_next, iconData: Icons.skip_next,
title: 'Next Channel', title: 'Next Channel',
), ),
if (widget.onPrevious != null) if (widget.onPrevious != null)
OptionItem( OptionItem(
onTap: (context) { onTap: (context) {
widget.onPrevious!(); widget.onPrevious!();
@@ -86,6 +104,11 @@ class _LitePlayerViewState extends ConsumerState<LitePlayerView> {
]; ];
}, },
); );
_chewieController!.addListener(() {
if (mounted) setState(() {});
});
setState(() {}); setState(() {});
} catch (e) { } catch (e) {
debugPrint('Error initializing Lite Player: $e'); debugPrint('Error initializing Lite Player: $e');
@@ -114,12 +137,13 @@ class _LitePlayerViewState extends ConsumerState<LitePlayerView> {
children: [ children: [
const Icon(Icons.error_outline, color: Colors.red, size: 48), const Icon(Icons.error_outline, color: Colors.red, size: 48),
const SizedBox(height: 16), 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), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
_disposeControllers(); _disposeControllers();
_initializePlayer(); _initializePlayer();
}, },
child: const Text('Retry'), child: const Text('Retry'),
), ),
@@ -128,40 +152,46 @@ class _LitePlayerViewState extends ConsumerState<LitePlayerView> {
); );
} }
if (_chewieController != null && _chewieController!.videoPlayerController.value.isInitialized) { if (_chewieController != null &&
_chewieController!.videoPlayerController.value.isInitialized) {
return Stack( return Stack(
children: [ children: [
Chewie(controller: _chewieController!), Chewie(controller: _chewieController!),
// Overlay Zapping Controls for easy access (since Chewie hides controls) // Overlay Zapping Controls for easy access (since Chewie hides controls)
if (widget.onPrevious != null || widget.onNext != null) // Hide them during fullscreen to avoid clutter
Positioned( if ((widget.onPrevious != null || widget.onNext != null) &&
bottom: 20, !(_chewieController?.isFullScreen ?? false))
right: 80, Positioned(
child: Row( bottom: 20,
mainAxisSize: MainAxisSize.min, right: 80,
children: [ child: Row(
if (widget.onPrevious != null) ...[ mainAxisSize: MainAxisSize.min,
IconButton( children: [
onPressed: widget.onPrevious, if (widget.onPrevious != null) ...[
icon: const Icon(Icons.navigate_before, color: Colors.white, size: 32), IconButton(
tooltip: 'Previous Channel', onPressed: widget.onPrevious,
), icon: const Icon(Icons.navigate_before,
const SizedBox(width: 16), color: Colors.white, size: 32),
], tooltip: 'Previous Channel',
if (widget.onNext != null) ),
IconButton( const SizedBox(width: 16),
onPressed: widget.onNext, ],
icon: const Icon(Icons.navigate_next, color: Colors.white, size: 32), if (widget.onNext != null)
tooltip: 'Next Channel', 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));
} }
} }
+1
View File
@@ -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] Correction Chemins HLS Relatifs (Bug chemins absolus FFmpeg)
- [x] Résolution Erreur 502 Proxy (Nettoyage Headers + API Buffer) - [x] Résolution Erreur 502 Proxy (Nettoyage Headers + API Buffer)
- [x] Optimisation HLS iOS (Force Keyframes + Anti-Empty Playlist) - [x] Optimisation HLS iOS (Force Keyframes + Anti-Empty Playlist)
- [x] Support Plein Écran Mobile (Orientations Landscape + UI Auto-hide)
## Progress Log ## Progress Log