Visibilità pubblico/link-only su tutte le piattaforme di streaming.

Default pubblico; opzione unica privato/non in elenco per Match Live TV,
YouTube piattaforma e canale società. Elenco dirette solo sessioni pubbliche.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 23:00:34 +02:00
parent 1fb5cd5aa2
commit 1677df7f86
7 changed files with 45 additions and 35 deletions

View File

@@ -11,6 +11,7 @@ module Public
sessions = StreamSession sessions = StreamSession
.broadcasting .broadcasting
.publicly_listed
.includes(:score_state, match: { team: :club }) .includes(:score_state, match: { team: :club })
sessions = sessions.joins(:match).where(matches: { team_id: team_ids }) if team_ids.present? sessions = sessions.joins(:match).where(matches: { team_id: team_ids }) if team_ids.present?

View File

@@ -3,6 +3,7 @@ class StreamSession < ApplicationRecord
PLATFORMS = %w[matchlivetv youtube facebook twitch].freeze PLATFORMS = %w[matchlivetv youtube facebook twitch].freeze
STATUSES = %w[idle connecting live reconnecting paused ended error].freeze STATUSES = %w[idle connecting live reconnecting paused ended error].freeze
PRIVACY_STATUSES = %w[public unlisted private].freeze
belongs_to :match belongs_to :match
belongs_to :user belongs_to :user
@@ -12,10 +13,13 @@ class StreamSession < ApplicationRecord
validates :platform, inclusion: { in: PLATFORMS } validates :platform, inclusion: { in: PLATFORMS }
validates :status, inclusion: { in: STATUSES } validates :status, inclusion: { in: STATUSES }
validates :privacy_status, inclusion: { in: PRIVACY_STATUSES }
before_validation :normalize_privacy_status
before_validation :ensure_publish_token, on: :create before_validation :ensure_publish_token, on: :create
scope :broadcasting, -> { where(status: %w[live connecting reconnecting]) } scope :broadcasting, -> { where(status: %w[live connecting reconnecting]) }
scope :publicly_listed, -> { where(privacy_status: "public") }
scope :search_by_team_or_opponent, lambda { |query| scope :search_by_team_or_opponent, lambda { |query|
q = query.to_s.strip q = query.to_s.strip
return all if q.blank? return all if q.blank?
@@ -105,6 +109,14 @@ class StreamSession < ApplicationRecord
end end
end end
def link_only?
privacy_status.in?(%w[unlisted private])
end
def publicly_listed?
privacy_status == "public"
end
def terminal? def terminal?
status.in?(%w[ended error]) status.in?(%w[ended error])
end end
@@ -151,6 +163,11 @@ class StreamSession < ApplicationRecord
self.publish_token ||= SecureRandom.urlsafe_base64(32) self.publish_token ||= SecureRandom.urlsafe_base64(32)
end end
def normalize_privacy_status
self.privacy_status = "public" if privacy_status.blank?
self.privacy_status = "unlisted" if privacy_status == "private"
end
def record_ended_timestamps! def record_ended_timestamps!
now = Time.current now = Time.current
update!(ended_at: now) if ended_at.nil? update!(ended_at: now) if ended_at.nil?

View File

@@ -1,5 +1,9 @@
module Sessions module Sessions
class Create class Create
def self.normalize_privacy(value)
value.to_s == "public" ? "public" : "unlisted"
end
def initialize(user:, match:, params:) def initialize(user:, match:, params:)
@user = user @user = user
@match = match @match = match
@@ -16,7 +20,7 @@ module Sessions
match: @match, match: @match,
user: @user, user: @user,
platform: platform, platform: platform,
privacy_status: @params[:privacy_status] || "unlisted", privacy_status: Sessions::Create.normalize_privacy(@params[:privacy_status]),
quality_preset: @params[:quality_preset] || "720p_30_2.5mbps", quality_preset: @params[:quality_preset] || "720p_30_2.5mbps",
target_bitrate: @params[:target_bitrate] || 2_500_000, target_bitrate: @params[:target_bitrate] || 2_500_000,
target_fps: @params[:target_fps] || 30, target_fps: @params[:target_fps] || 30,

View File

@@ -8,7 +8,7 @@ module Youtube
@credential = @resolver.resolve @credential = @resolver.resolve
end end
def create_broadcast!(title:, privacy_status: "unlisted", scheduled_at: nil) def create_broadcast!(title:, privacy_status: "public", scheduled_at: nil)
return mock_broadcast if @credential.blank? || missing_oauth_config? return mock_broadcast if @credential.blank? || missing_oauth_config?
client = authorized_client client = authorized_client

View File

@@ -30,7 +30,7 @@ class StepTransmission extends ConsumerStatefulWidget {
class _StepTransmissionState extends ConsumerState<StepTransmission> { class _StepTransmissionState extends ConsumerState<StepTransmission> {
String _platform = 'matchlivetv'; String _platform = 'matchlivetv';
String _privacy = 'unlisted'; bool _linkOnly = false;
String? _youtubeChannel; String? _youtubeChannel;
bool _creating = false; bool _creating = false;
@@ -60,7 +60,7 @@ class _StepTransmissionState extends ConsumerState<StepTransmission> {
widget.match.id, widget.match.id,
platform: _platform, platform: _platform,
youtubeChannel: _platform == 'youtube' ? _youtubeChannel : null, youtubeChannel: _platform == 'youtube' ? _youtubeChannel : null,
privacyStatus: _privacy, privacyStatus: _linkOnly ? 'unlisted' : 'public',
qualityPreset: '720p_30_2.5mbps', qualityPreset: '720p_30_2.5mbps',
targetBitrate: 2500000, targetBitrate: 2500000,
targetFps: 30, targetFps: 30,
@@ -213,39 +213,27 @@ class _StepTransmissionState extends ConsumerState<StepTransmission> {
), ),
], ],
const SizedBox(height: 24), const SizedBox(height: 24),
Text('Visibilità video', style: theme.textTheme.titleLarge), Text('Visibilità', style: theme.textTheme.titleLarge),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Solo per YouTube. Su Match Live TV la pagina diretta segue le impostazioni del sito.', 'Vale per Match Live TV e YouTube (canale nostro o della società). '
'Di default la diretta è pubblica.',
style: theme.textTheme.bodySmall, style: theme.textTheme.bodySmall,
), ),
const SizedBox(height: 12), const SizedBox(height: 8),
SegmentedButton<String>( SwitchListTile(
segments: const [ contentPadding: EdgeInsets.zero,
ButtonSegment( value: _linkOnly,
value: 'unlisted', onChanged: (v) => setState(() => _linkOnly = v),
label: Text('Non in elenco'), title: const Text('Privato / non in elenco'),
icon: Icon(Icons.link), subtitle: Text(
), _platform == 'matchlivetv'
ButtonSegment( ? 'Non compare nell\'elenco dirette del sito. Solo chi ha il link.'
value: 'private', : _platform == 'youtube'
label: Text('Privato'), ? 'Non compare su YouTube in ricerca o nel canale. Solo chi ha il link.'
icon: Icon(Icons.lock_outline), : 'Visibile solo a chi ha il link, non in elenco pubblico.',
),
],
selected: {_privacy},
onSelectionChanged: _platform == 'youtube'
? (s) => setState(() => _privacy = s.first)
: null,
),
if (_platform != 'youtube')
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Seleziona YouTube Live per impostare la visibilità del video.',
style: theme.textTheme.bodySmall?.copyWith(color: AppTheme.textSecondary),
),
), ),
),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('Qualità', style: theme.textTheme.titleLarge), Text('Qualità', style: theme.textTheme.titleLarge),
const SizedBox(height: 12), const SizedBox(height: 12),

