From d08cd365279aca8dd4b291f12e38036bb549e44f Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Thu, 4 Jun 2026 08:32:30 +0200 Subject: [PATCH] Fix YouTube waiting: transcode verso RTMP e sync stato live. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Il relay non usa più copy (stream inactive su YouTube); PublisherSync nel refresh overlay; activate solo quando lo stream YouTube è active. Co-authored-by: Cursor --- backend/app/jobs/overlay_refresh_job.rb | 1 + backend/app/services/streams/youtube_relay.rb | 33 +++++++++++++++---- .../app/services/youtube/broadcast_service.rb | 8 ++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/backend/app/jobs/overlay_refresh_job.rb b/backend/app/jobs/overlay_refresh_job.rb index fbf0993..0ed2ba6 100644 --- a/backend/app/jobs/overlay_refresh_job.rb +++ b/backend/app/jobs/overlay_refresh_job.rb @@ -26,6 +26,7 @@ class OverlayRefreshJob < ApplicationJob return self.class.cancel_chain(session_id) unless Streams::OverlayRelay.running?(session_id) Streams::Overlay::Refresh.call(session) + Mediamtx::PublisherSync.new(session).call if session.status.in?(%w[connecting reconnecting live paused]) if session.platform == "youtube" Streams::YoutubeRelay.ensure_publishing!(session) end diff --git a/backend/app/services/streams/youtube_relay.rb b/backend/app/services/streams/youtube_relay.rb index b1d363b..7dcfd9b 100644 --- a/backend/app/services/streams/youtube_relay.rb +++ b/backend/app/services/streams/youtube_relay.rb @@ -19,14 +19,9 @@ module Streams intake = mediamtx_intake_url(session) output = "rtmp://a.rtmp.youtube.com/live2/#{session.stream_key}" + # YouTube richiede FLV H.264/AAC stabili; -c copy lascia spesso streamStatus=inactive. pid = Process.spawn( - "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "warning", - "-rw_timeout", "15000000", - "-i", intake, - "-c", "copy", - "-f", "flv", - output, - %i[out err] => log_path, + *youtube_ffmpeg_args(intake, output, log_path), pgroup: true ) Process.detach(pid) @@ -62,13 +57,33 @@ module Streams 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 + start(session) + redis.set(restart_debounce_key(session.id), Time.now.to_i, ex: 300) rescue Error => e Rails.logger.warn("[YoutubeRelay] ensure_publishing session=#{session.id}: #{e.message}") end private + def youtube_ffmpeg_args(intake, output, log_path) + [ + "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "warning", + "-fflags", "+genpts+discardcorrupt", "-use_wallclock_as_timestamps", "1", + "-rw_timeout", "15000000", + "-i", intake, + "-map", "0:v:0", "-map", "0:a:0", + "-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", + "-pix_fmt", "yuv420p", "-r", "30", "-g", "30", "-keyint_min", "30", "-sc_threshold", "0", + "-b:v", "4000k", "-maxrate", "4000k", "-bufsize", "8000k", + "-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "1", + "-f", "flv", output, + %i[out err] => log_path + ] + end + def schedule_youtube_activate(session) return if session.youtube_broadcast_id.blank? @@ -88,6 +103,10 @@ module Streams @redis ||= Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0")) end + def restart_debounce_key(session_id) + format("youtube_relay:debounce:%s", session_id) + end + def store_pid(session_id, pid) redis.set(format(REDIS_KEY, session_id), pid, ex: 48.hours.to_i) end diff --git a/backend/app/services/youtube/broadcast_service.rb b/backend/app/services/youtube/broadcast_service.rb index 64a508c..ee1a831 100644 --- a/backend/app/services/youtube/broadcast_service.rb +++ b/backend/app/services/youtube/broadcast_service.rb @@ -56,12 +56,18 @@ module Youtube return if @credential.blank? || missing_oauth_config? client = authorized_client - item = client.list_live_broadcasts("status", id: broadcast_id).items&.first + item = client.list_live_broadcasts("status", id: broadcast_id, mine: true).items&.first return unless item status = item.status.life_cycle_status.to_s return if status == "live" + stream_id = item.content_details&.bound_stream_id + if stream_id.present? + stream = client.list_live_streams("status", id: stream_id, mine: true).items&.first + return if stream&.status&.stream_status != "active" + end + if %w[ready created].include?(status) client.transition_live_broadcast("testing", broadcast_id, "id,status") status = "testing"