Aggiunge overlay server-side burn-in e stabilizza diretta live.
Tabellone, badge e brand sono ricodificati nel flusso _air via OverlayRelay; sync punteggio HTTP, volume condiviso rails/sidekiq, qualità 720p migliorata e badge CONNECTING in ripresa da pausa. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
103
backend/app/services/streams/youtube_relay.rb
Normal file
103
backend/app/services/streams/youtube_relay.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
module Streams
|
||||
# Relay continuo verso YouTube: legge l'uscita MediaMTX del path (camera o slate alwaysAvailable)
|
||||
# senza interrompere FLV quando il telefono si mette in pausa o perde rete.
|
||||
class YoutubeRelay
|
||||
class Error < StandardError; end
|
||||
|
||||
REDIS_KEY = "youtube_relay:pid:%s"
|
||||
|
||||
class << self
|
||||
def start(session)
|
||||
return unless session.platform == "youtube"
|
||||
return if session.stream_key.blank?
|
||||
return if session.terminal?
|
||||
|
||||
stop(session) if pid_for(session.id).present?
|
||||
|
||||
log_path = log_file(session)
|
||||
FileUtils.mkdir_p(File.dirname(log_path))
|
||||
intake = mediamtx_intake_url(session)
|
||||
output = "rtmp://a.rtmp.youtube.com/live2/#{session.stream_key}"
|
||||
|
||||
pid = Process.spawn(
|
||||
"ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "warning",
|
||||
"-rw_timeout", "15000000",
|
||||
"-i", intake,
|
||||
"-c", "copy",
|
||||
"-f", "flv",
|
||||
output,
|
||||
%i[out err] => log_path,
|
||||
pgroup: true
|
||||
)
|
||||
Process.detach(pid)
|
||||
store_pid(session.id, pid)
|
||||
Rails.logger.info("[YoutubeRelay] started pid=#{pid} session=#{session.id}")
|
||||
pid
|
||||
rescue Errno::ENOENT => e
|
||||
raise Error, "ffmpeg non disponibile: #{e.message}"
|
||||
end
|
||||
|
||||
def stop(session)
|
||||
pid = pid_for(session.id)
|
||||
return false if pid.blank?
|
||||
|
||||
terminate_pid(pid)
|
||||
clear_pid(session.id)
|
||||
Rails.logger.info("[YoutubeRelay] stopped pid=#{pid} session=#{session.id}")
|
||||
true
|
||||
end
|
||||
|
||||
def running?(session_id)
|
||||
pid = pid_for(session_id)
|
||||
pid.present? && process_alive?(pid)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mediamtx_intake_url(session)
|
||||
base = ENV.fetch("MEDIAMTX_INTERNAL_RTMP_URL", "rtmp://mediamtx:1935")
|
||||
"#{base.chomp('/')}/#{session.mediamtx_overlay_path_name}"
|
||||
end
|
||||
|
||||
def log_file(session)
|
||||
Rails.root.join("log", "youtube_relay_#{session.id}.log").to_s
|
||||
end
|
||||
|
||||
def redis
|
||||
@redis ||= Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
|
||||
end
|
||||
|
||||
def store_pid(session_id, pid)
|
||||
redis.set(format(REDIS_KEY, session_id), pid, ex: 48.hours.to_i)
|
||||
end
|
||||
|
||||
def clear_pid(session_id)
|
||||
redis.del(format(REDIS_KEY, session_id))
|
||||
end
|
||||
|
||||
def pid_for(session_id)
|
||||
redis.get(format(REDIS_KEY, session_id))
|
||||
end
|
||||
|
||||
def process_alive?(pid)
|
||||
Process.kill(0, pid.to_i)
|
||||
true
|
||||
rescue Errno::ESRCH, Errno::EPERM
|
||||
false
|
||||
end
|
||||
|
||||
def terminate_pid(pid)
|
||||
Process.kill("TERM", -pid.to_i)
|
||||
sleep 0.5
|
||||
rescue Errno::ESRCH
|
||||
nil
|
||||
else
|
||||
begin
|
||||
Process.kill("KILL", -pid.to_i)
|
||||
rescue Errno::ESRCH
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user