Merge branch 'feature/adaptive-bitrate' into collaudo
Integra bitrate adattivo e telemetria, con il mute audio come icona nella toolbar della regia. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,8 @@ class SessionChannel < ApplicationCable::Channel
|
||||
Sessions::SetAudioMute.new(session, muted: true).call
|
||||
when "unmute_audio"
|
||||
Sessions::SetAudioMute.new(session, muted: false).call
|
||||
when "set_min_quality"
|
||||
Sessions::SetMinQuality.new(session, preset: data["min_quality_preset"]).call
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ module Api
|
||||
current_bitrate: params[:current_bitrate],
|
||||
target_bitrate: params[:target_bitrate],
|
||||
fps: params[:fps],
|
||||
thermal_state: sanitized_thermal_state,
|
||||
last_seen_at: Time.current
|
||||
)
|
||||
sync_publisher_when_streaming!(params[:fps].to_f)
|
||||
@@ -147,6 +148,13 @@ module Api
|
||||
}
|
||||
end
|
||||
|
||||
def min_quality
|
||||
Sessions::SetMinQuality.new(@session, preset: params[:min_quality_preset]).call
|
||||
render json: session_json(@session)
|
||||
rescue ArgumentError => e
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def youtube_stats
|
||||
count = if @session.platform == "youtube" && @session.youtube_broadcast_id.present?
|
||||
Youtube::BroadcastService.new(@session.match.team)
|
||||
@@ -166,6 +174,11 @@ module Api
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
def sanitized_thermal_state
|
||||
value = params[:thermal_state].to_s.downcase.presence
|
||||
value if value.in?(%w[nominal fair serious critical])
|
||||
end
|
||||
|
||||
def session_params
|
||||
params.permit(:platform, :privacy_status, :quality_preset, :target_bitrate, :target_fps, :youtube_channel)
|
||||
end
|
||||
@@ -194,6 +207,7 @@ module Api
|
||||
youtube_ready: session.youtube_ready?,
|
||||
privacy_status: session.privacy_status,
|
||||
quality_preset: session.quality_preset,
|
||||
min_quality_preset: session.min_quality_preset,
|
||||
target_bitrate: session.target_bitrate,
|
||||
started_at: session.started_at,
|
||||
disconnection_count: session.disconnection_count,
|
||||
|
||||
@@ -7,7 +7,7 @@ module Public
|
||||
layout "regia"
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
skip_before_action :verify_authenticity_token, only: %i[score pause resume stop audio_mute]
|
||||
skip_before_action :verify_authenticity_token, only: %i[score pause resume stop audio_mute min_quality]
|
||||
|
||||
before_action :set_session_from_token
|
||||
|
||||
@@ -67,8 +67,20 @@ module Public
|
||||
render json: status_payload
|
||||
end
|
||||
|
||||
def min_quality
|
||||
Sessions::SetMinQuality.new(@session, preset: params[:min_quality_preset]).call
|
||||
render json: status_payload
|
||||
rescue ArgumentError => e
|
||||
render json: status_payload.merge(error: e.message), status: :unprocessable_entity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def camera_telemetry_payload(publisher_online)
|
||||
cam = @session.device_states.find_by(device_role: "camera")
|
||||
(cam&.as_regia_payload || {}).merge(publisher_online: publisher_online)
|
||||
end
|
||||
|
||||
def set_session_from_token
|
||||
@session = Sessions::RegiaAccess.find_session_by_token(params[:token])
|
||||
return if @session
|
||||
@@ -93,7 +105,10 @@ module Public
|
||||
home_name: @session.match.team.name,
|
||||
away_name: @session.match.opponent_name,
|
||||
points_target: Scoring::Rules.points_target_for_match(@session.match, current_set: score&.current_set || 1),
|
||||
score: score&.as_cable_payload
|
||||
score: score&.as_cable_payload,
|
||||
min_quality_preset: @session.min_quality_preset,
|
||||
min_quality_options: Sessions::SelectQuality.allowed_min_ids(@session.quality_preset),
|
||||
telemetry: camera_telemetry_payload(publisher_online)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,8 +57,14 @@ module Public
|
||||
copyPrompt: t("regia.js.copy_prompt"),
|
||||
shareRegiaText: t("regia.js.share_regia_text"),
|
||||
shareLiveText: t("regia.js.share_live_text"),
|
||||
minQualityUpdated: t("regia.js.min_quality_updated"),
|
||||
minQualityError: t("regia.js.min_quality_error"),
|
||||
minQualityAuto: t("regia.min_quality.auto"),
|
||||
previewWaiting: t("regia.preview_waiting"),
|
||||
streamEnded: t("regia.preview_ended"),
|
||||
previewHide: t("regia.preview_hide"),
|
||||
previewShow: t("regia.preview_show"),
|
||||
previewOffHint: t("regia.preview_off_hint"),
|
||||
resumeLabel: t("regia.resume"),
|
||||
pauseLabel: t("regia.pause"),
|
||||
muteLabel: t("regia.mute"),
|
||||
@@ -67,7 +73,20 @@ module Public
|
||||
pausedBadge: t("regia.status.paused"),
|
||||
liveBadge: t("regia.status.live"),
|
||||
waitingBadge: t("regia.status.waiting"),
|
||||
subtitlePrefix: t("regia.subtitle_prefix")
|
||||
subtitlePrefix: t("regia.subtitle_prefix"),
|
||||
telemetryWaiting: t("regia.telemetry.waiting"),
|
||||
telemetryStale: t("regia.telemetry.stale"),
|
||||
telemetryJustNow: t("regia.telemetry.just_now"),
|
||||
telemetrySecondsAgo: t("regia.telemetry.seconds_ago"),
|
||||
telemetryPublisherOn: t("regia.telemetry.publisher_on"),
|
||||
telemetryPublisherOff: t("regia.telemetry.publisher_off"),
|
||||
telemetryThermalNominal: t("regia.telemetry.thermal_nominal"),
|
||||
telemetryThermalFair: t("regia.telemetry.thermal_fair"),
|
||||
telemetryThermalSerious: t("regia.telemetry.thermal_serious"),
|
||||
telemetryThermalCritical: t("regia.telemetry.thermal_critical"),
|
||||
telemetryBitrateMax: t("regia.telemetry.bitrate_max"),
|
||||
telemetryBitrateTitle: t("regia.telemetry.bitrate_title"),
|
||||
telemetryBitrateTap: t("regia.telemetry.bitrate_tap")
|
||||
}.to_json
|
||||
end
|
||||
|
||||
|
||||
@@ -16,4 +16,17 @@ class DeviceState < ApplicationRecord
|
||||
status: stream_session.status
|
||||
}
|
||||
end
|
||||
|
||||
def as_regia_payload
|
||||
{
|
||||
battery: battery_level,
|
||||
network: network_type,
|
||||
signal: signal_strength,
|
||||
bitrate: current_bitrate,
|
||||
target_bitrate: target_bitrate,
|
||||
fps: fps,
|
||||
thermal_state: thermal_state,
|
||||
last_seen_at: last_seen_at&.iso8601
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,7 @@ class StreamSession < ApplicationRecord
|
||||
validates :platform, inclusion: { in: PLATFORMS }
|
||||
validates :status, inclusion: { in: STATUSES }
|
||||
validates :privacy_status, inclusion: { in: PRIVACY_STATUSES }
|
||||
validates :min_quality_preset, inclusion: { in: Sessions::SelectQuality::MIN_QUALITY_IDS }
|
||||
|
||||
before_validation :normalize_privacy_status
|
||||
before_validation :ensure_publish_token, on: :create
|
||||
|
||||
@@ -9,11 +9,12 @@ module Sessions
|
||||
@session.pause! if @session.may_pause?
|
||||
Mediamtx::PublisherSync.forget_recording_state!(@session.id)
|
||||
begin
|
||||
Mediamtx::Client.for_session(@session).set_path_recording(@session, enabled: false)
|
||||
mtx = Mediamtx::Client.for_session(@session)
|
||||
mtx.set_path_recording(@session, enabled: false)
|
||||
mtx.set_always_available(@session, enabled: true)
|
||||
rescue Mediamtx::Client::Error => e
|
||||
Rails.logger.warn("[Sessions::Pause] disable recording: #{e.message}")
|
||||
Rails.logger.warn("[Sessions::Pause] MediaMTX: #{e.message}")
|
||||
end
|
||||
Mediamtx::Client.for_session(@session).set_always_available(@session, enabled: true)
|
||||
# Slate su path per HLS in pausa; RTMP telefono si ferma via comando app.
|
||||
log_event("paused")
|
||||
SessionChannel.broadcast_message(@session, { type: "command", action: "pause_stream" })
|
||||
|
||||
@@ -3,6 +3,8 @@ module Sessions
|
||||
class SelectQuality
|
||||
UPLOAD_HEADROOM = 0.8
|
||||
UPLOAD_HEADROOM_1080P = 1.0
|
||||
AUTO_ID = "auto"
|
||||
AUTO_FLOOR_BITRATE = 800_000
|
||||
|
||||
PRESETS = [
|
||||
{ id: "1080p_30_4.5mbps", target_bitrate: 4_500_000, target_fps: 30 },
|
||||
@@ -11,6 +13,32 @@ module Sessions
|
||||
{ id: "720p_30_1.5mbps", target_bitrate: 1_500_000, target_fps: 30 }
|
||||
].freeze
|
||||
|
||||
MIN_QUALITY_IDS = ([AUTO_ID] + PRESETS.map { |preset| preset[:id] }).freeze
|
||||
|
||||
def self.floor_bitrate(preset_id)
|
||||
return AUTO_FLOOR_BITRATE if preset_id.blank? || preset_id == AUTO_ID
|
||||
|
||||
PRESETS.find { |preset| preset[:id] == preset_id }&.fetch(:target_bitrate) || AUTO_FLOOR_BITRATE
|
||||
end
|
||||
|
||||
def self.allowed_min_ids(session_preset)
|
||||
ids = [AUTO_ID] + PRESETS.reject { |preset| preset[:id].start_with?("1080p") }.reverse.map { |preset| preset[:id] }
|
||||
ids << "1080p_30_4.5mbps" if session_preset.to_s.start_with?("1080p")
|
||||
ids
|
||||
end
|
||||
|
||||
def self.display_name(preset_id)
|
||||
return AUTO_ID if preset_id.blank? || preset_id == AUTO_ID
|
||||
|
||||
preset = PRESETS.find { |item| item[:id] == preset_id }
|
||||
return preset_id unless preset
|
||||
|
||||
resolution = preset[:id].start_with?("1080p") ? "1080p" : "720p"
|
||||
mbps = preset[:target_bitrate] / 1_000_000.0
|
||||
mbps_text = (mbps == mbps.to_i) ? mbps.to_i.to_s : format("%.1f", mbps)
|
||||
"#{resolution} · #{mbps_text} Mbps"
|
||||
end
|
||||
|
||||
def self.call(upload_mbps:)
|
||||
new(upload_mbps: upload_mbps).call
|
||||
end
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Sessions
|
||||
class SetMinQuality
|
||||
def initialize(session, preset:)
|
||||
@session = session
|
||||
@preset = preset.to_s.strip
|
||||
end
|
||||
|
||||
def call
|
||||
id = normalize
|
||||
unless SelectQuality.allowed_min_ids(@session.quality_preset).include?(id)
|
||||
raise ArgumentError, "Qualità minima non valida"
|
||||
end
|
||||
|
||||
return @session if @session.min_quality_preset == id
|
||||
|
||||
@session.update!(min_quality_preset: id)
|
||||
@session.stream_events.create!(
|
||||
event_type: "quality_changed",
|
||||
occurred_at: Time.current,
|
||||
metadata: { "min_quality_preset" => id }
|
||||
)
|
||||
SessionChannel.broadcast_message(
|
||||
@session,
|
||||
{ type: "command", action: "set_min_quality", min_quality_preset: id }
|
||||
)
|
||||
@session
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize
|
||||
return SelectQuality::AUTO_ID if @preset.blank? || @preset == SelectQuality::AUTO_ID
|
||||
|
||||
@preset
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#0a0a0e">
|
||||
<title><%= content_for?(:title) ? yield(:title) : t("regia.meta_title_fallback") %></title>
|
||||
<%= render "shared/meta_tags" %>
|
||||
<link rel="stylesheet" href="/regia.css?v=1">
|
||||
<link rel="stylesheet" href="/regia.css?v=11">
|
||||
<%= yield :head %>
|
||||
</head>
|
||||
<body class="regia-body">
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:6px">−</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="regia-btn regia-btn--yellow" data-action="close_set" style="margin-top:12px"><%= t("regia.board.close_set") %></button>
|
||||
<button type="button" class="regia-btn regia-btn--yellow" data-action="close_set"><%= t("regia.board.close_set") %></button>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
data-resume-url="<%= j public_regia_resume_path(params[:token]) %>"
|
||||
data-audio-mute-url="<%= j public_regia_audio_mute_path(params[:token]) %>"
|
||||
data-stop-url="<%= j public_regia_stop_path(params[:token]) %>"
|
||||
data-min-quality-url="<%= j public_regia_min_quality_path(params[:token]) %>"
|
||||
data-min-quality="<%= j @session.min_quality_preset %>"
|
||||
data-cable-url="<%= j "/cable?regia_token=#{params[:token]}" %>"
|
||||
data-hls-url="<%= j @session.hls_playback_url %>"
|
||||
data-home-name="<%= j @team.name %>"
|
||||
@@ -31,56 +33,122 @@
|
||||
data-initial-audio-muted="<%= @session.audio_muted %>">
|
||||
|
||||
<header class="regia-header">
|
||||
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
|
||||
<p id="regia-subtitle"><%= t("regia.subtitle_prefix") %> <%= regia_header_subtitle(@match, @score) %></p>
|
||||
<div class="regia-header__text">
|
||||
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
|
||||
<p id="regia-subtitle"><%= t("regia.subtitle_prefix") %> <%= regia_header_subtitle(@match, @score) %></p>
|
||||
</div>
|
||||
<span id="regia-badge" class="regia-badge <%= @stream_closed ? 'regia-badge--ended' : (@session.paused? ? 'regia-badge--wait' : (@on_air ? 'regia-badge--live' : 'regia-badge--wait')) %>">
|
||||
<%= @stream_closed ? t("regia.status.ended") : (@session.paused? ? t("regia.status.paused") : (@on_air ? t("regia.status.live") : t("regia.status.waiting"))) %>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<% if @live_share_url.present? %>
|
||||
<div class="regia-share-group">
|
||||
<p class="regia-share-label"><%= t("regia.live_share_label") %></p>
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-live" style="margin:0">
|
||||
<%= t("regia.share_live") %>
|
||||
<nav class="regia-toolbar" aria-label="<%= t("regia.links_nav") %>">
|
||||
<% if @live_share_url.present? %>
|
||||
<div class="regia-tool-group" role="group" aria-label="<%= t("regia.live_share_label") %>">
|
||||
<span class="regia-tool-group__label"><%= t("regia.live_share_label") %></span>
|
||||
<div class="regia-tool-group__btns">
|
||||
<button type="button" class="regia-icon-btn" id="btn-share-live" aria-label="<%= t("regia.share_live") %>" title="<%= t("regia.share_live") %>">
|
||||
<svg class="icon-outline" viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
|
||||
</button>
|
||||
<button type="button" class="regia-icon-btn" id="btn-copy-live" aria-label="<%= t("regia.copy_live") %>" title="<%= t("regia.copy_live") %>">
|
||||
<svg class="icon-outline" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="regia-tool-group" role="group" aria-label="<%= t("regia.regia_share_label") %>">
|
||||
<span class="regia-tool-group__label"><%= t("regia.regia_share_label") %></span>
|
||||
<div class="regia-tool-group__btns">
|
||||
<button type="button" class="regia-icon-btn" id="btn-share-regia" aria-label="<%= t("regia.share_regia") %>" title="<%= t("regia.share_regia") %>">
|
||||
<svg class="icon-outline" viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-live" style="margin:0">
|
||||
<%= t("regia.copy_link") %>
|
||||
<button type="button" class="regia-icon-btn" id="btn-copy-regia" aria-label="<%= t("regia.copy_regia") %>" title="<%= t("regia.copy_regia") %>">
|
||||
<svg class="icon-outline" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="regia-share-group">
|
||||
<p class="regia-share-label"><%= t("regia.regia_share_label") %></p>
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-regia" style="margin:0">
|
||||
<%= t("regia.share_regia") %>
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-regia" style="margin:0">
|
||||
<%= t("regia.copy_link") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="regia-preview">
|
||||
<% unless @stream_closed %>
|
||||
<video id="regia-preview" playsinline muted autoplay></video>
|
||||
<div class="regia-tool-group regia-tool-group--stream" role="group" aria-label="<%= t("regia.stream_controls_label") %>">
|
||||
<span class="regia-tool-group__label" aria-hidden="true"> </span>
|
||||
<div class="regia-tool-group__btns">
|
||||
<button type="button" class="regia-icon-btn" id="btn-toggle-preview" aria-label="<%= t("regia.preview_hide") %>" title="<%= t("regia.preview_hide") %>" aria-pressed="true">
|
||||
<svg class="icon-fill icon-eye" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
||||
<svg class="icon-fill icon-eye-off" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78 3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="regia-icon-btn<%= " is-paused" if @session.paused? %>" id="btn-pause" aria-label="<%= @session.paused? ? t("regia.resume") : t("regia.pause") %>" title="<%= @session.paused? ? t("regia.resume") : t("regia.pause") %>">
|
||||
<svg class="icon-fill icon-pause" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
|
||||
<svg class="icon-fill icon-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="regia-icon-btn<%= " is-muted" if @session.audio_muted %>" id="btn-audio-mute" aria-label="<%= @session.audio_muted ? t("regia.unmute") : t("regia.mute") %>" title="<%= @session.audio_muted ? t("regia.unmute") : t("regia.mute") %>">
|
||||
<svg class="icon-fill icon-mic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5-3c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/></svg>
|
||||
<svg class="icon-fill icon-mic-off" viewBox="0 0 24 24" aria-hidden="true"><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="regia-icon-btn regia-icon-btn--danger" id="btn-stop" aria-label="<%= t("regia.stop") %>" title="<%= t("regia.stop") %>">
|
||||
<svg class="icon-fill" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6h12v12H6z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="regia-preview-placeholder" class="regia-preview__placeholder">
|
||||
<%= @stream_closed ? t("regia.preview_ended") : t("regia.preview_waiting") %>
|
||||
</nav>
|
||||
|
||||
<section class="regia-preview-wrap" id="regia-preview-wrap">
|
||||
<div class="regia-preview" id="regia-preview-box">
|
||||
<% unless @stream_closed %>
|
||||
<video id="regia-preview" playsinline muted autoplay></video>
|
||||
<% end %>
|
||||
<div id="regia-preview-placeholder" class="regia-preview__placeholder">
|
||||
<%= @stream_closed ? t("regia.preview_ended") : t("regia.preview_waiting") %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="regia-scoreboard">
|
||||
<%= render regia_board_partial(@match) %>
|
||||
</section>
|
||||
|
||||
<% unless @stream_closed %>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause"><%= @session.paused? ? t("regia.resume") : t("regia.pause") %></button>
|
||||
<button type="button" class="regia-btn regia-btn--outline<%= @session.audio_muted ? ' is-muted' : '' %>" id="btn-audio-mute"><%= @session.audio_muted ? t("regia.unmute") : t("regia.mute") %></button>
|
||||
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop"><%= t("regia.stop") %></button>
|
||||
<section class="regia-telemetry" id="regia-telemetry" aria-label="<%= t("regia.telemetry.label") %>">
|
||||
<div class="regia-telemetry__row">
|
||||
<span class="regia-chip" id="tel-battery" title="<%= t("regia.telemetry.battery") %>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16 4h-1V3h-6v1H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H8V6h8v14z"/></svg>
|
||||
<span class="regia-chip__label"><%= t("regia.telemetry.battery") %></span>
|
||||
<span class="regia-chip__value">—</span>
|
||||
</span>
|
||||
<span class="regia-chip" id="tel-network" title="<%= t("regia.telemetry.network") %>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.08 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z"/></svg>
|
||||
<span class="regia-chip__label"><%= t("regia.telemetry.network") %></span>
|
||||
<span class="regia-chip__value">—</span>
|
||||
</span>
|
||||
<button type="button" class="regia-chip regia-chip--action" id="tel-bitrate" aria-expanded="false" aria-controls="regia-quality" title="<%= t("regia.telemetry.bitrate_tap") %>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 13h2v-2H3v2zm4 4h2v-6H7v6zm4 4h2V7h-2v14zm4-8h2V9h-2v4zm4-4h2V5h-2v4z"/></svg>
|
||||
<span class="regia-chip__label"><%= t("regia.telemetry.bitrate") %></span>
|
||||
<span class="regia-chip__value">—</span>
|
||||
<span class="regia-chip__hint" hidden></span>
|
||||
</button>
|
||||
<span class="regia-chip" id="tel-fps" title="<%= t("regia.telemetry.fps") %>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>
|
||||
<span class="regia-chip__label"><%= t("regia.telemetry.fps") %></span>
|
||||
<span class="regia-chip__value">—</span>
|
||||
</span>
|
||||
<span class="regia-chip" id="tel-thermal" title="<%= t("regia.telemetry.thermal") %>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 13V5c0-1.66-1.34-3-3-3S9 3.34 9 5v8c-1.21.91-2 2.37-2 4 0 2.76 2.24 5 5 5s5-2.24 5-5c0-1.63-.79-3.09-2-4zm-3 8c-1.66 0-3-1.34-3-3 0-1.19.7-2.23 1.71-2.72L11 14.73V5c0-.55.45-1 1-1s1 .45 1 1v9.73l.29.18C14.3 15.77 15 16.81 15 18c0 1.66-1.34 3-3 3z"/></svg>
|
||||
<span class="regia-chip__label"><%= t("regia.telemetry.thermal") %></span>
|
||||
<span class="regia-chip__value">—</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="regia-quality" id="regia-quality" hidden>
|
||||
<p class="regia-quality__hint"><%= t("regia.min_quality.recommend") %></p>
|
||||
<label for="min-quality"><%= t("regia.min_quality.label") %></label>
|
||||
<select id="min-quality" title="<%= t("regia.min_quality.hint") %>">
|
||||
<% Sessions::SelectQuality.allowed_min_ids(@session.quality_preset).each do |id| %>
|
||||
<option value="<%= id %>" <%= "selected" if @session.min_quality_preset == id %>>
|
||||
<%= id == Sessions::SelectQuality::AUTO_ID ? t("regia.min_quality.auto") : Sessions::SelectQuality.display_name(id) %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
<p class="regia-telemetry__meta" id="tel-meta"><%= t("regia.telemetry.waiting") %></p>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -97,4 +165,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/regia.js?v=9"></script>
|
||||
<script src="/regia.js?v=19"></script>
|
||||
|
||||
Reference in New Issue
Block a user