Fix YouTube relay instabile e timer LIVE a zero in app

ffmpeg: probesize/analyzeduration su _air, restart più rapido e avvio relay
al go_live. App: sincronizza started_at dal server e fallback al passaggio live.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-04 08:56:59 +02:00
parent 841e99c775
commit fde1213ca5
4 changed files with 30 additions and 3 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -41,6 +41,7 @@ class _CameraScreenState extends ConsumerState<CameraScreen> {
static const _previewKey = ValueKey<String>('camera_streaming_preview');
Timer? _elapsedTimer;
Timer? _sessionSyncTimer;
Timer? _telemetryTimer;
Timer? _statsTimer;
Timer? _scorePullTimer;
@@ -75,6 +76,9 @@ class _CameraScreenState extends ConsumerState<CameraScreen> {
);
}
});
_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<CameraScreen> {
} catch (_) {}
}
/// Il server imposta `started_at` solo al primo go_live; sincronizza per il timer LIVE.
Future<void> _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<void> _lockOrientations() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
@@ -497,6 +515,7 @@ class _CameraScreenState extends ConsumerState<CameraScreen> {
@override
void dispose() {
_elapsedTimer?.cancel();
_sessionSyncTimer?.cancel();
_telemetryTimer?.cancel();
_statsTimer?.cancel();
_scorePullTimer?.cancel();

View File

@@ -111,6 +111,8 @@ class SessionNotifier extends StateNotifier<SessionState> {
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<SessionState> {
privacyStatus: s.privacyStatus,
qualityPreset: s.qualityPreset,
targetBitrate: s.targetBitrate,
startedAt: s.startedAt,
startedAt: startedAt,
disconnectionCount: s.disconnectionCount,
score: s.score,
devices: s.devices,