View File

@@ -154,7 +154,7 @@ class ApiClient {
String matchId, { String matchId, {
String platform = 'matchlivetv', String platform = 'matchlivetv',
String? youtubeChannel, String? youtubeChannel,
String privacyStatus = 'unlisted', String privacyStatus = 'public',
String qualityPreset = '720p_30_2.5mbps', String qualityPreset = '720p_30_2.5mbps',
int? targetBitrate, int? targetBitrate,
int? targetFps, int? targetFps,

View File

@@ -13,7 +13,7 @@ class StreamSession {
this.shareUrl, this.shareUrl,
this.youtubeWatchUrl, this.youtubeWatchUrl,
this.youtubeBroadcastId, this.youtubeBroadcastId,
this.privacyStatus = 'unlisted', this.privacyStatus = 'public',
this.qualityPreset = '720p_30_2.5mbps', this.qualityPreset = '720p_30_2.5mbps',
this.targetBitrate = 2500000, this.targetBitrate = 2500000,
this.startedAt, this.startedAt,
@@ -62,7 +62,7 @@ class StreamSession {
shareUrl: json['share_url'] as String?, shareUrl: json['share_url'] as String?,
youtubeWatchUrl: json['youtube_watch_url'] as String?, youtubeWatchUrl: json['youtube_watch_url'] as String?,
youtubeBroadcastId: json['youtube_broadcast_id'] as String?, youtubeBroadcastId: json['youtube_broadcast_id'] as String?,
privacyStatus: json['privacy_status'] as String? ?? 'unlisted', privacyStatus: json['privacy_status'] as String? ?? 'public',
qualityPreset: json['quality_preset'] as String? ?? '720p_30_2.5mbps', qualityPreset: json['quality_preset'] as String? ?? '720p_30_2.5mbps',
targetBitrate: json['target_bitrate'] as int? ?? 2500000, targetBitrate: json['target_bitrate'] as int? ?? 2500000,
startedAt: json['started_at'] != null startedAt: json['started_at'] != null