diff --git a/backend/app/services/mediamtx/publisher_sync.rb b/backend/app/services/mediamtx/publisher_sync.rb index 4515818..053dd5a 100644 --- a/backend/app/services/mediamtx/publisher_sync.rb +++ b/backend/app/services/mediamtx/publisher_sync.rb @@ -16,7 +16,9 @@ module Mediamtx 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) if @session.may_go_live? @session.go_live! @@ -25,21 +27,25 @@ module Mediamtx elsif @session.reconnecting? && @session.may_reconnect? @session.reconnect! cancel_timeout - kick_youtube!(@session) + kick_youtube!(@session) if @session.platform == "youtube" end kick_youtube!(@session) if @session.platform == "youtube" end 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) 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) restore_slate_path!(@session) end + sync_path_recording!(@session, enabled: false) if @session.paused? && !publisher_online + @session.reload.tap do |session| schedule_youtube_relay_if_needed(session) end @@ -92,6 +98,31 @@ 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:) + 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). def publisher_offline_sustained?(session_id) misses = redis.incr(format("publisher_offline_miss:%s", session_id)).to_i diff --git a/backend/app/services/recordings/upload_from_session.rb b/backend/app/services/recordings/upload_from_session.rb index 45aa71e..cccb92a 100644 --- a/backend/app/services/recordings/upload_from_session.rb +++ b/backend/app/services/recordings/upload_from_session.rb @@ -94,6 +94,7 @@ module Recordings end def cleanup_mediamtx_path + Mediamtx::PublisherSync.forget_recording_state!(@session.id) Mediamtx::Client.new.delete_path(@session) rescue Mediamtx::Client::Error => e Rails.logger.warn("[Recordings::UploadFromSession] delete_path: #{e.message}") diff --git a/backend/app/services/sessions/pause.rb b/backend/app/services/sessions/pause.rb index 534ad39..b21fe6d 100644 --- a/backend/app/services/sessions/pause.rb +++ b/backend/app/services/sessions/pause.rb @@ -7,6 +7,12 @@ module Sessions def call cancel_timeout_job @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) # Slate su path per HLS in pausa; RTMP telefono si ferma via comando app. log_event("paused") diff --git a/backend/spec/services/mediamtx/publisher_sync_spec.rb b/backend/spec/services/mediamtx/publisher_sync_spec.rb new file mode 100644 index 0000000..ef43a19 --- /dev/null +++ b/backend/spec/services/mediamtx/publisher_sync_spec.rb @@ -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