Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ assignees: ''
<!-- URL da transmissão ao vivo onde o problema ocorre, se puder compartilhar.
Ajuda muito reproduzir o cenário exato. -->

## Diagnóstico da extensão

<!-- Com a live aberta, abra o popup do ZeroDelay > Indicadores no player >
"Diagnóstico" > Copiar, e cole o JSON aqui dentro do bloco abaixo.
Ele traz os últimos ~2 minutos do motor (latência, buffer, velocidade)
e as capacidades do player — nada além do que você vê no JSON. -->

```json

```

## Capturas de tela / console

<!-- Se possível, anexe prints. Para erros de JavaScript, abra o DevTools
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Nightly canary: load the extension on a real 24/7 live and verify that
# (1) YouTube's private player APIs the engine depends on still exist, and
# (2) the engine comes alive and samples the stream (via its own diagnostics
# bridge). This is the project's early-warning system for the risk ROADMAP.md
# calls out: a YouTube-side refactor silently breaking everyone at once.
#
# Deliberately NOT part of the PR/push CI: YouTube from a datacenter IP is
# inherently flaky (consent walls, robot checks, regional ads). A failure
# files/updates ONE deduplicated issue (label: canary) for a human to verify
# locally — it never blocks merges.
name: Canary

on:
schedule:
- cron: '23 6 * * *' # daily, off the top of the hour
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
canary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci --no-audit --no-fund
- name: Probe a live stream with the extension loaded
run: node scripts/canary.mjs
env:
# Repository variable to repoint when the default 24/7 stream dies.
CANARY_VIDEO: ${{ vars.CANARY_VIDEO }}
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: canary-report
path: canary-report.json
if-no-files-found: ignore
- name: File (or update) the canary issue
if: failure()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let report = '(canary-report.json missing — the run crashed before probing)';
try { report = fs.readFileSync('canary-report.json', 'utf8'); } catch {}
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
'## Canário vermelho 🐤',
'',
'A sonda noturna falhou ao validar as APIs privadas do player / o motor numa live real.',
`Execução: ${runUrl}`,
'',
'**Antes de reagir:** rode `npm run canary` localmente — IP de datacenter no CI',
'é naturalmente instável (consent wall, robot check). Se falhar local também,',
'as APIs do YouTube provavelmente mudaram (ver `caps` no relatório).',
'',
'<details><summary>canary-report.json</summary>',
'',
'```json',
report.slice(0, 60000),
'```',
'</details>',
].join('\n');
const { data: open } = await github.rest.issues.listForRepo({
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'canary', per_page: 1,
});
if (open.length) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: open[0].number, body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner, repo: context.repo.repo,
title: '[Canary]: sonda noturna falhou — verificar APIs do player',
labels: ['canary'], body,
});
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ node_modules/
# Marketing/store assets — não versionados (gerados a partir dos HTML localmente).
publishing/assets/

# Canary probe output (scripts/canary.mjs — uploaded as a CI artifact, never committed)
canary-report.json

# OS / editor / cloud cruft
.DS_Store
Thumbs.db
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ Todas as mudanças relevantes deste projeto são documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/)
e o projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).

## [Não lançado]

### Adicionado

- **"Copiar diagnóstico"** no painel avançado do popup: copia um JSON com os
últimos ~2 minutos do motor (latência, buffer e velocidade aplicada, 1
amostra/s), as capacidades detectadas do player, o estado do controlador, o
modo e as configurações — pronto para colar num relatório de bug (o template
de issue já traz o espaço). 100% local: nada é coletado nem enviado; o dado
só existe na memória da aba e só sai do navegador se você mesmo colar.
- **"Resumo da sessão"** ao lado do diagnóstico: copia um texto curto e
compartilhável com o tempo assistido, o atraso recuperado pelo catch-up, os
pulos para o ao vivo, as travadas e a latência média da live atual.

### Adicionado (ferramentas do repositório)

- **Canário noturno no CI** (`.github/workflows/canary.yml` +
`scripts/canary.mjs` / `npm run canary`): todo dia, carrega a extensão numa
live 24/7 de verdade e confere (1) que as APIs privadas do player que o motor
usa ainda existem e (2) que o motor liga e amostra a live (pela ponte de
diagnóstico). Falha abre/atualiza UMA issue deduplicada (label `canary`) —
nunca bloqueia merge, porque YouTube visto de IP de datacenter é
naturalmente instável. É o alarme antecipado para o risco que o ROADMAP
aponta como existencial: o YouTube refatorar o player e quebrar todo mundo.

