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:
@@ -12,28 +12,32 @@ module Mediamtx
|
||||
end
|
||||
end
|
||||
|
||||
def create_path(session)
|
||||
path = session.mediamtx_path_name
|
||||
record = session.match.team.entitlements.recording_enabled_for_mediamtx?
|
||||
def create_overlay_path(session)
|
||||
path = session.mediamtx_overlay_path_name
|
||||
body = {
|
||||
source: "publisher",
|
||||
overridePublisher: true,
|
||||
alwaysAvailable: true,
|
||||
alwaysAvailableFile: slate_file_path,
|
||||
alwaysAvailableTracks: slate_tracks,
|
||||
record: record,
|
||||
recordPath: "/recordings/%path/%Y-%m-%d_%H-%M-%S",
|
||||
recordSegmentDuration: "60s"
|
||||
record: false
|
||||
}
|
||||
if record
|
||||
retention_hours = session.match.team.entitlements.recording_retention_days * 24 + 48
|
||||
body[:recordDeleteAfter] = "#{retention_hours}h"
|
||||
end
|
||||
unless session.matchlivetv_platform?
|
||||
body[:runOnReady] = run_on_ready_script(session)
|
||||
body[:runOnReadyRestart] = true
|
||||
body[:runOnNotReady] = webhook_curl(session, "disconnect")
|
||||
response = @conn.post("/v3/config/paths/add/#{CGI.escape(path)}", body)
|
||||
unless response.success?
|
||||
err = response.body.is_a?(Hash) ? response.body["error"] : response.body
|
||||
raise Error, "MediaMTX overlay path create failed: #{response.status} #{err}"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def create_path(session)
|
||||
path = session.mediamtx_path_name
|
||||
# record: false finché non c'è publisher — con alwaysAvailable MediaMTX registrerebbe
|
||||
# solo la slate (schermo nero) in pausa/attesa.
|
||||
body = recording_body(session, enabled: false).merge(
|
||||
source: "publisher",
|
||||
overridePublisher: true,
|
||||
alwaysAvailable: true,
|
||||
alwaysAvailableFile: slate_file_path
|
||||
)
|
||||
# YouTube: relay continuo gestito da Streams::YoutubeRelay (ffmpeg in sidekiq/rails).
|
||||
response = @conn.post("/v3/config/paths/add/#{CGI.escape(path)}", body)
|
||||
unless response.success?
|
||||
err = response.body.is_a?(Hash) ? response.body["error"] : response.body
|
||||
@@ -44,18 +48,29 @@ module Mediamtx
|
||||
|
||||
def delete_path(session)
|
||||
delete_path_name(session.mediamtx_path_name)
|
||||
delete_path_name(session.mediamtx_overlay_path_name)
|
||||
end
|
||||
|
||||
def delete_path_name(path_name)
|
||||
@conn.post("/v3/config/paths/delete/#{CGI.escape(path_name)}")
|
||||
end
|
||||
|
||||
def set_path_recording(session, enabled:)
|
||||
path = session.mediamtx_path_name
|
||||
body = recording_body(session, enabled: enabled)
|
||||
response = @conn.patch("/v3/config/paths/patch/#{CGI.escape(path)}", body)
|
||||
unless response.success?
|
||||
err = response.body.is_a?(Hash) ? response.body["error"] : response.body
|
||||
raise Error, "MediaMTX recording patch failed: #{response.status} #{err}"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def patch_path(session)
|
||||
path = session.mediamtx_path_name
|
||||
body = {
|
||||
alwaysAvailable: true,
|
||||
alwaysAvailableFile: slate_file_path,
|
||||
alwaysAvailableTracks: slate_tracks
|
||||
alwaysAvailableFile: slate_file_path
|
||||
}
|
||||
response = @conn.patch("/v3/config/paths/patch/#{CGI.escape(path)}", body)
|
||||
unless response.success?
|
||||
@@ -81,6 +96,21 @@ module Mediamtx
|
||||
|
||||
private
|
||||
|
||||
def recording_body(session, enabled:)
|
||||
ent = session.match.team.entitlements
|
||||
can_record = ent.recording_enabled_for_mediamtx?
|
||||
body = {
|
||||
record: enabled && can_record,
|
||||
recordPath: "/recordings/%path/%Y-%m-%d_%H-%M-%S",
|
||||
recordSegmentDuration: "60s"
|
||||
}
|
||||
if enabled && can_record
|
||||
retention_hours = ent.recording_retention_days * 24 + 48
|
||||
body[:recordDeleteAfter] = "#{retention_hours}h"
|
||||
end
|
||||
body
|
||||
end
|
||||
|
||||
def publish_webhook(session)
|
||||
webhook_curl(session, "connect")
|
||||
end
|
||||
@@ -122,12 +152,5 @@ module Mediamtx
|
||||
def slate_file_path
|
||||
ENV.fetch("MEDIAMTX_SLATE_FILE", "/slates/offline.mp4")
|
||||
end
|
||||
|
||||
def slate_tracks
|
||||
[
|
||||
{ codec: "H264" },
|
||||
{ codec: "MPEG4Audio", sampleRate: 48_000, channelCount: 2 }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
51
backend/app/services/mediamtx/publisher_sync.rb
Normal file
51
backend/app/services/mediamtx/publisher_sync.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
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
|
||||
def initialize(session)
|
||||
@session = session
|
||||
end
|
||||
|
||||
def call
|
||||
return @session if @session.terminal?
|
||||
|
||||
info = Mediamtx::Client.new.list_paths.find { |i| i["name"] == @session.mediamtx_path_name }
|
||||
publisher_online = info && info["online"]
|
||||
|
||||
if publisher_online
|
||||
if @session.paused?
|
||||
# RTMP ancora connesso in pausa: non forzare live/reconnect.
|
||||
elsif @session.may_go_live?
|
||||
@session.go_live!
|
||||
Streams::Overlay::Refresh.call(@session.reload)
|
||||
elsif @session.reconnecting? && @session.may_reconnect?
|
||||
@session.reconnect!
|
||||
cancel_timeout
|
||||
Streams::Overlay::Refresh.call(@session.reload)
|
||||
end
|
||||
elsif !@session.paused? && @session.live? && @session.may_lose_connection?
|
||||
@session.lose_connection!
|
||||
schedule_timeout unless @session.paused?
|
||||
end
|
||||
|
||||
@session.reload
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cancel_timeout
|
||||
return if @session.timeout_job_id.blank?
|
||||
|
||||
Sidekiq::ScheduledSet.new.find_job(@session.timeout_job_id)&.delete
|
||||
@session.update!(timeout_job_id: nil)
|
||||
end
|
||||
|
||||
def schedule_timeout
|
||||
job = DisconnectionTimeoutJob.perform_in(
|
||||
MatchLiveTv.reconnect_timeout_seconds.seconds,
|
||||
@session.id
|
||||
)
|
||||
@session.update!(timeout_job_id: job)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user