Se il push della cover custom all'agent fallisce (timeout/connection refused), usa lo slate di default invece di abortire la creazione sessione. Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.1 KiB
Ruby
106 lines
3.1 KiB
Ruby
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 = CoverSlatePaths.filename_for(result.record)
|
|
raise Error, "slate filename missing" if filename.blank?
|
|
|
|
# Path completo (non File.basename): il checksum AS può contenere "/" e spezzerebbe il path.
|
|
mediamtx_path = CoverSlatePaths.expected_path(result.record)
|
|
|
|
if cloud_node?
|
|
push_to_agent!(filename, result.record, host_path)
|
|
else
|
|
verify_local_slate!(host_path)
|
|
end
|
|
|
|
@mediamtx_path = mediamtx_path
|
|
rescue StandardError => e
|
|
# Agent cloud down / timeout / disk missing: non bloccare il go-live, usa slate default.
|
|
Rails.logger.warn("[SlateDistributor] session=#{@session.id} #{e.class}: #{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
|