## [1.4.0] - 2026-07-07

### Adicionado
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ YouTube player UI.
- Keyboard shortcuts to toggle on/off (`Alt+Shift+Y`) and jump to live
(`Alt+Shift+L`) — `⌘+Shift+…` on Mac — remappable at
`chrome://extensions/shortcuts`
- One-tap **Copy diagnostics** (the engine's last ~2 minutes, ready to paste
into a bug report) and a shareable **session summary** — both 100% local,
copied only when you ask

## Settings

Expand Down
3 changes: 3 additions & 0 deletions README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ YouTube.
- Atalhos de teclado para ligar/desligar (`Alt+Shift+Y`) e pular para o ao vivo
(`Alt+Shift+L`) — `⌘+Shift+…` no Mac —, reconfiguráveis em
`chrome://extensions/shortcuts`
- **Copiar diagnóstico** em um toque (os últimos ~2 minutos do motor, pronto
para colar num relatório de bug) e um **resumo da sessão** compartilhável —
ambos 100% locais, copiados só quando você pede

## Configurações

Expand Down
33 changes: 33 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,39 @@
"faqShortcutsA": {
"message": "Two shortcuts (on Mac, swap Alt for ⌘ Command):\n• Alt+Shift+Y — turn ZeroDelay on/off\n• Alt+Shift+L — jump to live\nYou can view and change them at chrome://extensions/shortcuts."
},
"diagLabel": {
"message": "Diagnostics (for bug reports)"
},
"summaryLabel": {
"message": "Session summary"
},
"toolCopy": {
"message": "Copy"
},
"toolCopied": {
"message": "Copied!"
},
"toolNoLive": {
"message": "Open a live stream"
},
"summaryTitle": {
"message": "session summary"
},
"summaryWatched": {
"message": "watched"
},
"summaryRecovered": {
"message": "delay recovered"
},
"summaryJumps": {
"message": "jumps to live"
},
"summaryStalls": {
"message": "stalls"
},
"summaryAvgLatency": {
"message": "average latency"
},
"aboutLink": {
"message": "About ZeroDelay"
},
Expand Down
33 changes: 33 additions & 0 deletions _locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,39 @@
"faqShortcutsA": {
"message": "Dos atajos (en Mac, cambia Alt por ⌘ Command):\n• Alt+Shift+Y — activar/desactivar ZeroDelay\n• Alt+Shift+L — saltar al directo\nPuedes verlos y cambiarlos en chrome://extensions/shortcuts."
},
"diagLabel": {
"message": "Diagnóstico (para reportar problemas)"
},
"summaryLabel": {
"message": "Resumen de la sesión"
},
"toolCopy": {
"message": "Copiar"
},
"toolCopied": {
"message": "¡Copiado!"
},
"toolNoLive": {
"message": "Abre un directo"
},
"summaryTitle": {
"message": "resumen de la sesión"
},
"summaryWatched": {
"message": "visto"
},
"summaryRecovered": {
"message": "retraso recuperado"
},
"summaryJumps": {
"message": "saltos al directo"
},
"summaryStalls": {
"message": "cortes"
},
"summaryAvgLatency": {
"message": "latencia media"
},
"aboutLink": {
"message": "Acerca de ZeroDelay"
},
Expand Down
33 changes: 33 additions & 0 deletions _locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,39 @@
"faqShortcutsA": {
"message": "Deux raccourcis (sur Mac, remplacez Alt par ⌘ Command) :\n• Alt+Shift+Y — activer/désactiver ZeroDelay\n• Alt+Shift+L — aller au direct\nVous pouvez les voir et les modifier dans chrome://extensions/shortcuts."
},
"diagLabel": {
"message": "Diagnostic (pour signaler un problème)"
},
"summaryLabel": {
"message": "Résumé de la session"
},
"toolCopy": {
"message": "Copier"
},
"toolCopied": {
"message": "Copié !"
},
"toolNoLive": {
"message": "Ouvrez un direct"
},
"summaryTitle": {
"message": "résumé de la session"
},
"summaryWatched": {
"message": "visionné"
},
"summaryRecovered": {
"message": "retard rattrapé"
},
"summaryJumps": {
"message": "sauts vers le direct"
},
"summaryStalls": {
"message": "blocages"
},
"summaryAvgLatency": {
"message": "latence moyenne"
},
"aboutLink": {
"message": "À propos"
},
Expand Down
33 changes: 33 additions & 0 deletions _locales/pt_BR/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,39 @@
"faqShortcutsA": {
"message": "São dois atalhos (no Mac, troque Alt por ⌘ Command):\n• Alt+Shift+Y — ativar/desativar o ZeroDelay\n• Alt+Shift+L — pular para o ao vivo\nVocê pode vê-los e mudá-los em chrome://extensions/shortcuts."
},
"diagLabel": {
"message": "Diagnóstico (para relatar problemas)"
},
"summaryLabel": {
"message": "Resumo da sessão"
},
"toolCopy": {
"message": "Copiar"
},
"toolCopied": {
"message": "Copiado!"
},
"toolNoLive": {
"message": "Abra uma live"
},
"summaryTitle": {
"message": "resumo da sessão"
},
"summaryWatched": {
"message": "assistido"
},
"summaryRecovered": {
"message": "atraso recuperado"
},
"summaryJumps": {
"message": "pulos para o ao vivo"
},
"summaryStalls": {
"message": "travadas"
},
"summaryAvgLatency": {
"message": "latência média"
},
"aboutLink": {
"message": "Sobre o ZeroDelay"
},
Expand Down
13 changes: 13 additions & 0 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,19 @@ export const label = {
faqShortcutsQ: msg('faqShortcutsQ', 'Existem atalhos de teclado?'),
faqShortcutsA: msg('faqShortcutsA', 'São dois atalhos (no Mac, troque Alt por ⌘ Command):\n• Alt+Shift+Y — ativar/desativar o ZeroDelay\n• Alt+Shift+L — pular para o ao vivo\nVocê pode vê-los e mudá-los em chrome://extensions/shortcuts.'),

// Diagnostics + session summary (the two copy tools in the advanced panel).
diagLabel: msg('diagLabel', 'Diagnóstico (para relatar problemas)'),
summaryLabel: msg('summaryLabel', 'Resumo da sessão'),
toolCopy: msg('toolCopy', 'Copiar'),
toolCopied: msg('toolCopied', 'Copiado!'),
toolNoLive: msg('toolNoLive', 'Abra uma live'),
summaryTitle: msg('summaryTitle', 'resumo da sessão'),
summaryWatched: msg('summaryWatched', 'assistido'),
summaryRecovered: msg('summaryRecovered', 'atraso recuperado'),
summaryJumps: msg('summaryJumps', 'pulos para o ao vivo'),
summaryStalls: msg('summaryStalls', 'travadas'),
summaryAvgLatency: msg('summaryAvgLatency', 'latência média'),

// Footer links — project page + issue tracker on GitHub.
aboutLink: msg('aboutLink', 'Sobre o ZeroDelay'),
reportIssue: msg('reportIssue', 'Relatar um problema'),
Expand Down
27 changes: 26 additions & 1 deletion content.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,32 @@ function main(common) {

// Active-tab-only path: background.js sends this directly to the tab the
// "go-live" shortcut was pressed on (PR #16).
function onRuntimeMessage(msg) {
function onRuntimeMessage(msg, _sender, sendResponse) {
if (msg?.type === 'go-live') dispatchGoLive();
else if (msg?.type === 'zd-diagnostics') return relayDiagnostics(sendResponse);
}

// Popup → engine diagnostics relay. Every frame gets the request; only a
// frame whose engine has live samples answers (see inject.js), so the first
// response IS the live one. When no frame answers, the port simply closes
// unanswered and the popup's own timeout reads it as "no live here".
function relayDiagnostics(sendResponse) {
let settled = false;
const finish = payload => {
if (settled) return;
settled = true;
document.removeEventListener('_zd_diag_response', onResponse);
try { sendResponse(payload); } catch { /* popup closed while we waited */ }
};
const onResponse = e => { if (e.detail?.ok) finish(e.detail); };
document.addEventListener('_zd_diag_response', onResponse);
document.dispatchEvent(new CustomEvent('_zd_diag_request'));
setTimeout(() => {
if (settled) return;
settled = true; // stop listening; deliberately never responds
document.removeEventListener('_zd_diag_response', onResponse);
}, 800);
return true; // keep the message channel open for the async response
}

// Guard against double-registration if the content script re-inits in the
Expand Down Expand Up @@ -127,6 +151,7 @@ function main(common) {
return s;
};
injectScript('engine/controller.js');
injectScript('engine/telemetry.js');
injectScript('inject.js').id = '_live_catch_up';
}

Expand Down
Loading