Mantiene la copertina YouTube se il telefono cade e avvia ffmpeg sul nodo, non via SSH.

Il relay HLS→RTMPS resta sul worker/agent del nodo assegnato, così tre dirette contemporanee restano in onda sul sito e sul canale della società senza schermo nero.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-16 20:41:46 +02:00
co-authored by Cursor
parent bfb3115a64
commit baa6283a15
36 changed files with 1444 additions and 141 deletions
@@ -4,11 +4,20 @@ module MediamtxPlayback
private
def mediamtx_paths_index
@mediamtx_paths_index ||= Mediamtx::Client.new.list_paths.index_by { |i| i["name"] }
mediamtx_paths_index_for_url(MatchLiveTv.mediamtx_api_url)
end
def mediamtx_paths_index_for(session)
mediamtx_paths_index_for_url(session.mediamtx_api_base_url)
end
def mediamtx_paths_index_for_url(api_url)
@mediamtx_paths_by_origin ||= {}
@mediamtx_paths_by_origin[api_url] ||= Mediamtx::Client.new(base_url: api_url).list_paths.index_by { |i| i["name"] }
end
def mediamtx_path_info(session, path_name: mediamtx_playback_path_name(session))
mediamtx_paths_index[path_name]
mediamtx_paths_index_for(session)[path_name]
end
def mediamtx_playback_path_name(session)
@@ -28,7 +37,7 @@ module MediamtxPlayback
end
def mediamtx_publisher_online?(session)
info = mediamtx_paths_index[session.mediamtx_path_name]
info = mediamtx_paths_index_for(session)[session.mediamtx_path_name]
info && info["online"]
end
end
@@ -20,7 +20,8 @@ class HlsProxyController < ActionController::Base
def proxy_mediamtx_path(upstream_path)
return head :not_found if upstream_path.blank?
upstream = "#{MatchLiveTv.mediamtx_hls_url}/#{upstream_path}"
origin = hls_origin_for(upstream_path)
upstream = "#{origin}/#{upstream_path}"
upstream = "#{upstream}?#{request.query_string}" if request.query_string.present?
cookie = request.headers["Cookie"].presence || "cookieCheck=1"
@@ -30,7 +31,7 @@ class HlsProxyController < ActionController::Base
if response.status.in?([301, 302, 307, 308])
location = response.headers["location"].to_s
cookie = cookie_from_set_header(response.headers["set-cookie"]).presence || cookie
upstream = resolve_upstream_url(location)
upstream = resolve_upstream_url(location, origin)
next
end
@@ -43,10 +44,21 @@ class HlsProxyController < ActionController::Base
head :bad_gateway
end
def resolve_upstream_url(location)
def hls_origin_for(upstream_path)
session_id = upstream_path[/\Alive\/match_([0-9a-f-]{36})/i, 1]
if session_id
session = StreamSession.find_by(id: session_id)
node_hls = session&.stream_node&.internal_hls_url.presence
return node_hls.sub(%r{/$}, "") if node_hls.present?
end
MatchLiveTv.mediamtx_hls_url.sub(%r{/$}, "")
end
def resolve_upstream_url(location, origin)
return location if location.start_with?("http://", "https://")
base = MatchLiveTv.mediamtx_hls_url.sub(%r{/$}, "")
base = origin.to_s.sub(%r{/$}, "")
location.start_with?("/") ? "#{base}#{location}" : "#{base}/#{location}"
end
+1 -1
View File
@@ -1,4 +1,4 @@
# Avvia/riavvia il relay YouTube solo sui worker con YOUTUBE_RELAY_WORKER=1 (coda youtube_relay).
# Avvia/riavvia il relay YouTube sul worker del nodo (coda youtube_relay_<slug>).
class YoutubeRelayEnsureJob < ApplicationJob
queue_as Streams::YoutubeRelay::QUEUE
+2 -1
View File
@@ -17,6 +17,7 @@ class YoutubeRelayStopJob < ApplicationJob
return unless result == :wrong_host
return if attempts >= 30
self.class.set(wait: 2.seconds).perform_later(session_id, attempts + 1)
self.class.set(wait: 2.seconds, queue: Streams::YoutubeRelay.queue_for(session))
.perform_later(session_id, attempts + 1)
end
end
+16 -2
View File
@@ -4,19 +4,33 @@ class YoutubeCredential < ApplicationRecord
attr_encrypted :access_token,
key: :encryption_key,
attribute: "access_token_encrypted",
mode: :single_iv_salt
mode: :single_iv_and_salt,
algorithm: "aes-256-cbc",
iv: :encryption_iv
attr_encrypted :refresh_token,
key: :encryption_key,
attribute: "refresh_token_encrypted",
mode: :single_iv_salt
mode: :single_iv_and_salt,
algorithm: "aes-256-cbc",
iv: :encryption_iv
def expired?
expires_at.present? && expires_at < Time.current
end
def usable?
refresh_token.present?
rescue ArgumentError, OpenSSL::Cipher::CipherError, NoMethodError
false
end
private
def encryption_key
Rails.application.secret_key_base[0, 32]
end
def encryption_iv
encryption_key[0, 16]
end
end
+2 -3
View File
@@ -22,8 +22,7 @@ module Mediamtx
def create_path(session)
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).
# solo la slate in pausa/attesa. Slate resta accesa anche su YouTube (copertina se l'app cade).
body = recording_body(session, enabled: false).merge(
source: "publisher",
overridePublisher: true
@@ -76,7 +75,7 @@ module Mediamtx
end
# Slate alwaysAvailable: copertina sullo stesso path quando il telefono è offline.
# Disattivare quando il publisher è in onda; riattivare in pausa/disconnessione.
# Resta accesa anche con publisher in onda (MediaMTX usa il publisher se presente).
def set_always_available(session, enabled:)
path = session.mediamtx_path_name
return true if always_available_remembered?(path, enabled: enabled)
@@ -19,7 +19,6 @@ module Mediamtx
if @session.paused?
# RTMP ancora connesso in pausa: non forzare live/reconnect.
else
enable_live_path_once!(@session)
if @session.may_go_live?
@session.go_live!
@session.reload
@@ -75,23 +74,10 @@ module Mediamtx
@session.update!(timeout_job_id: job)
end
# Compatibilità con webhook / controller.
def self.schedule_youtube_pipeline!(session, force: false)
Youtube::LivePipeline.schedule!(session, force: force)
end
def enable_live_path_once!(session)
return unless session.platform == "youtube"
key = format("youtube:slate_disabled:%s", session.id)
return unless redis.set(key, "1", nx: true, ex: 48.hours.to_i)
Client.for_session(session).set_always_available(session, enabled: false)
rescue Client::Error => e
redis.del(format("youtube:slate_disabled:%s", session.id))
Rails.logger.warn("[PublisherSync] disable slate session=#{session.id}: #{e.message}")
end
def restore_slate_path!(session)
return if session.platform == "matchlivetv"
@@ -183,7 +169,7 @@ module Mediamtx
key = format("youtube_relay:sched:%s", session.id)
return unless redis.set(key, "1", nx: true, ex: 10)
YoutubeRelayEnsureJob.set(queue: Streams::YoutubeRelay::QUEUE).perform_later(session.id)
Streams::YoutubeRelay.enqueue_ensure!(session)
end
def redis
@@ -77,9 +77,8 @@ module Streams
dns.upsert_a(hostname, ip)
simulated = instance.raw.is_a?(Hash) && (instance.raw[:simulated] || instance.raw["simulated"])
api_base = simulated ? home.api_base_url : "http://#{private_ip}:9997"
internal_rtmp = simulated ? home.internal_rtmp_url : "rtmp://#{private_ip}:1935"
internal_hls = simulated ? home.internal_hls_url : "http://#{private_ip}:8888"
urls = urls_for_node(role: role, home: home, hostname: hostname, simulated: simulated,
private_ip: private_ip, public_ip: ip, use_node_hostname: use_node_hostname)
StreamNode.create!(
slug: slug,
@@ -88,11 +87,11 @@ module Streams
status: "ready",
provider: provider_name_for(cloud, role: role),
provider_instance_id: instance.id,
rtmp_base_url: use_node_hostname ? "rtmp://#{hostname}:1935" : home.rtmp_base_url,
hls_base_url: use_node_hostname ? "https://#{hostname}/hls" : home.hls_base_url,
api_base_url: api_base,
internal_rtmp_url: internal_rtmp,
internal_hls_url: internal_hls,
rtmp_base_url: urls.fetch(:rtmp_base_url),
hls_base_url: urls.fetch(:hls_base_url),
api_base_url: urls.fetch(:api_base_url),
internal_rtmp_url: urls.fetch(:internal_rtmp_url),
internal_hls_url: urls.fetch(:internal_hls_url),
max_publishers: max,
max_relays: max,
last_health_at: Time.current,
@@ -105,6 +104,39 @@ module Streams
)
end
def urls_for_node(role:, home:, hostname:, simulated:, private_ip:, public_ip: nil, use_node_hostname:)
if role == "lab" && ENV["MEDIAMTX_LAB_API_URL"].present?
return {
api_base_url: ENV.fetch("MEDIAMTX_LAB_API_URL"),
internal_rtmp_url: ENV.fetch("MEDIAMTX_LAB_INTERNAL_RTMP_URL", "rtmp://mediamtx_lab:1935"),
internal_hls_url: ENV.fetch("MEDIAMTX_LAB_HLS_URL", "http://mediamtx_lab:8888"),
rtmp_base_url: ENV.fetch("MEDIAMTX_LAB_RTMP_URL", "rtmp://127.0.0.1:11935"),
# HLS pubblico resta sul proxy del sito: il controller instrada al MediaMTX del nodo.
hls_base_url: home.hls_base_url
}
end
control_ip = control_ip_for(role: role, private_ip: private_ip, public_ip: public_ip)
{
api_base_url: simulated ? home.api_base_url : "http://#{control_ip}:9997",
internal_rtmp_url: simulated ? home.internal_rtmp_url : "rtmp://#{control_ip}:1935",
internal_hls_url: simulated ? home.internal_hls_url : "http://#{control_ip}:8888",
rtmp_base_url: use_node_hostname ? "rtmp://#{hostname}:1935" : home.rtmp_base_url,
# Player del sito: proxy Rails/edge. HTTPS diretto sul nodo arriva dopo TLS/Caddy.
hls_base_url: home.hls_base_url
}
end
# Senza WireGuard/network privata Rails deve parlare con l'IPv4 pubblico (API+HLS).
def control_ip_for(role:, private_ip:, public_ip:)
public_ip = public_ip.presence
return private_ip if role != "cloud" || public_ip.blank?
return public_ip if ENV["STREAM_CLOUD_PUBLIC_CONTROL"] == "1"
return public_ip if private_ip.blank? || private_ip == public_ip
private_ip
end
def next_slug(prefix)
used = StreamNode.where("slug LIKE ?", "#{prefix}-%").pluck(:slug)
n = 1
+159 -25
View File
@@ -1,6 +1,10 @@
require "json"
require "net/http"
require "uri"
module Streams
# Relay verso YouTube: legge RTMP/HLS da MediaMTX e inoltra su RTMPS (-c copy). Nessun overlay.
# ffmpeg gira solo sui worker Sidekiq con YOUTUBE_RELAY_WORKER=1 (coda youtube_relay).
# Relay verso YouTube: legge HLS da MediaMTX e inoltra su RTMPS (-c copy).
# Sul nodo cloud ffmpeg è avviato dall'agent locale (immagine CPX) all'avvio diretta.
class YoutubeRelay
class Error < StandardError; end
@@ -8,6 +12,7 @@ module Streams
OWNER_KEY = "youtube_relay:owner:%s"
OWNED_SET = "youtube_relay:owned:%s"
QUEUE = :youtube_relay
QUEUE_PREFIX = "youtube_relay"
class << self
def worker?
@@ -18,18 +23,44 @@ module Streams
ENV.fetch("RELAY_MAX_CONCURRENT", "4").to_i
end
def local_node_slug
ENV["STREAM_NODE_SLUG"].presence || NodeRegistry::HOME_SLUG
end
def assigned_node_slug(session)
session.stream_node&.slug.presence || NodeRegistry::HOME_SLUG
end
def node_matches?(session)
assigned_node_slug(session) == local_node_slug
end
def queue_for(session)
"#{QUEUE_PREFIX}_#{assigned_node_slug(session)}"
end
def enqueue_ensure!(session, wait: nil)
opts = { queue: queue_for(session) }
opts[:wait] = wait if wait
YoutubeRelayEnsureJob.set(**opts).perform_later(session.id)
end
def enqueue_stop!(session)
YoutubeRelayStopJob.set(queue: queue_for(session)).perform_later(session.id)
end
def start(session)
return unless worker?
return enqueue_ensure!(session) unless worker? && node_matches?(session)
start_on_worker!(session)
end
# Non cancella owner/pid qui: solo il worker owner deve killare ffmpeg.
def stop(session)
if worker? && owner_is_local?(session.id)
if worker? && owner_is_local?(session.id) && node_matches?(session)
stop_on_worker!(session)
else
YoutubeRelayStopJob.set(queue: QUEUE).perform_later(session.id)
enqueue_stop!(session)
end
true
end
@@ -41,7 +72,7 @@ module Streams
return false if pid.blank? && owner.present? && owner != worker_id && redis.ttl(format(OWNER_KEY, session_id)) <= 30
return false if pid.blank?
return process_alive?(pid) if owner.blank? || owner == worker_id
return process_alive?(pid, session_id: session_id) if owner.blank? || owner == worker_id
# Relay su altro host: attivo se lock owner ancora fresco.
redis.ttl(format(OWNER_KEY, session_id)) > 30
@@ -52,15 +83,22 @@ module Streams
return if session.terminal?
return if session.stream_key.blank?
if worker?
if worker? && node_matches?(session)
ensure_on_worker!(session)
else
YoutubeRelayEnsureJob.set(queue: QUEUE).perform_later(session.id)
enqueue_ensure!(session)
end
end
def ensure_on_worker!(session)
return :not_worker unless worker?
unless node_matches?(session)
enqueue_ensure!(session, wait: 2.seconds)
Rails.logger.info(
"[YoutubeRelay] wrong_node local=#{local_node_slug} assigned=#{assigned_node_slug(session)} session=#{session.id}"
)
return :wrong_host
end
return unless session.platform == "youtube"
return if session.terminal?
return if session.stream_key.blank?
@@ -68,7 +106,7 @@ module Streams
return unless intake_available?(session)
pid = pid_for(session.id)
if pid.present? && !process_alive?(pid.to_i)
if pid.present? && !process_alive?(pid.to_i, session_id: session.id)
clear_local_ownership(session.id)
end
@@ -78,8 +116,8 @@ module Streams
end
if at_capacity?
YoutubeRelayEnsureJob.set(wait: 5.seconds, queue: QUEUE).perform_later(session.id)
Rails.logger.info("[YoutubeRelay] at capacity worker=#{worker_id} session=#{session.id} requeue")
enqueue_ensure!(session, wait: 5.seconds)
Rails.logger.info("[YoutubeRelay] at capacity worker=#{worker_id} node=#{local_node_slug} session=#{session.id} requeue")
return :at_capacity
end
@@ -109,7 +147,7 @@ module Streams
return :noop
end
terminate_pid(pid)
terminate_pid(pid, session_id: session.id)
clear_local_ownership(session.id)
Rails.logger.info("[YoutubeRelay] stopped pid=#{pid} session=#{session.id} worker=#{worker_id}")
:stopped
@@ -140,26 +178,88 @@ module Streams
stop_on_worker!(session) if pid_for(session.id).present? && owner_is_local?(session.id)
# Due EnsureJob concorrenti (concurrency Sidekiq > 1) non devono spawnare due ffmpeg.
unless redis.set(format("youtube_relay:startlock:%s", session.id), worker_id, nx: true, ex: 20)
return pid_for(session.id).to_i
end
log_path = log_file(session)
FileUtils.mkdir_p(File.dirname(log_path))
intake_source = mediamtx_intake_source(session)
output = "rtmps://a.rtmps.youtube.com/live2/#{session.stream_key}"
pid = Process.spawn(
*youtube_ffmpeg_args(intake_source, output),
%i[out err] => log_path,
pgroup: true
)
Process.detach(pid)
pid = if relay_agent_enabled?
start_via_agent!(session)
else
spawn_ffmpeg!(youtube_ffmpeg_args(intake_source, output), log_path)
end
store_pid(session.id, pid)
claim_ownership!(session.id)
Rails.logger.info("[YoutubeRelay] started pid=#{pid} session=#{session.id} intake=#{intake_source.join(":")} worker=#{worker_id}")
Rails.logger.info(
"[YoutubeRelay] started pid=#{pid} session=#{session.id} intake=#{intake_source.join(':')} " \
"worker=#{worker_id} agent=#{relay_agent_url || '-'}"
)
schedule_youtube_activate(session)
pid
rescue Errno::ENOENT => e
raise Error, "ffmpeg non disponibile: #{e.message}"
end
def relay_agent_url
ENV["STREAM_NODE_RELAY_AGENT_URL"].presence
end
def relay_agent_enabled?
relay_agent_url.present?
end
def relay_agent_secret
ENV["STREAM_NODE_AGENT_SECRET"].presence || ENV["MEDIAMTX_WEBHOOK_SECRET"].presence || ""
end
def spawn_ffmpeg!(args, log_path)
pid = Process.spawn(*args, %i[out err] => log_path, pgroup: true)
Process.detach(pid)
pid
end
def start_via_agent!(session)
payload = {
"session_id" => session.id,
"path" => session.mediamtx_path_name,
"rtmps" => "rtmps://a.rtmps.youtube.com/live2/#{session.stream_key}"
}
res = agent_request(Net::HTTP::Post, "/relays", payload)
pid = res["pid"].to_i
raise Error, "relay agent spawn failed: #{res.inspect}" if pid <= 0
File.write(
log_file(session),
"agent=#{relay_agent_url} pid=#{pid} path=#{session.mediamtx_path_name}\n"
)
pid
end
def agent_request(http_class, path, payload = nil)
uri = URI.parse("#{relay_agent_url.chomp('/')}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 5
http.read_timeout = 10
req = http_class.new(uri)
req["Authorization"] = "Bearer #{relay_agent_secret}" if relay_agent_secret.present?
req["Content-Type"] = "application/json"
req.body = JSON.generate(payload) if payload
res = http.request(req)
body = res.body.present? ? JSON.parse(res.body) : {}
unless res.is_a?(Net::HTTPSuccess)
raise Error, "relay agent #{path} HTTP #{res.code} #{body.inspect}"
end
body
rescue JSON::ParserError => e
raise Error, "relay agent JSON: #{e.message}"
end
# Remux verso YouTube: video+audio copy (AAC già da app/slate a 48k mono).
# HLS (ADTS) → FLV richiede -bsf:a aac_adtstoasc; RTMP ha già ASC in FLV tags.
def youtube_ffmpeg_args(intake_source, output)
@@ -179,6 +279,9 @@ module Streams
end
common_head + [
"-reconnect", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "2",
"-rw_timeout", "15000000",
"-live_start_index", "-1",
"-i", url,
@@ -190,14 +293,29 @@ module Streams
end
def mediamtx_intake_source(session)
base = session.mediamtx_internal_rtmp_url
rtmp_base, hls_base = intake_bases(session)
if Mediamtx::PublisherOnline.active?(session)
return [:rtmp, "#{base.chomp('/')}/#{session.mediamtx_path_name}"]
return [:rtmp, "#{rtmp_base.chomp('/')}/#{session.mediamtx_path_name}"]
end
path = session.mediamtx_path_name
hls = session.mediamtx_internal_hls_url.chomp("/")
[:hls, "#{hls}/#{path}/index.m3u8"]
[:hls, "#{hls_base.chomp('/')}/#{path}/index.m3u8"]
end
# Sul nodo assegnato ffmpeg legge MediaMTX in loopback (o hostname Docker locale).
def intake_bases(session)
if relay_agent_enabled? && node_matches?(session)
return ["rtmp://127.0.0.1:1935", "http://127.0.0.1:8888"]
end
if node_matches?(session)
[
ENV["STREAM_NODE_LOCAL_RTMP_URL"].presence || session.mediamtx_internal_rtmp_url,
ENV["STREAM_NODE_LOCAL_HLS_URL"].presence || session.mediamtx_internal_hls_url
]
else
[session.mediamtx_internal_rtmp_url, session.mediamtx_internal_hls_url]
end
end
def intake_available?(session)
@@ -257,7 +375,9 @@ module Streams
redis.get(format(REDIS_KEY, session_id))
end
def process_alive?(pid)
def process_alive?(pid, session_id: nil)
return agent_session_running?(session_id) if relay_agent_enabled? && session_id.present?
stat = File.read("/proc/#{pid.to_i}/stat")
return false if stat.split[2] == "Z"
@@ -267,12 +387,26 @@ module Streams
false
end
def terminate_pid(pid)
def agent_session_running?(session_id)
res = agent_request(Net::HTTP::Get, "/relays/#{session_id}")
res["running"] == true
rescue Error
false
end
def terminate_pid(pid, session_id: nil)
if relay_agent_enabled? && session_id.present?
agent_request(Net::HTTP::Delete, "/relays/#{session_id}")
return
end
Process.kill("TERM", -pid.to_i)
sleep 0.5
rescue Errno::ESRCH
nil
else
return if relay_agent_enabled?
begin
Process.kill("KILL", -pid.to_i)
rescue Errno::ESRCH
@@ -32,7 +32,8 @@ module Youtube
end
def team_channel_available?
@ent.youtube_enabled? && @ent.premium_full? && @mode == "team" && @team.club.youtube_credential.present?
@ent.youtube_enabled? && @ent.premium_full? && @mode == "team" &&
@team.club.youtube_credential&.usable?
end
def effective_channel
@@ -63,7 +63,6 @@ module Youtube
return
end
Mediamtx::Client.for_session(session).set_always_available(session, enabled: false)
session.go_live! if session.may_go_live?
session.reconnect! if session.reconnecting? && session.may_reconnect?
+3 -3
View File
@@ -15,7 +15,7 @@ module Youtube
when "matchlivetv_light"
PlatformCredential.configured?
when "team"
@team.club.youtube_credential.present? || PlatformCredential.configured?
@team.club.youtube_credential&.usable? || PlatformCredential.configured?
else
false
end
@@ -31,7 +31,7 @@ module Youtube
if @mode == "matchlivetv_light"
PlatformCredential.configured?
elsif @mode == "team"
@team.club.youtube_credential.blank? && PlatformCredential.configured?
!@team.club.youtube_credential&.usable? && PlatformCredential.configured?
else
false
end
@@ -46,7 +46,7 @@ module Youtube
end
def needs_team_oauth?
@mode == "team" && @ent.premium_full? && @team.club.youtube_credential.blank? && !PlatformCredential.configured?
@mode == "team" && @ent.premium_full? && !@team.club.youtube_credential&.usable? && !PlatformCredential.configured?
end
end
end
+10 -6
View File
@@ -54,6 +54,7 @@
</div>
<p id="offline-msg" class="live-status-msg" hidden><%= t("live.show.offline_msg") %></p>
<p id="awaiting-msg" class="live-status-msg" hidden><%= t("live.show.awaiting_msg") %></p>
<p id="reconnecting-msg" class="live-status-msg" hidden><%= t("live.show.js_reconnecting_awaiting") %></p>
<p id="paused-msg" class="live-status-msg" hidden><%= t("live.show.paused_msg") %></p>
<script>
@@ -63,25 +64,26 @@
const offlineMsg = document.getElementById("offline-msg");
const pausedMsg = document.getElementById("paused-msg");
const awaitingMsg = document.getElementById("awaiting-msg");
const reconnectingMsg = document.getElementById("reconnecting-msg");
const playHint = document.getElementById("play-hint");
const streamBadge = document.getElementById("live-stream-badge");
function syncStreamBadge(data) {
if (!streamBadge) return;
if (data.stream_closed) {
streamBadge.textContent = "<%= j t("live.overlays.badge_ended") %>";
streamBadge.textContent = <%= raw t("live.overlays.badge_ended").to_json %>;
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-ended";
return;
}
const paused = !!(data.paused || data.status === "paused");
if (paused) {
streamBadge.textContent = "<%= j t("live.index.badge_paused") %>";
streamBadge.textContent = <%= raw t("live.index.badge_paused").to_json %>;
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
} else if (data.on_air) {
streamBadge.textContent = "<%= j t("live.index.badge_live") %>";
streamBadge.textContent = <%= raw t("live.index.badge_live").to_json %>;
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-live";
} else {
streamBadge.textContent = "<%= j t("live.overlays.badge_waiting") %>";
streamBadge.textContent = <%= raw t("live.overlays.badge_waiting").to_json %>;
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
}
}
@@ -282,9 +284,10 @@
if (onAir || sessionLive) {
offlineMsg.hidden = true;
if (!publisherOnline && showingCover && !paused) {
awaitingMsg.hidden = false;
awaitingMsg.textContent = "<%= j t("live.show.js_reconnecting_awaiting") %>";
awaitingMsg.hidden = true;
if (reconnectingMsg) reconnectingMsg.hidden = false;
} else {
if (reconnectingMsg) reconnectingMsg.hidden = true;
awaitingMsg.hidden = !awaitingSignal;
}
pausedMsg.hidden = !paused;
@@ -299,6 +302,7 @@
} else {
offlineMsg.hidden = false;
awaitingMsg.hidden = true;
if (reconnectingMsg) reconnectingMsg.hidden = true;
pausedMsg.hidden = true;
}