Stabilizza live YouTube/RTMP e fix crash rotazione mobile.

Pipeline YouTube con relay, overlay e publisher sync lato backend; fix GL pauseGlDuringRotation su Android per evitare crash in rotazione durante lo streaming Flutter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 15:30:55 +02:00
parent 1058c644bd
commit 854738b46d
65 changed files with 2606 additions and 579 deletions

View File

@@ -2,12 +2,14 @@ import 'dart:async';
import 'dart:math';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../app/theme.dart';
import '../../providers/score_provider.dart';
import '../../providers/score_sync_provider.dart';
import '../../providers/session_provider.dart';
import '../../shared/api_client.dart';
import '../../shared/models/match.dart';
@@ -74,9 +76,9 @@ class _StepNetworkTestState extends ConsumerState<StepNetworkTest> {
_youtubePoll?.cancel();
var attempts = 0;
_youtubePoll = Timer.periodic(const Duration(seconds: 2), (_) async {
_youtubePoll = Timer.periodic(const Duration(seconds: 3), (_) async {
attempts++;
if (!mounted || attempts > 30) {
if (!mounted || attempts > 60) {
_youtubePoll?.cancel();
return;
}
@@ -150,14 +152,10 @@ class _StepNetworkTestState extends ConsumerState<StepNetworkTest> {
if (session.score != null) {
ref.read(scoreProvider.notifier).reset(session.score!);
}
// Tabellone via HTTP; WebSocket solo dalla camera (evita doppio connect controller/camera).
try {
await ref.read(websocketServiceProvider).connect(
sessionId: session.id,
deviceRole: 'controller',
);
} catch (_) {
// Tabellone locale; sync quando la camera si connette.
}
await ref.read(scoreSyncProvider).pullFromServer();
} catch (_) {}
}
String _connectivityLabel(List<ConnectivityResult> results) {
@@ -184,18 +182,22 @@ class _StepNetworkTestState extends ConsumerState<StepNetworkTest> {
ref.read(scoreProvider.notifier).reset(const ScoreState());
}
if (!mounted) return;
await ref.read(websocketServiceProvider).disconnect();
if (!mounted) return;
context.go('/session/${started.id}/camera');
try {
final ws = ref.read(websocketServiceProvider);
await ws.connect(sessionId: started.id, deviceRole: 'controller');
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Diretta avviata ma sync limitata: $e')),
);
}
} on DioException catch (e) {
if (mounted) {
final msg = e.response?.data is Map
? (e.response!.data['error'] ?? e.response!.data['message'])?.toString()
: null;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
msg?.isNotEmpty == true ? 'Errore avvio diretta: $msg' : 'Errore avvio diretta: ${e.message}',
),
),
);
}
} catch (e) {
if (mounted) {