mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
feat: introduce LitePlayerView for video playback with custom zapping controls and mobile fullscreen support.
This commit is contained in:
1 parent
c57273be2c
commit
12b799a7fd
2 files changed
+70
-39
No files matched your search
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user