diff --git a/backend/app/services/mediamtx/publisher_sync.rb b/backend/app/services/mediamtx/publisher_sync.rb index f5b6a8a..3e20de0 100644 --- a/backend/app/services/mediamtx/publisher_sync.rb +++ b/backend/app/services/mediamtx/publisher_sync.rb @@ -17,7 +17,9 @@ module Mediamtx # RTMP ancora connesso in pausa: non forzare live/reconnect. elsif @session.may_go_live? @session.go_live! - Streams::Overlay::Refresh.call(@session.reload) + @session.reload + Streams::Overlay::Refresh.call(@session) + Streams::YoutubeRelay.ensure_publishing!(@session) if @session.platform == "youtube" elsif @session.reconnecting? && @session.may_reconnect? @session.reconnect! cancel_timeout diff --git a/backend/app/services/streams/youtube_relay.rb b/backend/app/services/streams/youtube_relay.rb index eda8b0d..29e2ff0 100644 --- a/backend/app/services/streams/youtube_relay.rb +++ b/backend/app/services/streams/youtube_relay.rb @@ -56,10 +56,13 @@ module Streams return if session.stream_key.blank? return unless Streams::OverlayRelay.running?(session.id) + pid = pid_for(session.id) + clear_pid(session.id) if pid.present? && !process_alive?(pid.to_i) + return if running?(session.id) last_restart = redis.get(restart_debounce_key(session.id)).to_i - return if last_restart.positive? && (Time.now.to_i - last_restart) < 20 + return if last_restart.positive? && (Time.now.to_i - last_restart) < 5 start(session) redis.set(restart_debounce_key(session.id), Time.now.to_i, ex: 300) @@ -75,6 +78,7 @@ module Streams [ "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "warning", "-fflags", "+genpts+discardcorrupt", "-use_wallclock_as_timestamps", "1", + "-analyzeduration", "10000000", "-probesize", "10000000", "-rw_timeout", "15000000", "-i", intake, "-f", "lavfi", "-i", "anullsrc=channel_layout=mono:sample_rate=48000", diff --git a/mobile/lib/features/camera_mode/camera_screen.dart b/mobile/lib/features/camera_mode/camera_screen.dart index 1a79953..3fa0a47 100644 --- a/mobile/lib/features/camera_mode/camera_screen.dart +++ b/mobile/lib/features/camera_mode/camera_screen.dart @@ -41,6 +41,7 @@ class _CameraScreenState extends ConsumerState { static const _previewKey = ValueKey('camera_streaming_preview'); Timer? _elapsedTimer; + Timer? _sessionSyncTimer; Timer? _telemetryTimer; Timer? _statsTimer; Timer? _scorePullTimer; @@ -75,6 +76,9 @@ class _CameraScreenState extends ConsumerState { ); } }); + _sessionSyncTimer = Timer.periodic(const Duration(seconds: 15), (_) { + unawaited(_syncSessionStartedAt()); + }); _telemetryTimer = Timer.periodic(const Duration(seconds: 10), (_) => _sendTelemetry()); _statsTimer = Timer.periodic(const Duration(seconds: 30), (_) => _fetchStats()); _scorePullTimer = Timer.periodic(const Duration(seconds: 2), (_) { @@ -263,6 +267,20 @@ class _CameraScreenState extends ConsumerState { } catch (_) {} } + /// Il server imposta `started_at` solo al primo go_live; sincronizza per il timer LIVE. + Future _syncSessionStartedAt() async { + final current = ref.read(sessionProvider).session; + if (current == null || current.startedAt != null || !current.isLive) return; + try { + final fresh = await ref.read(apiClientProvider).fetchSession(widget.sessionId); + if (!mounted || fresh.startedAt == null) return; + ref.read(sessionProvider.notifier).setSession( + fresh, + match: ref.read(sessionProvider).match, + ); + } catch (_) {} + } + Future _lockOrientations() async { await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, @@ -497,6 +515,7 @@ class _CameraScreenState extends ConsumerState { @override void dispose() { _elapsedTimer?.cancel(); + _sessionSyncTimer?.cancel(); _telemetryTimer?.cancel(); _statsTimer?.cancel(); _scorePullTimer?.cancel(); diff --git a/mobile/lib/providers/session_provider.dart b/mobile/lib/providers/session_provider.dart index 753e717..a623d0e 100644 --- a/mobile/lib/providers/session_provider.dart +++ b/mobile/lib/providers/session_provider.dart @@ -111,6 +111,8 @@ class SessionNotifier extends StateNotifier { state = state.copyWith(cameraDevice: device); return; } + final startedAt = s.startedAt ?? + ((incoming == 'live' || incoming == 'reconnecting') ? DateTime.now() : null); state = state.copyWith( session: StreamSession( id: s.id, @@ -127,7 +129,7 @@ class SessionNotifier extends StateNotifier { privacyStatus: s.privacyStatus, qualityPreset: s.qualityPreset, targetBitrate: s.targetBitrate, - startedAt: s.startedAt, + startedAt: startedAt, disconnectionCount: s.disconnectionCount, score: s.score, devices: s.devices,