mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
feat(player): bouton plein écran et reconnexion du proxy live
Plein écran — le bouton n'existait que dans les contrôles VOD, sous une icône aspect_ratio peu parlante, et les contrôles Live TV n'en avaient aucun. Ajout du bouton côté Live TV et passage aux icônes fullscreen / fullscreen_exit. L'état suit désormais l'évènement fullscreenchange du document : sortir par la touche Échap laissait l'icône désynchronisée. Proxy live — la route /api/live/<id>.ts relayait la source sans aucune reprise. Les panneaux Xtream ferment régulièrement la connexion en cours de route, et le MediaSource du navigateur recevait alors un sourceEnded : la lecture s'arrêtait net au bout de quelques dizaines de secondes. Le proxy rouvre maintenant la source tant que le client écoute, avec quatre tentatives et une seconde d'attente entre chaque, le temps que le panneau libère le slot de connexion. Le compteur repart de zéro dès qu'une connexion a tenu plus d'une minute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b82b101aa
commit
f490e96448
2 files changed
+159
-38
No files matched your search
@@ -61,6 +61,12 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
bool _isMuted = false;
|
||||
bool _ignoreStatusUpdates = false;
|
||||
|
||||
/// Reflète l'état plein écran du document. Le navigateur peut en sortir
|
||||
/// sans passer par notre bouton (touche Échap), d'où l'écoute de
|
||||
/// `fullscreenchange` plutôt qu'un simple booléen basculé au clic.
|
||||
bool _isFullscreen = false;
|
||||
StreamSubscription<html.Event>? _fullscreenSubscription;
|
||||
|
||||
/// Server-side transcoding quality. Live TV defaults to `source`
|
||||
/// (direct MPEG-TS proxy, zero transcoding); VOD defaults to `high`.
|
||||
late StreamQuality _quality = widget.streamType == StreamType.live
|
||||
@@ -85,15 +91,32 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
_onHover(); // Start the auto-hide timer
|
||||
_initializePlayer(startTimeOverride: widget.startTime);
|
||||
_setupMessageListener();
|
||||
_fullscreenSubscription =
|
||||
html.document.onFullscreenChange.listen((_) => _syncFullscreenState());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messageSubscription?.cancel();
|
||||
_fullscreenSubscription?.cancel();
|
||||
_controlsTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _documentIsFullscreen {
|
||||
final doc = html.document;
|
||||
return doc.fullscreenElement != null ||
|
||||
(doc as dynamic).webkitFullscreenElement != null ||
|
||||
(doc as dynamic).mozFullScreenElement != null;
|
||||
}
|
||||
|
||||
void _syncFullscreenState() {
|
||||
final value = _documentIsFullscreen;
|
||||
if (mounted && value != _isFullscreen) {
|
||||
setState(() => _isFullscreen = value);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializePlayer({
|
||||
double? startTimeOverride,
|
||||
bool isChannelSwitch = false,
|
||||
@@ -320,15 +343,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
|
||||
void _toggleFullscreen() {
|
||||
try {
|
||||
final doc = html.document;
|
||||
final isFullscreen = doc.fullscreenElement != null ||
|
||||
(doc as dynamic).webkitFullscreenElement != null ||
|
||||
(doc as dynamic).mozFullScreenElement != null;
|
||||
|
||||
if (isFullscreen) {
|
||||
doc.exitFullscreen();
|
||||
if (_documentIsFullscreen) {
|
||||
html.document.exitFullscreen();
|
||||
} else {
|
||||
final element = doc.documentElement;
|
||||
final element = html.document.documentElement;
|
||||
if (element != null) {
|
||||
element.requestFullscreen();
|
||||
}
|
||||
@@ -337,6 +355,12 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
print('[PlayerScreen] Fullscreen error: $e');
|
||||
_sendMessage({'type': 'request_fullscreen'});
|
||||
}
|
||||
// `fullscreenchange` arrive de façon asynchrone : relire l'état juste
|
||||
// après l'appel évite que l'icône reste figée si l'évènement est perdu.
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 150),
|
||||
_syncFullscreenState,
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleMute() {
|
||||
@@ -860,10 +884,14 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildGlassIconButton(
|
||||
icon: Icons.aspect_ratio_rounded,
|
||||
icon: _isFullscreen
|
||||
? Icons.fullscreen_exit_rounded
|
||||
: Icons.fullscreen_rounded,
|
||||
onTap: _toggleFullscreen,
|
||||
transparent: true,
|
||||
tooltip: 'Plein écran',
|
||||
tooltip: _isFullscreen
|
||||
? 'Quitter le plein écran'
|
||||
: 'Plein écran',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -972,6 +1000,18 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
onSelected: _changeQuality,
|
||||
size: buttonSize,
|
||||
),
|
||||
|
||||
SizedBox(width: spacing),
|
||||
|
||||
// Fullscreen
|
||||
_buildSimpleIconButton(
|
||||
icon: _isFullscreen
|
||||
? Icons.fullscreen_exit_rounded
|
||||
: Icons.fullscreen_rounded,
|
||||
onTap: _toggleFullscreen,
|
||||
size: buttonSize,
|
||||
tooltip: _isFullscreen ? 'Quitter le plein écran' : 'Plein écran',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user