From 42dadb993d4d819cfd6d186cea076d70b27d8ba3 Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Fri, 28 Aug 2026 15:05:12 +0200 Subject: [PATCH] =?UTF-8?q?Abilita=20l'embed=20YouTube=20sulle=20dirette?= =?UTF-8?q?=20e=20tiene=20la=20copia=20locale=20se=20il=20VOD=20non=20?= =?UTF-8?q?=C3=A8=20incorporabile.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../recordings/verify_youtube_replay_job.rb | 11 +++- .../recordings/verify_youtube_replay.rb | 22 +++++++- .../app/services/youtube/broadcast_service.rb | 21 +++++++- backend/app/services/youtube/vod_status.rb | 7 ++- backend/app/views/public/replay/show.html.erb | 54 +++++++++---------- .../api/v1/public_replay_youtube_only_spec.rb | 21 ++++++++ 6 files changed, 104 insertions(+), 32 deletions(-) diff --git a/backend/app/jobs/recordings/verify_youtube_replay_job.rb b/backend/app/jobs/recordings/verify_youtube_replay_job.rb index fbb1015..811109a 100644 --- a/backend/app/jobs/recordings/verify_youtube_replay_job.rb +++ b/backend/app/jobs/recordings/verify_youtube_replay_job.rb @@ -15,7 +15,16 @@ module Recordings result = Recordings::VerifyYoutubeReplay.new(recording).call if result.ok? - Recordings::ClearTemporaryMediaJob.perform_async(recording.id, "verified") + recording.reload + embeddable = recording.metadata.is_a?(Hash) && recording.metadata.dig("youtube", "embeddable") != false + if embeddable + Recordings::ClearTemporaryMediaJob.perform_async(recording.id, "verified") + else + Rails.logger.warn( + "[Recordings::VerifyYoutubeReplayJob] keep_temp recording=#{recording.id} " \ + "youtube_not_embeddable" + ) + end return end diff --git a/backend/app/services/recordings/verify_youtube_replay.rb b/backend/app/services/recordings/verify_youtube_replay.rb index 252e40d..1ca5c96 100644 --- a/backend/app/services/recordings/verify_youtube_replay.rb +++ b/backend/app/services/recordings/verify_youtube_replay.rb @@ -46,10 +46,11 @@ module Recordings return Result.new(status: :pending, message: "vod_not_ready") end + info = enable_embed_if_needed!(info) apply_verified!(info) Rails.logger.info( "[Recordings::VerifyYoutubeReplay] ok recording=#{@recording.id} " \ - "youtube_video_id=#{info.video_id}" + "youtube_video_id=#{info.video_id} embeddable=#{info.embeddable}" ) Result.new(status: :ok, message: "verified") rescue Youtube::VodStatus::Error => e @@ -68,6 +69,7 @@ module Recordings "thumbnail_url" => info.thumbnail_url, "privacy_status" => info.privacy_status, "upload_status" => info.upload_status, + "embeddable" => info.embeddable, "verified_via" => "live_broadcast" ).compact @@ -82,5 +84,23 @@ module Recordings @recording.update!(attrs) end + + def enable_embed_if_needed!(info) + return info if info.embeddable != false + return info if info.video_id.to_s.start_with?("mock_") + + session = @recording.stream_session + Youtube::BroadcastService.new(session.match.team).enable_video_embed!( + info.video_id, + privacy_status: info.privacy_status + ) + refreshed = Youtube::VodStatus.new(@recording.team, channel: "team").fetch(info.video_id) + refreshed.ready ? refreshed : info + rescue Youtube::BroadcastService::Error, Youtube::VodStatus::Error => e + Rails.logger.warn( + "[Recordings::VerifyYoutubeReplay] enable_embed recording=#{@recording.id}: #{e.message}" + ) + info + end end end diff --git a/backend/app/services/youtube/broadcast_service.rb b/backend/app/services/youtube/broadcast_service.rb index f731dc1..2f9f530 100644 --- a/backend/app/services/youtube/broadcast_service.rb +++ b/backend/app/services/youtube/broadcast_service.rb @@ -24,7 +24,8 @@ module Youtube ), content_details: Google::Apis::YoutubeV3::LiveBroadcastContentDetails.new( enable_auto_start: true, - enable_auto_stop: false + enable_auto_stop: false, + enable_embed: true ) ) result = client.insert_live_broadcast("snippet,status,contentDetails", broadcast) @@ -97,6 +98,24 @@ module Youtube :skipped end + def enable_video_embed!(video_id, privacy_status: "unlisted") + return if video_id.blank? || video_id.to_s.start_with?("mock_") + return if @credential.blank? || missing_oauth_config? + + client = authorized_client + video = Google::Apis::YoutubeV3::Video.new( + id: video_id, + status: Google::Apis::YoutubeV3::VideoStatus.new( + embeddable: true, + privacy_status: privacy_status.presence || "unlisted", + self_declared_made_for_kids: false + ) + ) + client.update_video("status", video) + rescue Google::Apis::Error => e + raise Error, e.message + end + def complete_broadcast!(broadcast_id) return if broadcast_id.blank? return if @credential.blank? || missing_oauth_config? diff --git a/backend/app/services/youtube/vod_status.rb b/backend/app/services/youtube/vod_status.rb index 1d70a89..e919c5e 100644 --- a/backend/app/services/youtube/vod_status.rb +++ b/backend/app/services/youtube/vod_status.rb @@ -13,6 +13,7 @@ module Youtube :thumbnail_url, :privacy_status, :upload_status, + :embeddable, keyword_init: true ) @@ -32,7 +33,8 @@ module Youtube duration_secs: nil, thumbnail_url: nil, privacy_status: "unlisted", - upload_status: "processed" + upload_status: "processed", + embeddable: true ) end @@ -51,7 +53,8 @@ module Youtube duration_secs: parse_duration(item.content_details&.duration), thumbnail_url: item.snippet&.thumbnails&.high&.url || item.snippet&.thumbnails&.default&.url, privacy_status: item.status&.privacy_status, - upload_status: upload_status + upload_status: upload_status, + embeddable: item.status&.embeddable != false ) rescue Google::Apis::Error => e raise Error, e.message diff --git a/backend/app/views/public/replay/show.html.erb b/backend/app/views/public/replay/show.html.erb index e6ef7b7..0644588 100644 --- a/backend/app/views/public/replay/show.html.erb +++ b/backend/app/views/public/replay/show.html.erb @@ -14,33 +14,6 @@

