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:
@@ -53,6 +53,8 @@ class SessionChannel < ApplicationCable::Channel
|
||||
Sessions::Stop.new(session).call
|
||||
when "pause_stream"
|
||||
Sessions::Pause.new(session).call
|
||||
when "resume_stream"
|
||||
Sessions::Resume.new(session).call
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ module Api
|
||||
render json: session_json(@session)
|
||||
end
|
||||
|
||||
def resume
|
||||
Sessions::Resume.new(@session).call
|
||||
render json: session_json(@session)
|
||||
end
|
||||
|
||||
def events
|
||||
events = @session.stream_events.recent.limit(100)
|
||||
render json: events.map { |e| event_json(e) }
|
||||
|
||||
@@ -63,6 +63,7 @@ module Public
|
||||
closed = @session.terminal?
|
||||
{
|
||||
status: @session.status,
|
||||
paused: @session.paused?,
|
||||
stream_closed: closed,
|
||||
live: !closed && (@session.live? || mediamtx_online?(@session)),
|
||||
on_air: !closed && mediamtx_online?(@session),
|
||||
|
||||
@@ -54,7 +54,7 @@ class StreamSession < ApplicationRecord
|
||||
end
|
||||
|
||||
event :pause do
|
||||
transitions from: :live, to: :paused
|
||||
transitions from: %i[live reconnecting], to: :paused
|
||||
end
|
||||
|
||||
event :resume do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
32
backend/app/services/sessions/resume.rb
Normal file
32
backend/app/services/sessions/resume.rb
Normal 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
|
||||
@@ -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?
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</section>
|
||||
|
||||
<% unless @stream_closed %>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause">Interrompi (riprendibile)</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause">Metti in pausa</button>
|
||||
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop">Chiudi diretta</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user