Rendi affidabile pausa con slate e conferma i controlli laterali.
Evita buchi HLS/YouTube in pausa (path slate ricreata, reload player, no patch recording inutili) e richiede conferma su tasti laterali app/regia. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,7 +74,48 @@ module Mediamtx
|
||||
set_always_available(session, enabled: true)
|
||||
end
|
||||
|
||||
# Slate alwaysAvailable: copertina sullo stesso path quando il telefono è offline.
|
||||
# Garantisce path config + slate: se MediaMTX ha perso la config (restart → all_others),
|
||||
# in pausa non resterebbe nessuno stream verso HLS/YouTube.
|
||||
def ensure_path_with_slate!(session)
|
||||
path = session.mediamtx_path_name
|
||||
response = @conn.get("/v3/config/paths/get/#{CGI.escape(path)}")
|
||||
missing = response.status == 404 ||
|
||||
(response.body.is_a?(Hash) && response.body["error"].present?)
|
||||
|
||||
if missing
|
||||
forget_always_available(path)
|
||||
begin
|
||||
create_path(session)
|
||||
rescue Error
|
||||
# Path creato in parallelo o già presente: forza solo la slate.
|
||||
forget_always_available(path)
|
||||
set_always_available(session, enabled: true)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
conf = response.body.is_a?(Hash) ? response.body : {}
|
||||
desired = slate_file_path(session)
|
||||
if conf["alwaysAvailable"] == true && conf["alwaysAvailableFile"].to_s == desired.to_s
|
||||
remember_always_available(path, enabled: true)
|
||||
return true
|
||||
end
|
||||
|
||||
forget_always_available(path)
|
||||
set_always_available(session, enabled: true)
|
||||
end
|
||||
|
||||
# Spegnere record solo se attivo: ogni PATCH path ricarica MediaMTX e distrugge i muxer HLS.
|
||||
def disable_recording_if_active!(session)
|
||||
path = session.mediamtx_path_name
|
||||
response = @conn.get("/v3/config/paths/get/#{CGI.escape(path)}")
|
||||
return true unless response.success?
|
||||
return true unless response.body.is_a?(Hash) && response.body["record"] == true
|
||||
|
||||
set_path_recording(session, enabled: false)
|
||||
end
|
||||
|
||||
# Slate alwaysAvailable: copertina sullo stesso path quando il telefono è offline.
|
||||
# Resta accesa anche con publisher in onda (MediaMTX usa il publisher se presente).
|
||||
def set_always_available(session, enabled:)
|
||||
path = session.mediamtx_path_name
|
||||
|
||||
@@ -79,9 +79,8 @@ module Mediamtx
|
||||
end
|
||||
|
||||
def restore_slate_path!(session)
|
||||
return if session.platform == "matchlivetv"
|
||||
|
||||
Client.for_session(session).set_always_available(session, enabled: true)
|
||||
# Anche su matchlivetv: dopo restart MediaMTX la path può cadere su all_others senza slate.
|
||||
Client.for_session(session).ensure_path_with_slate!(session)
|
||||
rescue Client::Error => e
|
||||
Rails.logger.warn("[PublisherSync] enable slate session=#{session.id}: #{e.message}")
|
||||
end
|
||||
@@ -146,6 +145,14 @@ module Mediamtx
|
||||
nil
|
||||
end
|
||||
|
||||
def self.remember_recording_off!(session_id)
|
||||
redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
|
||||
redis.set("mediamtx:recording:#{session_id}", "0", ex: 48.hours.to_i)
|
||||
redis.set(format("mediamtx:recording:patched_at:%s", session_id), Time.current.to_i, ex: 48.hours.to_i)
|
||||
rescue Redis::BaseError
|
||||
nil
|
||||
end
|
||||
|
||||
def recording_state_key(session_id)
|
||||
"mediamtx:recording:#{session_id}"
|
||||
end
|
||||
|
||||
@@ -7,11 +7,13 @@ module Sessions
|
||||
def call
|
||||
cancel_timeout_job
|
||||
@session.pause! if @session.may_pause?
|
||||
Mediamtx::PublisherSync.forget_recording_state!(@session.id)
|
||||
begin
|
||||
mtx = Mediamtx::Client.for_session(@session)
|
||||
mtx.set_path_recording(@session, enabled: false)
|
||||
mtx.set_always_available(@session, enabled: true)
|
||||
# Ricrea/riallinea slate prima di spegnere l'RTMP del telefono.
|
||||
mtx.ensure_path_with_slate!(@session)
|
||||
# PATCH recording solo se era attivo (evita kill muxer HLS a ogni pausa).
|
||||
mtx.disable_recording_if_active!(@session)
|
||||
Mediamtx::PublisherSync.remember_recording_off!(@session.id)
|
||||
rescue Mediamtx::Client::Error => e
|
||||
Rails.logger.warn("[Sessions::Pause] MediaMTX: #{e.message}")
|
||||
end
|
||||
|
||||
@@ -4,10 +4,15 @@ module Streams
|
||||
ENV.fetch("MEDIAMTX_SLATES_CUSTOM_DIR", "/slates/custom")
|
||||
end
|
||||
|
||||
# ActiveStorage checksum is base64 e può contenere "/", "+", "=" — non usabile come path.
|
||||
def self.safe_digest(checksum)
|
||||
checksum.to_s.tr("+/", "-_").delete("=")
|
||||
end
|
||||
|
||||
def self.filename_for(record)
|
||||
return nil unless record.cover_slate.attached?
|
||||
|
||||
digest = record.cover_slate.blob.checksum
|
||||
digest = safe_digest(record.cover_slate.blob.checksum)
|
||||
prefix = record.class.name.underscore
|
||||
"#{prefix}-#{record.id}-#{digest}.mp4"
|
||||
end
|
||||
|
||||
@@ -31,8 +31,11 @@ module Streams
|
||||
end
|
||||
|
||||
host_path = result.slate_local_path
|
||||
filename = File.basename(host_path)
|
||||
mediamtx_path = "#{CoverSlatePaths.slates_custom_root}/#{filename}"
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user