<%= t("replay.show.processing_title") %>

<%= t("replay.show.processing_body") %>

- <% elsif @recording.ready? && @recording.replay_source == "youtube" && @recording.youtube_video_id.present? && !@recording.youtube_video_id.to_s.start_with?("mock_") %> -
- - <%= render "public/live/player_overlays", - match: @match, - session: @session, - stream_closed: true, - on_air: false, - badge_label: t("replay.show.badge_label"), - badge_class: "badge-ended" %> -
-

- <%= l_local(@recording.recorded_at_or_fallback) %> - · <%= t("replay.show.meta_line_duration", value: @recording.duration_label) %> - · <%= @recording.views_label %> - <% if @recording.source_platform_label != "—" %> - · <%= @recording.source_platform_label %> - <% end %> -

-

- <%= t("replay.show.youtube_only_body") %> - <%= link_to t("replay.show.youtube_link"), @recording.youtube_watch_url, target: "_blank", rel: "noopener" %> -

<% elsif @recording.ready? && @recording.storage_key.present? %>
<% end %> + <% elsif @recording.ready? && @recording.replay_source == "youtube" && @recording.youtube_video_id.present? && !@recording.youtube_video_id.to_s.start_with?("mock_") %> +
+ + <%= render "public/live/player_overlays", + match: @match, + session: @session, + stream_closed: true, + on_air: false, + badge_label: t("replay.show.badge_label"), + badge_class: "badge-ended" %> +
+

+ <%= l_local(@recording.recorded_at_or_fallback) %> + · <%= t("replay.show.meta_line_duration", value: @recording.duration_label) %> + · <%= @recording.views_label %> + <% if @recording.source_platform_label != "—" %> + · <%= @recording.source_platform_label %> + <% end %> +

+

+ <%= t("replay.show.youtube_only_body") %> + <%= link_to t("replay.show.youtube_link"), @recording.youtube_watch_url, target: "_blank", rel: "noopener" %> +

<% elsif @recording.ready? %>

<%= t("replay.show.file_missing_title") %>

diff --git a/backend/spec/requests/api/v1/public_replay_youtube_only_spec.rb b/backend/spec/requests/api/v1/public_replay_youtube_only_spec.rb index fc6eb9b..58bbeb2 100644 --- a/backend/spec/requests/api/v1/public_replay_youtube_only_spec.rb +++ b/backend/spec/requests/api/v1/public_replay_youtube_only_spec.rb @@ -57,4 +57,25 @@ RSpec.describe "Public replay show youtube-only", type: :request do expect(response.body).to include("id=\"replay-player\"") expect(response.body).not_to include("youtube.com/embed/") end + + it "preferisce il player MP4 locale se la copia temporanea YouTube è ancora presente" do + Recording.create!( + stream_session: session, + team: team, + status: "ready", + storage_policy: "temporary", + storage_key: "temporary_replays/teams/#{team.id}/sessions/#{session.id}/replay.mp4", + youtube_video_id: "publicYtVid01", + youtube_verified_at: Time.current, + privacy_status: "unlisted", + title: "Derby temp", + expires_at: nil, + metadata: { "source_platform" => "youtube" } + ) + + get "/replay/#{session.id}" + expect(response).to have_http_status(:ok) + expect(response.body).to include("id=\"replay-player\"") + expect(response.body).not_to include("youtube.com/embed/publicYtVid01") + end end