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