From 13bfa4b016041e53cd6fec073f1181efe32056d9 Mon Sep 17 00:00:00 2001
From: Francisco Solla
Date: Wed, 16 Sep 2026 20:39:22 -0300
Subject: [PATCH] =?UTF-8?q?fix(C181):=20player=20do=20acervo=20volta=20a?=
=?UTF-8?q?=20ter=20o=20YouTube=20in-page=20e=20espera=20a=20C=C3=A2mara?=
=?UTF-8?q?=20gerar=20o=20trecho?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- restaura o embed do YouTube como superfície opt-in no SpeechDetailPlayer
(a Câmara segue a entrada padrão; o embed nunca é a porta automática, guardrail C178)
- controle simétrico "Assistir no YouTube"/"Assistir na Câmara" no bloco de saída,
com o ponto da fala preservado na troca (pendingSeekRef/youtubeStart)
- SPEECH_VOD_PLAYER_POLICY passa a pollAttempts: 2 / pollDelayMs: 3000 — espera
bounded dentro do clique em vez de tratar o primeiro GERANDO como falha
- unit do resolver (pin + GERANDO→PRONTO) e do player (ida-e-volta + ponto);
pin SSR do e2e mantendo o embed fora do HTML inicial
Closes #1103
---
docs/changelog/2026-09-16-c181.md | 1 +
...-volta-ao-youtube-e-vod-com-espera-impl.md | 166 ++++++++++--------
.../campaign/speech/SpeechDetailPlayer.tsx | 152 ++++++++++++++--
src/utilities/speech/speechVodResolver.ts | 21 ++-
tests/e2e/campaignSpeechAcervo.e2e.spec.ts | 20 ++-
tests/unit/speechDetailPlayer.unit.spec.tsx | 42 +++++
tests/unit/speechVodResolver.unit.spec.ts | 56 +++++-
7 files changed, 339 insertions(+), 119 deletions(-)
create mode 100644 docs/changelog/2026-09-16-c181.md
diff --git a/docs/changelog/2026-09-16-c181.md b/docs/changelog/2026-09-16-c181.md
new file mode 100644
index 00000000..e17f5cff
--- /dev/null
+++ b/docs/changelog/2026-09-16-c181.md
@@ -0,0 +1 @@
+**C181 — Acervo: o player volta a ter o YouTube in-page e espera a Câmara gerar o trecho (2026-09-16):** o aviso do dono era um defeito de duas cabeças na mesma fala. Primeira: depois do C178 a Câmara virou o caminho padrão, mas quem preferia o YouTube ficava sem volta na própria página (só o link externo "Abrir no YouTube", que sai da página). Segunda: o clique do player usava `SPEECH_VOD_PLAYER_POLICY` com `pollAttempts: 0`, então o primeiro `GERANDO` da Câmara já virava o estado "gerando" com "Tentar novamente" — a geração era curta e o retry "funcionava", provando que o app desistia cedo. A correção é pequena e simétrica. No `SpeechDetailPlayer`, o `
+ ) : resolution.kind === 'failed' ? (
+
+ {resolution.message ??
+ 'Não foi possível resolver o arquivo deste trecho na Câmara. Tente novamente.'}
+
+ O arquivo deste trecho não pôde ser verificado na Câmara agora.
+
+ ) : null
+ ) : null
+
// C178 — the exit block stays reachable in every state: the app never sees the
// reason the video cannot play, so the way out cannot depend on detecting it.
// The Câmara panel owns the primary CTA while it is idle ("Assistir o trecho");
@@ -416,17 +503,44 @@ export const SpeechDetailPlayer = ({
)}
-
+
)
@@ -517,6 +631,8 @@ export const SpeechDetailPlayer = ({
) : null}
+ {inlineNotice}
+
{publishedCut ? : null}
{selection ? (
diff --git a/src/utilities/speech/speechVodResolver.ts b/src/utilities/speech/speechVodResolver.ts
index d2dd2cd6..f9a3c00b 100644
--- a/src/utilities/speech/speechVodResolver.ts
+++ b/src/utilities/speech/speechVodResolver.ts
@@ -15,10 +15,11 @@ import {
* owns the wait budget through a `SpeechVodPolicy`, and the media verification
* (not the policy) decides what may reach the browser.
*
- * The player keeps the bounded click of C162 (`SPEECH_VOD_PLAYER_POLICY`); the
- * cut job (C169) needs the measured first-request window (~30s, then short
- * polls) plus the stored links as a last verified candidate, and passes
- * `SPEECH_VOD_CUT_POLICY` + `cachedUrls`.
+ * The player keeps a short click budget of its own (`SPEECH_VOD_PLAYER_POLICY`;
+ * C181 widened it to poll a transcode in progress) and stops before the cut
+ * job's window; the cut job (C169) needs the measured first-request window
+ * (~30s, then short polls) plus the stored links as a last verified candidate,
+ * and passes `SPEECH_VOD_CUT_POLICY` + `cachedUrls`.
*/
export type SpeechVodPolicy = {
@@ -34,13 +35,19 @@ export type SpeechVodPolicy = {
pollDelayMs: number
}
-/** C162's player click: short, no poll, immediate answer. */
+/**
+ * C181's player click: a short, own budget that waits out the Câmara's
+ * transcoding instead of treating the first `GERANDO` as a failure — 1 read +
+ * 2 polls of 3 s (~6 s extra), between the old immediate answer and the cut
+ * job's budget (a 45 s read timeout plus 2 polls of 5 s). The wait stays
+ * bounded; past it the honest "gerando" state and the retry remain.
+ */
export const SPEECH_VOD_PLAYER_POLICY: SpeechVodPolicy = {
statusTimeoutMs: 15_000,
statusRetries: 1,
retryDelayMs: 1_000,
- pollAttempts: 0,
- pollDelayMs: 1_000,
+ pollAttempts: 2,
+ pollDelayMs: 3_000,
}
/** C169's cut job: covers the measured ~30s first request and 1–2 short polls. */
diff --git a/tests/e2e/campaignSpeechAcervo.e2e.spec.ts b/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
index 67fd095e..79cfcad7 100644
--- a/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
+++ b/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
@@ -11,11 +11,13 @@ import { assertCampaignRedirect, expect, rendered, test } from './fixtures/campa
* the filter URL contract and the detail's player/transcript/actions. The
* player picks the surface from the row coordinates — the Câmara excerpt as the
* default when the speech has one (stored-VOD capa resolved on click), a
- * clickable YouTube cover when it only has a session link (C178: YouTube is the
- * external exit, never an embedded entry) — and honest states when neither
- * exists — and the stored VOD link itself is never rendered (C162). Playback is
- * browser territory and is not asserted here; the HTML carries the contract
- * (`