`toggleCaptions()` solo alcanza la PRIMERA pista, y a proposito: es un toggle, y las demas se quedan en `disabled` porque poner una en `hidden` OBLIGA al navegador a descargar su VTT — encenderlas todas bajaria todos los ficheros de golpe. La consecuencia es que un video con dos idiomas no tenia ninguna forma de llegar al segundo. `<MediaPlayer.CaptionFloat>` es la lista: `Off` + una entrada por pista, mismo patron que el `RateFloat` de hoy (DropdownMenu + RadioGroup, con el `CaptionButton` real de disparador y su toggle anulado con `preventDefault()` en la costura). OPT-IN: el boton solo sigue siendo el control correcto para una fuente de una sola pista. Por debajo, dos anadidos a soma y ninguno al puerto: - `selectCaptionTrack(track | null)` — pone UNA en `hidden` y el resto en `disabled`, por la misma razon de descarga. - `captionTrackList` + `activeCaptionTrack` — espejos reactivos. El `TextTrackList` vivo es una coleccion del DOM y NO notifica, asi que un chrome que LISTA las pistas no se enteraba de una que llegase tarde. Los refresca `syncCaptions`, que ya corria en addtrack / removetrack / change. Cambiar de idioma NO dispara `commit-toggle-captions`: solo lo hace una transicion real de encendido/apagado. Pasar de ingles a espanol con los subtitulos ya puestos no es un toggle — la misma distincion que el commit de mute hace con `s.muted !== wasMuted`. De paso, tres claves de texto que se me quedaron ayer solo en el LANGS del provider y no en el `texts` del morfo (restart, skip-previous, skip-next), mas `captions-none` para la entrada «Off». El `texts` del morfo ES la superficie i18n que pinta la demo, asi que faltar ahi es faltar de verdad. Verificado en Chrome con dos pistas VTT reales: abrir no toca los modos · exacto UNA pista en `hidden` y el resto en `disabled` · las cues PINTAN el idioma elegido (English «Inline captions demo track», Espanol «Pista de subtitulos de prueba») y Off deja la caja vacia, no congelada · panel 94px. Test nacido en rojo. 17/17 player · morfo:check media-player PASS · eidos-lint invalid 0 · class-hooks 0 · check 0 errores propios (78, los mismos de antes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>alpha-0.1-dir-prefs
parent
c73669cde7
commit
21d817c1f6
@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* `<MediaPlayer.CaptionFloat>` — the subtitle TRACK picker, as a floating
|
||||
* list. Optional: `<CaptionButton>` on its own is still the right control
|
||||
* for a single-track source, and this is the other one.
|
||||
*
|
||||
* Why it exists: `toggleCaptions()` can only ever reach the first track, and
|
||||
* deliberately so — it is a toggle. A source with several subtitle languages
|
||||
* had no way to reach the rest at all. The list adds `Off` plus one entry
|
||||
* per track, which is what the button structurally cannot express.
|
||||
*
|
||||
* Why NOT the `SettingsButton`: the gear is declared in the morfo for
|
||||
* speed / quality / track, but speed now has its own list and quality does
|
||||
* not exist in the `MediaProvider` port (nothing in `MediaSnapshot` carries
|
||||
* it — it is an HLS/DASH concept the consumer's engine would supply). A gear
|
||||
* holding one section would be this component wearing a costume, so the gear
|
||||
* stays unimplemented until it has two real axes. See the eidos README.
|
||||
*
|
||||
* The trigger stays the real `<CaptionButton>`: it keeps its morfo part, its
|
||||
* `aria-pressed`, the `c` hotkey and the glyph that follows `data-state`,
|
||||
* plus soma's own disabling when no track exists. Its toggle is suppressed
|
||||
* here — `composeHandlers` skips the soma handler when the consumer's
|
||||
* `onclick` calls `preventDefault()` — so the click opens the list.
|
||||
*
|
||||
* A radio group, like the speed list: exactly one track is on at a time, so
|
||||
* the current one is announced as checked instead of merely looking it.
|
||||
*/
|
||||
import { DropdownMenu } from '$uix/eidos/components/dropdown-menu';
|
||||
import { MediaPlayerProvider } from '$soma/components/media-player';
|
||||
import CaptionButton from './media-player-caption-button.svelte';
|
||||
import type { MediaPlayerCaptionFloatProps } from './types';
|
||||
|
||||
let {
|
||||
side = 'top',
|
||||
sideOffset = 8,
|
||||
align = 'center',
|
||||
...rest
|
||||
}: MediaPlayerCaptionFloatProps = $props();
|
||||
|
||||
const provider = MediaPlayerProvider.require();
|
||||
const tracks = $derived(provider.captionTrackList);
|
||||
const off = $derived(provider.soma.langs.ts('#?components.media-player.captions-none|Off'));
|
||||
|
||||
// The index IS the identity: `TextTrack.id` comes from the `<track>` element's
|
||||
// `id` attribute and is empty in practice, and two tracks can share a
|
||||
// language. `'off'` is the absence.
|
||||
const value = $derived.by(() => {
|
||||
const i = tracks.indexOf(provider.activeCaptionTrack as TextTrack);
|
||||
return i === -1 ? 'off' : String(i);
|
||||
});
|
||||
|
||||
/** `label` is what a `<track>` author wrote; fall back to the language tag. */
|
||||
const trackName = (t: TextTrack, i: number) => t.label || t.language || `${i + 1}`;
|
||||
</script>
|
||||
|
||||
<DropdownMenu {...rest}>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<CaptionButton
|
||||
{...props}
|
||||
onclick={(e: MouseEvent) => {
|
||||
(props as { onclick?: (e: MouseEvent) => void }).onclick?.(e);
|
||||
// Opening the list IS the click's whole job here.
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<!-- `data-media-player-caption-float` is an eidos-only marker: the panel is
|
||||
portalled, so the recipe needs something that travels with it to undo the
|
||||
menu's prose-sized min-width. -->
|
||||
<DropdownMenu.Content {side} {sideOffset} {align} size="sm" data-media-player-caption-float>
|
||||
<DropdownMenu.RadioGroup
|
||||
{value}
|
||||
onValueChange={(v: string) =>
|
||||
provider.selectCaptionTrack(v === 'off' ? null : (tracks[Number(v)] ?? null))}
|
||||
>
|
||||
<DropdownMenu.RadioItem value="off">{off}</DropdownMenu.RadioItem>
|
||||
{#each tracks as track, i (i)}
|
||||
<DropdownMenu.RadioItem value={String(i)}>{trackName(track, i)}</DropdownMenu.RadioItem>
|
||||
{/each}
|
||||
</DropdownMenu.RadioGroup>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
Loading…
Reference in new issue