Files
MatchLiveTv/backend/app/services/youtube/broadcast_service.rb
Emiliano Frascaro fd7a5c2c8e Fix bind_live_broadcast: stream_id come keyword arg.
La gem google-apis-youtube_v3 richiede stream_id: e non un terzo argomento
posizionale, causando 500 in creazione sessione YouTube.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 00:11:38 +02:00

122 lines
4.1 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module Youtube
class BroadcastService
class Error < StandardError; end
def initialize(team, youtube_channel: nil)
@team = team
@resolver = CredentialResolver.new(team, channel: youtube_channel)
@credential = @resolver.resolve
end
def create_broadcast!(title:, privacy_status: "public", scheduled_at: nil)
return mock_broadcast if @credential.blank? || missing_oauth_config?
client = authorized_client
start_time = normalize_scheduled_start_time(scheduled_at)
broadcast = Google::Apis::YoutubeV3::LiveBroadcast.new(
snippet: Google::Apis::YoutubeV3::LiveBroadcastSnippet.new(
title: title,
scheduled_start_time: start_time.iso8601
),
status: Google::Apis::YoutubeV3::LiveBroadcastStatus.new(
privacy_status: privacy_status,
self_declared_made_for_kids: false
),
content_details: Google::Apis::YoutubeV3::LiveBroadcastContentDetails.new(
enable_auto_start: true,
enable_auto_stop: true
)
)
result = client.insert_live_broadcast("snippet,status,contentDetails", broadcast)
stream = Google::Apis::YoutubeV3::LiveStream.new(
snippet: Google::Apis::YoutubeV3::LiveStreamSnippet.new(title: "#{title} stream"),
cdn: Google::Apis::YoutubeV3::CdnSettings.new(
frame_rate: "30fps",
ingestion_type: "rtmp",
resolution: "720p"
)
)
stream_result = client.insert_live_stream("snippet,cdn", stream)
client.bind_live_broadcast(result.id, "id,contentDetails", stream_id: stream_result.id)
{
broadcast_id: result.id,
stream_id: stream_result.id,
stream_key: stream_result.cdn.ingestion_info.stream_name,
rtmp_url: stream_result.cdn.ingestion_info.ingestion_address
}
rescue Google::Apis::Error => e
raise Error, friendly_api_error(e)
end
def complete_broadcast!(broadcast_id)
return if broadcast_id.blank?
return if @credential.blank? || missing_oauth_config?
client = authorized_client
client.transition_live_broadcast("complete", broadcast_id, "id,status")
rescue Google::Apis::Error
nil
end
def viewer_count(broadcast_id)
return 0 if @credential.blank? || missing_oauth_config?
client = authorized_client
resp = client.list_live_broadcasts("statistics", id: broadcast_id)
resp.items&.first&.statistics&.concurrent_viewers.to_i
rescue Google::Apis::Error
0
end
private
def mock_broadcast
{
broadcast_id: "mock_#{SecureRandom.hex(8)}",
stream_id: "mock_stream",
stream_key: ENV.fetch("YOUTUBE_MOCK_STREAM_KEY", "mock-stream-key"),
rtmp_url: "rtmp://a.rtmp.youtube.com/live2"
}
end
def missing_oauth_config?
ENV["YOUTUBE_CLIENT_ID"].blank?
end
def authorized_client
OauthRefresh.new(@credential).apply!(Google::Apis::YoutubeV3::YouTubeService.new)
end
def friendly_api_error(error)
msg = error.message.to_s
return "Il canale YouTube non ha la diretta attiva. Apri YouTube Studio con laccount del canale Match Live TV, abilita la live (verifica telefono se richiesto) e riprova." if msg.include?("liveStreamingNotEnabled")
return "Credenziali YouTube non valide. Ricollega il canale dalla sezione Admin → YouTube." if msg.match?(/Unauthorized|invalid_grant/i)
if msg.match?(/invalidScheduledStartTime|scheduledStartTimeRequired/i)
return "Orario programmazione YouTube non valido. Riprova: la diretta partirà appena invii il segnale RTMP."
end
msg
end
# YouTube richiede scheduledStartTime obbligatorio e nel futuro (non accetta date partita già passate).
def normalize_scheduled_start_time(scheduled_at)
now = Time.current.utc
minimum = now + 5.minutes
maximum = now + 7.days
candidate =
if scheduled_at.blank?
minimum
else
scheduled_at.utc
end
candidate = minimum if candidate < minimum
candidate = maximum if candidate > maximum
candidate
end
end
end