Riduce reload MediaMTX in live e allinea ingest mobile al preset qualità.

Evita patch recording ripetute che distruggevano i muxer HLS (500 ops) e fa encoderare 1080p/720p con codec e bitrate coerenti al wizard.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-21 22:17:18 +02:00
parent e20c2ae619
commit 7d5d66840f
15 changed files with 195 additions and 40 deletions

View File

@@ -2,6 +2,8 @@ module Mediamtx
# MediaMTX in produzione è distroless: runOnReady con wget non funziona.
# Sincronizziamo lo stato Rails leggendo l'API paths (poll da /live/:id/status.json).
class PublisherSync
RECORDING_PATCH_COOLDOWN = -> { ENV.fetch("MEDIAMTX_RECORDING_PATCH_COOLDOWN_SECS", "45").to_i.seconds }
def initialize(session)
@session = session
end
@@ -9,17 +11,15 @@ module Mediamtx
def call
return @session if @session.terminal?
info = Mediamtx::Client.new.list_paths.find { |i| i["name"] == @session.mediamtx_path_name }
publisher_online = Mediamtx::PublisherOnline.active_path?(info)
path_info = Mediamtx::PublisherOnline.path_info(@session)
publisher_online = Mediamtx::PublisherOnline.active_path?(path_info)
if publisher_online
clear_publisher_misses!(@session.id)
if @session.paused?
# RTMP ancora connesso in pausa: non forzare live/reconnect.
sync_path_recording!(@session, enabled: false)
else
sync_path_recording!(@session, enabled: true)
enable_live_path!(@session)
enable_live_path_once!(@session)
if @session.may_go_live?
@session.go_live!
@session.reload
@@ -34,17 +34,18 @@ module Mediamtx
elsif !@session.paused? && @session.live? && @session.may_lose_connection?
if publisher_offline_sustained?(@session.id)
clear_publisher_misses!(@session.id)
sync_path_recording!(@session, enabled: false)
disable_recording_on_path!(@session, path_info: path_info)
restore_slate_path!(@session)
@session.lose_connection!
schedule_timeout unless @session.paused?
end
elsif !publisher_online && @session.status.in?(%w[reconnecting connecting]) && !@session.paused?
sync_path_recording!(@session, enabled: false)
# Non togliere recording qui: un gap RTMP breve non deve ricaricare la config MediaMTX.
restore_slate_path!(@session)
end
sync_path_recording!(@session, enabled: false) if @session.paused? && !publisher_online
desired_recording = recording_should_be_active?(publisher_online)
sync_path_recording!(@session, enabled: desired_recording, path_info: path_info)
@session.reload.tap do |session|
schedule_youtube_relay_if_needed(session)
@@ -79,14 +80,15 @@ module Mediamtx
Youtube::LivePipeline.schedule!(session, force: force)
end
# Match Live TV: lascia alwaysAvailable attivo — MediaMTX usa il publisher quando c'è
# e la copertina nei gap RTMP (telefono instabile). Disattivare la slate lasciava HLS
# senza stream ("no stream is available") e il player web in pausa/spinner.
def enable_live_path!(session)
def enable_live_path_once!(session)
return unless session.platform == "youtube"
key = format("youtube:slate_disabled:%s", session.id)
return unless redis.set(key, "1", nx: true, ex: 48.hours.to_i)
Client.new.set_always_available(session, enabled: false)
rescue Client::Error => e
redis.del(format("youtube:slate_disabled:%s", session.id))
Rails.logger.warn("[PublisherSync] disable slate session=#{session.id}: #{e.message}")
end
@@ -98,23 +100,62 @@ module Mediamtx
Rails.logger.warn("[PublisherSync] enable slate session=#{session.id}: #{e.message}")
end
# In produzione i webhook MediaMTX (wget) non girano: la registrazione va accesa qui.
def sync_path_recording!(session, enabled:)
def recording_should_be_active?(publisher_online)
return false unless @session.match.team.entitlements.recording_enabled_for_mediamtx?
return false if @session.paused? || @session.terminal?
return false unless publisher_online
return false unless @session.status.in?(%w[live reconnecting])
true
end
def disable_recording_on_path!(session, path_info:)
sync_path_recording!(session, enabled: false, path_info: path_info, force: true)
end
# In produzione i webhook MediaMTX (wget) non girano: la registrazione va accesa al primo go-live.
# Evitiamo patch ripetute: ogni reload config distrugge i muxer HLS (500 sul proxy /hls/).
def sync_path_recording!(session, enabled:, path_info: nil, force: false)
return unless session.match.team.entitlements.recording_enabled_for_mediamtx?
key = recording_state_key(session.id)
desired = enabled ? "1" : "0"
return if redis.get(key) == desired
return if path_info && path_recording_active?(path_info) == enabled
if !enabled && !force && recording_patch_on_cooldown?(session.id)
Rails.logger.debug("[PublisherSync] skip recording patch session=#{session.id} (cooldown)")
return
end
Client.new.set_path_recording(session, enabled: enabled)
redis.set(key, desired, ex: 48.hours.to_i)
mark_recording_patch!(session.id)
rescue Client::Error => e
Rails.logger.warn("[PublisherSync] recording enabled=#{enabled} session=#{session.id}: #{e.message}")
end
def path_recording_active?(path_info)
path_info["record"] == true
end
def recording_patch_on_cooldown?(session_id)
last = redis.get(recording_patch_at_key(session_id)).to_i
last.positive? && Time.at(last) > RECORDING_PATCH_COOLDOWN.call.ago
end
def mark_recording_patch!(session_id)
redis.set(recording_patch_at_key(session_id), Time.current.to_i, ex: 48.hours.to_i)
end
def recording_patch_at_key(session_id)
format("mediamtx:recording:patched_at:%s", session_id)
end
def self.forget_recording_state!(session_id)
redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
redis.del("mediamtx:recording:#{session_id}")
redis.del(format("mediamtx:recording:patched_at:%s", session_id))
rescue Redis::BaseError
nil
end

View File

@@ -2,6 +2,7 @@ module Sessions
# Sceglie il preset più alto supportato dall'upload misurato (test rete).
class SelectQuality
UPLOAD_HEADROOM = 0.8
UPLOAD_HEADROOM_1080P = 1.0
PRESETS = [
{ id: "1080p_30_4.5mbps", target_bitrate: 4_500_000, target_fps: 30 },
@@ -23,7 +24,8 @@ module Sessions
end
def upload_sufficient?(preset)
@upload_mbps >= (preset[:target_bitrate] / 1_000_000.0 * UPLOAD_HEADROOM)
headroom = preset[:id].start_with?("1080p") ? UPLOAD_HEADROOM_1080P : UPLOAD_HEADROOM
@upload_mbps >= (preset[:target_bitrate] / 1_000_000.0 * headroom)
end
end
end