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

@@ -0,0 +1,107 @@
module Youtube
# Broadcast YouTube (API) + relay ffmpeg copy da MediaMTX → RTMPS. Nessun overlay.
class LivePipeline
PIPELINE_DEBOUNCE_KEY = "youtube_pipeline:debounce:%s"
ACTIVATE_DEBOUNCE_KEY = "youtube_activate:debounce:%s"
SETUP_DEBOUNCE_KEY = "youtube_setup:debounce:%s"
PUBLISHER_SEEN_KEY = "publisher_online:seen:%s"
class << self
def schedule!(session, force: false)
return unless pipeline_eligible?(session)
ensure_broadcast_setup!(session) unless broadcast_ready?(session)
return unless force || redis.set(format(PIPELINE_DEBOUNCE_KEY, session.id), "1", nx: true, ex: 8)
Streams::YoutubeRelay.ensure_publishing!(session) if broadcast_ready?(session)
if broadcast_ready?(session)
YoutubeIngestWatchdogJob.schedule_chain(session.id)
schedule_activate!(session, force: true, wait: 20.seconds)
end
Rails.logger.info("[Youtube::LivePipeline] scheduled session=#{session.id} broadcast_ready=#{broadcast_ready?(session)}")
end
def schedule_activate!(session, force: false, wait: 8.seconds)
return unless pipeline_eligible?(session)
return unless broadcast_ready?(session)
debounce_ok = force || redis.set(format(ACTIVATE_DEBOUNCE_KEY, session.id), "1", nx: true, ex: 12)
return unless debounce_ok
YoutubeBroadcastActivateJob.set(wait: wait).perform_later(session.id, 1)
end
def ensure_broadcast_setup!(session)
return unless pipeline_eligible?(session)
return if broadcast_ready?(session)
return if redis.exists?(format(YoutubeBroadcastSetupJob::RUN_LOCK_KEY, session.id)).positive?
return unless redis.set(format(SETUP_DEBOUNCE_KEY, session.id), "1", nx: true, ex: 90)
YoutubeBroadcastSetupJob.perform_later(session.id)
Rails.logger.info("[Youtube::LivePipeline] queued YoutubeBroadcastSetupJob session=#{session.id}")
end
def tick_session!(session)
return unless pipeline_eligible?(session)
ensure_broadcast_setup!(session) unless broadcast_ready?(session)
online = Mediamtx::PublisherOnline.active?(session)
seen_key = format(PUBLISHER_SEEN_KEY, session.id)
was_online = redis.get(seen_key) == "1"
redis.set(seen_key, online ? "1" : "0", ex: 4.hours.to_i)
unless online
Streams::YoutubeRelay.ensure_publishing!(session) if broadcast_ready?(session)
return
end
if session.paused?
Streams::YoutubeRelay.ensure_publishing!(session) if broadcast_ready?(session)
return
end
Mediamtx::Client.new.set_always_available(session, enabled: false)
session.go_live! if session.may_go_live?
session.reconnect! if session.reconnecting? && session.may_reconnect?
if !was_online
Rails.logger.info("[Youtube::LivePipeline] publisher online session=#{session.id}")
schedule!(session, force: true)
return
end
if Streams::YoutubeRelay.running?(session.id)
schedule_activate!(session)
else
schedule!(session)
end
rescue Mediamtx::Client::Error => e
Rails.logger.warn("[Youtube::LivePipeline] tick session=#{session.id}: #{e.message}")
end
def pipeline_eligible?(session)
session.present? &&
session.platform == "youtube" &&
!session.terminal? &&
session.status.in?(%w[live connecting reconnecting paused])
end
def broadcast_ready?(session)
session.youtube_broadcast_id.present? && session.stream_key.present?
end
def eligible?(session)
pipeline_eligible?(session) && broadcast_ready?(session)
end
private
def redis
@redis ||= Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
end
end
end
end