Fix YouTube in attesa: audio AAC di riserva, retry attivazione broadcast
Il relay verso YouTube ora gestisce _air senza traccia audio, ritenta activate_broadcast finché l'ingest è active e riduce la finestra scheduledStartTime a ~20s. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,7 +21,7 @@ module Streams
|
||||
|
||||
# YouTube richiede FLV H.264/AAC stabili; -c copy lascia spesso streamStatus=inactive.
|
||||
pid = Process.spawn(
|
||||
*youtube_ffmpeg_args(intake, output, log_path),
|
||||
*youtube_ffmpeg_args(intake, output, log_path, with_audio: intake_has_audio?(intake)),
|
||||
pgroup: true
|
||||
)
|
||||
Process.detach(pid)
|
||||
@@ -68,13 +68,28 @@ module Streams
|
||||
|
||||
private
|
||||
|
||||
def youtube_ffmpeg_args(intake, output, log_path)
|
||||
[
|
||||
def youtube_ffmpeg_args(intake, output, log_path, with_audio:)
|
||||
base = [
|
||||
"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",
|
||||
"-i", intake
|
||||
]
|
||||
encode = youtube_encode_flags(output, log_path)
|
||||
|
||||
if with_audio
|
||||
base + ["-map", "0:v:0", "-map", "0:a:0"] + encode
|
||||
else
|
||||
# _air senza audio (overlay 0:a?): YouTube richiede comunque AAC.
|
||||
base + [
|
||||
"-f", "lavfi", "-i", "anullsrc=channel_layout=mono:sample_rate=48000",
|
||||
"-map", "0:v:0", "-map", "1:a:0"
|
||||
] + encode
|
||||
end
|
||||
end
|
||||
|
||||
def youtube_encode_flags(output, log_path)
|
||||
[
|
||||
"-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",
|
||||
@@ -84,10 +99,21 @@ module Streams
|
||||
]
|
||||
end
|
||||
|
||||
def intake_has_audio?(url)
|
||||
require "open3"
|
||||
stdout, _stderr, status = Open3.capture3(
|
||||
"ffprobe", "-v", "error", "-rw_timeout", "5000000",
|
||||
"-select_streams", "a", "-show_entries", "stream=index", "-of", "csv=p=0", url
|
||||
)
|
||||
status.success? && stdout.strip.present?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
def schedule_youtube_activate(session)
|
||||
return if session.youtube_broadcast_id.blank?
|
||||
|
||||
YoutubeBroadcastActivateJob.set(wait: 8.seconds).perform_later(session.id)
|
||||
YoutubeBroadcastActivateJob.schedule_with_retries(session.id)
|
||||
end
|
||||
|
||||
def mediamtx_intake_url(session)
|
||||
|
||||
@@ -51,31 +51,38 @@ module Youtube
|
||||
end
|
||||
|
||||
# Porta in onda appena c’è ingest su YouTube (non attendere scheduledStartTime).
|
||||
# @return [Symbol] :live, :waiting_ingest, :transitioned, :skipped
|
||||
def activate_broadcast!(broadcast_id)
|
||||
return if broadcast_id.blank?
|
||||
return if @credential.blank? || missing_oauth_config?
|
||||
return :skipped if broadcast_id.blank?
|
||||
return :skipped if @credential.blank? || missing_oauth_config?
|
||||
|
||||
client = authorized_client
|
||||
item = client.list_live_broadcasts("status,contentDetails", id: broadcast_id).items&.first
|
||||
return unless item
|
||||
return :skipped unless item
|
||||
|
||||
status = item.status.life_cycle_status.to_s
|
||||
return if status == "live"
|
||||
return :live if status == "live"
|
||||
|
||||
stream_id = item.content_details&.bound_stream_id
|
||||
if stream_id.present?
|
||||
stream = client.list_live_streams("status", id: stream_id).items&.first
|
||||
return if stream&.status&.stream_status != "active"
|
||||
stream_status = stream&.status&.stream_status.to_s
|
||||
return :waiting_ingest unless stream_status == "active"
|
||||
end
|
||||
|
||||
if %w[ready created].include?(status)
|
||||
client.transition_live_broadcast("testing", broadcast_id, "id,status")
|
||||
status = "testing"
|
||||
end
|
||||
client.transition_live_broadcast("live", broadcast_id, "id,status") if status == "testing"
|
||||
if status == "testing"
|
||||
client.transition_live_broadcast("live", broadcast_id, "id,status")
|
||||
return :transitioned
|
||||
end
|
||||
|
||||
:skipped
|
||||
rescue Google::Apis::Error => e
|
||||
Rails.logger.warn("[Youtube::BroadcastService] activate #{broadcast_id}: #{e.message}")
|
||||
nil
|
||||
:skipped
|
||||
end
|
||||
|
||||
def complete_broadcast!(broadcast_id)
|
||||
@@ -131,7 +138,7 @@ module Youtube
|
||||
# YouTube richiede scheduledStartTime nel futuro; per «vai subito» usiamo ~1 min (auto_start al RTMP).
|
||||
def normalize_scheduled_start_time(scheduled_at)
|
||||
now = Time.current.utc
|
||||
minimum = now + 90.seconds
|
||||
minimum = now + 20.seconds
|
||||
maximum = now + 7.days
|
||||
|
||||
candidate =
|
||||
|
||||
Reference in New Issue
Block a user