Fix registrazione replay in produzione via PublisherSync.
MediaMTX distroless non invoca i webhook wget: accendiamo record sul path quando il telefono pubblica RTMP e lo spegniamo in pausa/offline. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,7 +16,9 @@ module Mediamtx
|
|||||||
clear_publisher_misses!(@session.id)
|
clear_publisher_misses!(@session.id)
|
||||||
if @session.paused?
|
if @session.paused?
|
||||||
# RTMP ancora connesso in pausa: non forzare live/reconnect.
|
# RTMP ancora connesso in pausa: non forzare live/reconnect.
|
||||||
|
sync_path_recording!(@session, enabled: false)
|
||||||
else
|
else
|
||||||
|
sync_path_recording!(@session, enabled: true)
|
||||||
enable_live_path!(@session)
|
enable_live_path!(@session)
|
||||||
if @session.may_go_live?
|
if @session.may_go_live?
|
||||||
@session.go_live!
|
@session.go_live!
|
||||||
@@ -25,21 +27,25 @@ module Mediamtx
|
|||||||
elsif @session.reconnecting? && @session.may_reconnect?
|
elsif @session.reconnecting? && @session.may_reconnect?
|
||||||
@session.reconnect!
|
@session.reconnect!
|
||||||
cancel_timeout
|
cancel_timeout
|
||||||
kick_youtube!(@session)
|
kick_youtube!(@session) if @session.platform == "youtube"
|
||||||
end
|
end
|
||||||
kick_youtube!(@session) if @session.platform == "youtube"
|
kick_youtube!(@session) if @session.platform == "youtube"
|
||||||
end
|
end
|
||||||
elsif !@session.paused? && @session.live? && @session.may_lose_connection?
|
elsif !@session.paused? && @session.live? && @session.may_lose_connection?
|
||||||
if publisher_offline_sustained?(@session.id)
|
if publisher_offline_sustained?(@session.id)
|
||||||
clear_publisher_misses!(@session.id)
|
clear_publisher_misses!(@session.id)
|
||||||
|
sync_path_recording!(@session, enabled: false)
|
||||||
restore_slate_path!(@session)
|
restore_slate_path!(@session)
|
||||||
@session.lose_connection!
|
@session.lose_connection!
|
||||||
schedule_timeout unless @session.paused?
|
schedule_timeout unless @session.paused?
|
||||||
end
|
end
|
||||||
elsif !publisher_online && @session.status.in?(%w[reconnecting connecting]) && !@session.paused?
|
elsif !publisher_online && @session.status.in?(%w[reconnecting connecting]) && !@session.paused?
|
||||||
|
sync_path_recording!(@session, enabled: false)
|
||||||
restore_slate_path!(@session)
|
restore_slate_path!(@session)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sync_path_recording!(@session, enabled: false) if @session.paused? && !publisher_online
|
||||||
|
|
||||||
@session.reload.tap do |session|
|
@session.reload.tap do |session|
|
||||||
schedule_youtube_relay_if_needed(session)
|
schedule_youtube_relay_if_needed(session)
|
||||||
end
|
end
|
||||||
@@ -92,6 +98,31 @@ module Mediamtx
|
|||||||
Rails.logger.warn("[PublisherSync] enable slate session=#{session.id}: #{e.message}")
|
Rails.logger.warn("[PublisherSync] enable slate session=#{session.id}: #{e.message}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# In produzione i webhook MediaMTX (wget) non girano: la registrazione va accesa qui.
|
||||||
|
def sync_path_recording!(session, enabled:)
|
||||||
|
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
|
||||||
|
|
||||||
|
Client.new.set_path_recording(session, enabled: enabled)
|
||||||
|
redis.set(key, desired, ex: 48.hours.to_i)
|
||||||
|
rescue Client::Error => e
|
||||||
|
Rails.logger.warn("[PublisherSync] recording enabled=#{enabled} session=#{session.id}: #{e.message}")
|
||||||
|
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}")
|
||||||
|
rescue Redis::BaseError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def recording_state_key(session_id)
|
||||||
|
"mediamtx:recording:#{session_id}"
|
||||||
|
end
|
||||||
|
|
||||||
# Evita reconnect su un singolo poll offline (RTMP instabile dal telefono).
|
# Evita reconnect su un singolo poll offline (RTMP instabile dal telefono).
|
||||||
def publisher_offline_sustained?(session_id)
|
def publisher_offline_sustained?(session_id)
|
||||||
misses = redis.incr(format("publisher_offline_miss:%s", session_id)).to_i
|
misses = redis.incr(format("publisher_offline_miss:%s", session_id)).to_i
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ module Recordings
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cleanup_mediamtx_path
|
def cleanup_mediamtx_path
|
||||||
|
Mediamtx::PublisherSync.forget_recording_state!(@session.id)
|
||||||
Mediamtx::Client.new.delete_path(@session)
|
Mediamtx::Client.new.delete_path(@session)
|
||||||
rescue Mediamtx::Client::Error => e
|
rescue Mediamtx::Client::Error => e
|
||||||
Rails.logger.warn("[Recordings::UploadFromSession] delete_path: #{e.message}")
|
Rails.logger.warn("[Recordings::UploadFromSession] delete_path: #{e.message}")
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ module Sessions
|
|||||||
def call
|
def call
|
||||||
cancel_timeout_job
|
cancel_timeout_job
|
||||||
@session.pause! if @session.may_pause?
|
@session.pause! if @session.may_pause?
|
||||||
|
Mediamtx::PublisherSync.forget_recording_state!(@session.id)
|
||||||
|
begin
|
||||||
|
Mediamtx::Client.new.set_path_recording(@session, enabled: false)
|
||||||
|
rescue Mediamtx::Client::Error => e
|
||||||
|
Rails.logger.warn("[Sessions::Pause] disable recording: #{e.message}")
|
||||||
|
end
|
||||||
Mediamtx::Client.new.set_always_available(@session, enabled: true)
|
Mediamtx::Client.new.set_always_available(@session, enabled: true)
|
||||||
# Slate su path per HLS in pausa; RTMP telefono si ferma via comando app.
|
# Slate su path per HLS in pausa; RTMP telefono si ferma via comando app.
|
||||||
log_event("paused")
|
log_event("paused")
|
||||||
|
|||||||
42
backend/spec/services/mediamtx/publisher_sync_spec.rb
Normal file
42
backend/spec/services/mediamtx/publisher_sync_spec.rb
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe Mediamtx::PublisherSync do
|
||||||
|
let(:user) { User.create!(email: "sync@test.com", name: "Sync", password: "password123", role: "coach") }
|
||||||
|
let(:club) { Club.create!(name: "Sync Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||||
|
let(:team) { club.teams.create!(name: "Under 16", sport: "volleyball") }
|
||||||
|
let!(:match) { team.matches.create!(opponent_name: "Avversario") }
|
||||||
|
let!(:session) do
|
||||||
|
StreamSession.create!(
|
||||||
|
match: match, user: user, status: "connecting", platform: "matchlivetv",
|
||||||
|
publish_token: "tok", started_at: Time.current
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let(:client) { instance_double(Mediamtx::Client) }
|
||||||
|
let(:redis) { instance_double(Redis, get: nil, set: true, incr: 1, expire: true, del: 1) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Billing::AssignPlan.call(club: club, plan_slug: "premium_full")
|
||||||
|
allow(Mediamtx::Client).to receive(:new).and_return(client)
|
||||||
|
allow(client).to receive_messages(list_paths: [ {
|
||||||
|
"name" => session.mediamtx_path_name, "online" => true, "source" => { "type" => "rtmpConn" }
|
||||||
|
} ])
|
||||||
|
allow(Mediamtx::PublisherOnline).to receive(:active_path?).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:redis).and_return(redis)
|
||||||
|
allow(client).to receive(:set_path_recording)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "abilita la registrazione quando il publisher è online" do
|
||||||
|
described_class.new(session).call
|
||||||
|
|
||||||
|
expect(client).to have_received(:set_path_recording).with(session, enabled: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disabilita la registrazione in pausa" do
|
||||||
|
session.update!(status: "paused")
|
||||||
|
allow(Mediamtx::PublisherOnline).to receive(:active_path?).and_return(true)
|
||||||
|
|
||||||
|
described_class.new(session).call
|
||||||
|
|
||||||
|
expect(client).to have_received(:set_path_recording).with(session, enabled: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user