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:
@@ -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?
|
||||||
|
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user