Implementa pausa diretta con copertina slate e ripresa.

Metti in pausa ferma RTMP lato app mantenendo la camera aperta, MediaMTX
manda offline.mp4 agli spettatori e Chiudi termina definitivamente.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 23:31:48 +02:00
parent 1bd81d84af
commit a87cda156b
19 changed files with 305 additions and 45 deletions

View File

@@ -18,6 +18,9 @@ module Mediamtx
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"
@@ -43,6 +46,21 @@ module Mediamtx
@conn.post("/v3/config/paths/delete/#{CGI.escape(path_name)}")
end
def patch_path(session)
path = session.mediamtx_path_name
body = {
alwaysAvailable: true,
alwaysAvailableFile: slate_file_path,
alwaysAvailableTracks: slate_tracks
}
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 path patch failed: #{response.status} #{err}"
end
true
end
def list_paths
response = @conn.get("/v3/paths/list")
return [] unless response.success?
@@ -87,7 +105,6 @@ module Mediamtx
end
def relay_script(session)
return "echo 'no youtube key'" if session.stream_key.blank?
return "echo 'youtube relay skipped'" unless session.platform == "youtube"
key = session.stream_key
@@ -97,5 +114,16 @@ module Mediamtx
rtmp://a.rtmp.youtube.com/live2/#{key} 2>/var/log/ffmpeg-#{path}.log
SCRIPT
end
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

View File

@@ -5,14 +5,30 @@ module Sessions
end
def call
cancel_timeout_job
@session.pause! if @session.may_pause?
ensure_slate_on_path
log_event("paused")
SessionChannel.broadcast_message(@session, { type: "command", action: "pause_stream" })
SessionChannel.broadcast_message(@session, { type: "stream_event", event: "paused" })
@session
end
private
def cancel_timeout_job
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 ensure_slate_on_path
Mediamtx::Client.new.patch_path(@session)
rescue Mediamtx::Client::Error => e
Rails.logger.warn("[Sessions::Pause] slate path patch failed: #{e.message}")
end
def log_event(type)
@session.stream_events.create!(event_type: type, occurred_at: Time.current, metadata: {})
end

View File

@@ -0,0 +1,32 @@
module Sessions
class Resume
def initialize(session)
@session = session
end
def call
unless @session.paused?
raise Teams::EntitlementError.new("La sessione non è in pausa", code: "not_paused")
end
cancel_timeout_job
@session.begin_connect! if @session.may_begin_connect?
log_event("resumed")
SessionChannel.broadcast_message(@session, { type: "command", action: "resume_stream" })
@session
end
private
def cancel_timeout_job
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 log_event(type)
@session.stream_events.create!(event_type: type, occurred_at: Time.current, metadata: {})
end
end
end

View File

@@ -23,6 +23,8 @@ module Webhooks
private
def handle_connect(session)
return if session.paused?
return if session.live? && session.stream_events.where(event_type: "connected").exists?
if session.reconnecting?
@@ -38,6 +40,7 @@ module Webhooks
end
def handle_disconnect(session)
return if session.paused?
return unless session.live? || session.connecting?
if session.connecting?