// In-page "Download this video ▾" panel (docs/05 §3). Lists variants the daemon // enumerated via media.listVariants — the extension never parses the manifest itself. // DRM-protected variants (and a wholly DRM-protected manifest) are shown greyed out // with "Protected content" rather than attempted. import type { MediaVariant } from '../shared/protocol/index.js'; export interface VariantsPort { listVariants(manifestUrl: string): Promise<{ variants: MediaVariant[]; drmProtected: boolean } | { error: string }>; addVariant(manifestUrl: string, variantId: string): Promise<{ taskId: string } | { error: string }>; } function variantLabel(v: MediaVariant): string { const bits = [v.resolution, v.codec, v.bitrateBps ? `${Math.round(v.bitrateBps / 1000)} kbps` : null].filter(Boolean); return bits.length > 0 ? bits.join(' · ') : v.variantId; } const PANEL_ID = 'velox-video-panel'; export class VideoPanel { private root: HTMLElement | null = null; constructor( private readonly port: VariantsPort, private readonly doc: Document = document, ) {} /** Shows (or replaces) the floating panel for one detected manifest URL. */ show(manifestUrl: string): void { this.remove(); const root = this.doc.createElement('div'); root.id = PANEL_ID; Object.assign(root.style, { position: 'fixed', bottom: '16px', right: '16px', zIndex: '2147483647', font: '13px sans-serif', } satisfies Partial); const button = this.doc.createElement('button'); button.textContent = 'Download this video ▾'; root.appendChild(button); const menu = this.doc.createElement('ul'); menu.hidden = true; Object.assign(menu.style, { listStyle: 'none', margin: '4px 0 0', padding: '4px' } satisfies Partial); root.appendChild(menu); button.addEventListener('click', () => { menu.hidden = !menu.hidden; if (!menu.hidden) void this.populate(menu, manifestUrl); }); this.doc.body.appendChild(root); this.root = root; } remove(): void { this.root?.remove(); this.root = null; } private async populate(menu: HTMLUListElement, manifestUrl: string): Promise { menu.replaceChildren(this.loadingItem()); const result = await this.port.listVariants(manifestUrl); if ('error' in result) { menu.replaceChildren(this.messageItem(`Could not read variants: ${result.error}`)); return; } if (result.drmProtected || result.variants.length === 0) { menu.replaceChildren(this.messageItem(result.drmProtected ? 'Protected content' : 'No downloadable variants found')); return; } menu.replaceChildren( ...result.variants.map((v) => { const li = this.doc.createElement('li'); const item = this.doc.createElement('button'); item.textContent = v.drm ? `${variantLabel(v)} — Protected content` : variantLabel(v); item.disabled = v.drm; if (!v.drm) { item.addEventListener('click', () => void this.port.addVariant(manifestUrl, v.variantId)); } li.appendChild(item); return li; }), ); } private loadingItem(): HTMLLIElement { return this.messageItem('Loading…'); } private messageItem(text: string): HTMLLIElement { const li = this.doc.createElement('li'); li.textContent = text; return li; } }