Aggiunge copertina sponsor custom solo Premium Full, con override in wizard e slate sui nodi di streaming.
Permette upload da portale e app con ereditarietà partita→squadra→società, generazione MP4 via ffmpeg e distribuzione su home lab e nodi CPX per pause e assenza segnale. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
module Streams
|
||||
# Assicura che la slate MP4 custom sia sul nodo assegnato (home/lab locale, CPX via agent).
|
||||
class SlateDistributor
|
||||
class Error < StandardError; end
|
||||
|
||||
def self.slate_path_for_session(session)
|
||||
new(session).mediamtx_slate_path
|
||||
end
|
||||
|
||||
def self.ensure_for!(session)
|
||||
new(session).ensure!
|
||||
end
|
||||
|
||||
def initialize(session)
|
||||
@session = session
|
||||
@match = session.match
|
||||
@node = session.stream_node
|
||||
@resolver = CoverResolver.new(@match)
|
||||
end
|
||||
|
||||
def mediamtx_slate_path
|
||||
ensure! unless defined?(@mediamtx_path)
|
||||
@mediamtx_path
|
||||
end
|
||||
|
||||
def ensure!
|
||||
result = @resolver.resolve
|
||||
unless result
|
||||
@mediamtx_path = default_slate_path
|
||||
return @mediamtx_path
|
||||
end
|
||||
|
||||
host_path = result.slate_local_path
|
||||
filename = File.basename(host_path)
|
||||
mediamtx_path = "#{CoverSlatePaths.slates_custom_root}/#{filename}"
|
||||
|
||||
if cloud_node?
|
||||
push_to_agent!(filename, result.record, host_path)
|
||||
else
|
||||
verify_local_slate!(host_path)
|
||||
end
|
||||
|
||||
@mediamtx_path = mediamtx_path
|
||||
rescue Error => e
|
||||
Rails.logger.warn("[SlateDistributor] session=#{@session.id} #{e.message}")
|
||||
@mediamtx_path = default_slate_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cloud_node?
|
||||
@node&.role == "cloud" && agent_url.present?
|
||||
end
|
||||
|
||||
def agent_url
|
||||
ENV["STREAM_NODE_RELAY_AGENT_URL"].presence || @node&.relay_agent_url
|
||||
end
|
||||
|
||||
def agent_secret
|
||||
ENV["STREAM_NODE_AGENT_SECRET"].presence || "mediamtx_webhook_dev_secret"
|
||||
end
|
||||
|
||||
def push_to_agent!(filename, record, host_path)
|
||||
bytes = read_slate_bytes(record, host_path)
|
||||
raise Error, "slate bytes missing for #{filename}" if bytes.blank?
|
||||
|
||||
uri = URI.parse("#{agent_url.chomp('/')}/slates/#{CGI.escape(filename)}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.open_timeout = 5
|
||||
http.read_timeout = 120
|
||||
req = Net::HTTP::Put.new(uri)
|
||||
req["Authorization"] = "Bearer #{agent_secret}" if agent_secret.present?
|
||||
req["Content-Type"] = "video/mp4"
|
||||
req.body = bytes
|
||||
res = http.request(req)
|
||||
return if res.is_a?(Net::HTTPSuccess)
|
||||
|
||||
raise Error, "agent PUT /slates/#{filename} HTTP #{res.code} #{res.body.to_s.truncate(200)}"
|
||||
end
|
||||
|
||||
def read_slate_bytes(record, host_path)
|
||||
return File.binread(host_path) if host_path.present? && File.exist?(host_path)
|
||||
|
||||
return unless record.cover_slate.attached?
|
||||
|
||||
record.cover_slate.blob.open(tmpdir: Dir.tmpdir) do |file|
|
||||
return File.binread(file.path)
|
||||
end
|
||||
end
|
||||
|
||||
def verify_local_slate!(host_path)
|
||||
return if host_path.present? && File.exist?(host_path)
|
||||
|
||||
raise Error, "slate missing on disk: #{host_path}"
|
||||
end
|
||||
|
||||
def default_slate_path
|
||||
ENV.fetch("MEDIAMTX_SLATE_FILE", "/slates/offline.mp4")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user