Stabilizza live YouTube/RTMP e fix crash rotazione mobile.

Pipeline YouTube con relay, overlay e publisher sync lato backend; fix GL pauseGlDuringRotation su Android per evitare crash in rotazione durante lo streaming Flutter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 15:30:55 +02:00
parent 1058c644bd
commit 854738b46d
65 changed files with 2606 additions and 579 deletions

View File

@@ -31,28 +31,32 @@ module Mediamtx
path = session.mediamtx_path_name
# record: false finché non c'è publisher — con alwaysAvailable MediaMTX registrerebbe
# solo la slate (schermo nero) in pausa/attesa.
# YouTube: niente slate sul path camera (maschera il video al relay ffmpeg).
body = recording_body(session, enabled: false).merge(
source: "publisher",
overridePublisher: true,
alwaysAvailable: true,
alwaysAvailableFile: slate_file_path
overridePublisher: true
)
# YouTube: relay continuo gestito da Streams::YoutubeRelay (ffmpeg in sidekiq/rails).
body[:alwaysAvailable] = true
body[:alwaysAvailableFile] = slate_file_path
# YouTube: telefono → MediaMTX; relay copy verso RTMPS in sidekiq.
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 path create failed: #{response.status} #{err}"
end
remember_always_available(path, enabled: true)
true
end
def delete_path(session)
delete_path_name(session.mediamtx_path_name)
delete_path_name(session.mediamtx_overlay_path_name)
forget_always_available(session.mediamtx_path_name)
end
def delete_path_name(path_name)
@conn.post("/v3/config/paths/delete/#{CGI.escape(path_name)}")
forget_always_available(path_name)
end
def set_path_recording(session, enabled:)
@@ -67,16 +71,26 @@ module Mediamtx
end
def patch_path(session)
set_always_available(session, enabled: true)
end
# Slate alwaysAvailable: copertina sullo stesso path quando il telefono è offline.
# Disattivare quando il publisher è in onda; riattivare in pausa/disconnessione.
def set_always_available(session, enabled:)
path = session.mediamtx_path_name
body = {
alwaysAvailable: true,
alwaysAvailableFile: slate_file_path
}
return true if always_available_remembered?(path, enabled: enabled)
body = if enabled
{ alwaysAvailable: true, alwaysAvailableFile: slate_file_path }
else
{ alwaysAvailable: false }
end
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}"
raise Error, "MediaMTX alwaysAvailable patch failed: #{response.status} #{err}"
end
remember_always_available(path, enabled: enabled)
true
end
@@ -152,5 +166,35 @@ module Mediamtx
def slate_file_path
ENV.fetch("MEDIAMTX_SLATE_FILE", "/slates/offline.mp4")
end
def always_available_remembered?(path, enabled:)
redis.get(always_available_key(path)) == always_available_value(enabled)
rescue Redis::BaseError
false
end
def remember_always_available(path, enabled:)
redis.set(always_available_key(path), always_available_value(enabled), ex: 48.hours.to_i)
rescue Redis::BaseError
nil
end
def forget_always_available(path)
redis.del(always_available_key(path))
rescue Redis::BaseError
nil
end
def always_available_key(path)
"mediamtx:always_available:#{path}"
end
def always_available_value(enabled)
enabled ? "1" : "0"
end
def redis
@redis ||= Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
end
end
end