Skip to content

Commit

Permalink
feat(player): show example captions text on style change
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Feb 23, 2024
1 parent 0b6470f commit 8ceae6b
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import {
import { useMediaContext } from '../../../hooks/use-media-context';
import { useMediaState } from '../../../hooks/use-media-state';
import { createComputed } from '../../../hooks/use-signals';
import { Captions } from '../../ui/captions';
import * as Controls from '../../ui/controls';
import { useLayoutName } from '../utils';
import { DefaultLayoutContext, i18n, useDefaultLayoutContext } from './context';
import { createDefaultMediaLayout, type DefaultLayoutProps } from './media-layout';
import {
DefaultCaptionButton,
DefaultCaptions,
DefaultChaptersMenu,
DefaultControlsSpacer,
DefaultMuteButton,
Expand Down Expand Up @@ -111,7 +111,7 @@ function AudioLayout() {
const slots = useDefaultAudioLayoutSlots();
return (
<>
<Captions className="vds-captions" />
<DefaultCaptions />
<Controls.Root className="vds-controls">
<Controls.Group className="vds-controls-group">
{slot(slots, 'seekBackwardButton', <DefaultSeekButton backward tooltip="top start" />)}
Expand Down
36 changes: 26 additions & 10 deletions packages/react/src/components/layouts/default/font-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,22 +278,38 @@ function DefaultFontSettingSubmenu({
return i18n(translations, label);
}, [value, radioOptions]);

const onChange = React.useCallback((newValue: string) => {
setValue(newValue);
localStorage.setItem(`vds-player:${key}`, newValue);
player?.el?.style.setProperty(
`--media-user-${cssVarName}`,
getCssVarValue?.(newValue, player) ?? newValue,
);
}, []);
const update = React.useCallback(
(newValue: string) => {
setValue(newValue);
localStorage.setItem(`vds-player:${key}`, newValue);
player?.el?.style.setProperty(
`--media-user-${cssVarName}`,
getCssVarValue?.(newValue, player) ?? newValue,
);
},
[player],
);

const notify = React.useCallback(() => {
player?.dispatchEvent(new Event('vds-font-change'));
}, [player]);

const onChange = React.useCallback(
(newValue: string) => {
update(newValue);
notify();
},
[update, notify],
);

const onReset = React.useCallback(() => {
setValue(defaultValue);
}, []);
notify();
}, [notify]);

React.useEffect(() => {
const savedValue = localStorage.getItem(`vds-player:${key}`);
if (savedValue) onChange(savedValue);
if (savedValue) update(savedValue);

resets.all.add(onReset);
return () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/react/src/components/layouts/default/shared-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { MuteButton } from '../../ui/buttons/mute-button';
import { PIPButton } from '../../ui/buttons/pip-button';
import { PlayButton } from '../../ui/buttons/play-button';
import { SeekButton } from '../../ui/buttons/seek-button';
import { Captions } from '../../ui/captions';
import { ChapterTitle } from '../../ui/chapter-title';
import * as Menu from '../../ui/menu';
import * as AudioGainSlider from '../../ui/sliders/audio-gain-slider';
Expand Down Expand Up @@ -301,6 +302,18 @@ function DefaultSeekButton({
DefaultSeekButton.displayName = 'DefaultSeekButton';
export { DefaultSeekButton };

/* -------------------------------------------------------------------------------------------------
* DefaultCaptions
* -----------------------------------------------------------------------------------------------*/

function DefaultCaptions() {
const exampleText = useDefaultLayoutWord('Captions look like this');
return <Captions className="vds-captions" exampleText={exampleText} />;
}

DefaultCaptions.displayName = 'DefaultCaptions';
export { DefaultCaptions };

/* -------------------------------------------------------------------------------------------------
* DefaultVolumeSlider
* -----------------------------------------------------------------------------------------------*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';

import { useMediaState } from '../../../hooks/use-media-state';
import { Captions } from '../../ui/captions';
import * as Controls from '../../ui/controls';
import { Gesture } from '../../ui/gesture';
import * as Spinner from '../../ui/spinner';
Expand All @@ -13,6 +12,7 @@ import { createDefaultMediaLayout, type DefaultLayoutProps } from './media-layou
import {
DefaultAirPlayButton,
DefaultCaptionButton,
DefaultCaptions,
DefaultChaptersMenu,
DefaultChapterTitle,
DefaultControlsSpacer,
Expand All @@ -24,7 +24,6 @@ import {
DefaultSettingsMenu,
DefaultTimeInfo,
DefaultTimeSlider,
DefaultTitle,
DefaultVolumeSlider,
} from './shared-layout';
import {
Expand Down Expand Up @@ -101,7 +100,7 @@ function DefaultVideoLargeLayout() {
/>
) : null}
{slot(slots, 'bufferingIndicator', <DefaultBufferingIndicator />)}
{slot(slots, 'captions', <Captions className="vds-captions" />)}
{slot(slots, 'captions', <DefaultCaptions />)}
<Controls.Root className="vds-controls">
<Controls.Group className="vds-controls-group">
{slot(slots, 'topControlsGroupStart', null)}
Expand Down Expand Up @@ -160,7 +159,7 @@ function DefaultVideoSmallLayout() {
<>
<DefaultVideoGestures />
{slot(slots, 'bufferingIndicator', <DefaultBufferingIndicator />)}
{slot(slots, 'captions', <Captions className="vds-captions" />)}
{slot(slots, 'captions', <DefaultCaptions />)}
<Controls.Root className="vds-controls">
<Controls.Group className="vds-controls-group">
{slot(slots, 'topControlsGroupStart', null)}
Expand Down
9 changes: 8 additions & 1 deletion packages/vidstack/mangle.json
Original file line number Diff line number Diff line change
Expand Up @@ -794,5 +794,12 @@
"_getARIAValueMin": "Pn",
"_onAudioGainChange": "Nn",
"_watchAudioGain": "On",
"_isSliderActive": "Qn"
"_isSliderActive": "Qn",
"_createCueDisplayElement": "Tn",
"_createCueElement": "Un",
"_hideExample": "Vn",
"_hideExampleTimer": "Rn",
"_onFontStyleChange": "Wn",
"_removeExample": "Sn",
"_showExample": "Xn"
}
17 changes: 17 additions & 0 deletions packages/vidstack/player/styles/default/captions.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@
visibility: hidden;
}

.vds-captions[data-example] {
opacity: 1 !important;
visibility: visible !important;
}

:where([data-view-type='video'] .vds-captions [data-part='cue-display'][data-example]) {
--cue-text-align: center;
--cue-width: 100%;
--cue-top: 90%;
--cue-left: 0%;
}

:where([data-view-type='audio'] .vds-captions [data-part='cue-display']) {
--cue-width: 100%;
position: relative !important;
}

/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* VTT Cues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export type DefaultLayoutWord =
| 'Unmute'
| 'Volume'
| 'White'
| 'Yellow';
| 'Yellow'
| 'Captions look like this';

export type DefaultLayoutTranslations = {
[word in DefaultLayoutWord]: string;
Expand Down
1 change: 0 additions & 1 deletion packages/vidstack/src/components/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
type MediaPlayerState,
type MediaStateAccessors,
type MediaStore,
type RemotePlaybackType,
} from '../core';
import { MEDIA_ATTRIBUTES, mediaAttributes } from '../core/api/media-attrs';
import { mediaContext, type MediaContext } from '../core/api/media-context';
Expand Down
85 changes: 80 additions & 5 deletions packages/vidstack/src/components/ui/captions/captions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, effect, peek, scoped } from 'maverick.js';
import { listenEvent } from 'maverick.js/std';
import { listenEvent, setAttribute } from 'maverick.js/std';
import type { CaptionsRenderer } from 'media-captions';

import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
Expand All @@ -14,6 +14,10 @@ export interface CaptionsProps {
* - right-to-left (rtl)
*/
textDir: 'ltr' | 'rtl';
/**
* The text to be displayed when an example caption is being shown.
*/
exampleText: string;
}

/**
Expand All @@ -25,6 +29,7 @@ export interface CaptionsProps {
export class Captions extends Component<CaptionsProps> {
static props: CaptionsProps = {
textDir: 'ltr',
exampleText: 'Captions look like this.',
};

private _media!: MediaContext;
Expand All @@ -44,6 +49,9 @@ export class Captions extends Component<CaptionsProps> {
}

protected override onConnect(el: HTMLElement) {
const player = this._media.player;
if (player) listenEvent(player, 'vds-font-change', this._onFontStyleChange.bind(this));

if (this._renderer) {
effect(this._watchViewType.bind(this));
return;
Expand Down Expand Up @@ -108,15 +116,22 @@ export class Captions extends Component<CaptionsProps> {
private _onCueChange() {
this.el!.textContent = '';

if (this._hideExampleTimer >= 0) {
this._removeExample();
}

const { realCurrentTime, textTrack } = this._media.$state,
{ renderVTTCueString } = this._lib,
time = peek(realCurrentTime),
activeCues = peek(textTrack)!.activeCues;

const { renderVTTCueString } = this._lib;
for (const cue of activeCues) {
const cueEl = document.createElement('div');
cueEl.setAttribute('data-part', 'cue');
const displayEl = this._createCueDisplayElement(),
cueEl = this._createCueElement();

cueEl.innerHTML = renderVTTCueString(cue, time);

displayEl.append(cueEl);
this.el!.append(cueEl);
}
}
Expand Down Expand Up @@ -144,7 +159,67 @@ export class Captions extends Component<CaptionsProps> {

private _watchMediaTime() {
if (this._isHidden()) return;
const { realCurrentTime } = this._media.$state;

const { realCurrentTime, textTrack } = this._media.$state;
this._renderer.currentTime = realCurrentTime();

if (this._hideExampleTimer >= 0 && textTrack()?.activeCues[0]) {
this._removeExample();
}
}

private _onFontStyleChange() {
if (this._hideExampleTimer >= 0) {
this._hideExample();
return;
}

const { textTrack } = this._media.$state;

if (!textTrack()?.activeCues[0]) {
this._showExample();
} else {
this._renderer?.update(true);
}
}

private _showExample() {
const display = this._createCueDisplayElement();
setAttribute(display, 'data-example', '');

const cue = this._createCueElement();
setAttribute(cue, 'data-example', '');
cue.textContent = this.$props.exampleText();

display?.append(cue);
this.el?.append(display);

this.el?.setAttribute('data-example', '');

this._hideExample();
}

private _hideExampleTimer = -1;
private _hideExample() {
window.clearTimeout(this._hideExampleTimer);
this._hideExampleTimer = window.setTimeout(this._removeExample.bind(this), 2500);
}

private _removeExample() {
this.el?.removeAttribute('data-example');
if (this.el?.querySelector('[data-example]')) this.el.textContent = '';
this._hideExampleTimer = -1;
}

private _createCueDisplayElement() {
const el = document.createElement('div');
setAttribute(el, 'data-part', 'cue-display');
return el;
}

private _createCueElement() {
const el = document.createElement('div');
setAttribute(el, 'data-part', 'cue');
return el;
}
}
2 changes: 2 additions & 0 deletions packages/vidstack/src/core/api/player-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface MediaPlayerEvents
'media-player-connect': MediaPlayerConnectEvent;
/* @internal */
'find-media-player': FindMediaPlayerEvent;
/* @internal */
'vds-font-change': Event;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { $signal } from '../../../lit/directives/signal';
import {
DefaultCaptionButton,
DefaultCaptions,
DefaultChaptersMenu,
DefaultChapterTitle,
DefaultControlsSpacer,
Expand All @@ -30,7 +31,7 @@ import {

export function DefaultAudioLayout() {
return [
html`<media-captions class="vds-captions"></media-captions>`,
DefaultCaptions(),
html`
<media-controls class="vds-controls">
<media-controls-group class="vds-controls-group">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ function DefaultFontSettingMenu({

function onReset() {
onValueChange(defaultValue);
notify();
}

function notify() {
player.dispatchEvent(new Event('vds-font-change'));
}

function onOpen() {
Expand All @@ -230,6 +235,7 @@ function DefaultFontSettingMenu({
options: radioOptions,
onChange({ detail: value }) {
onValueChange(value);
notify();
},
})}
</media-menu-items>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ export function DefaultSeekButton({
`;
}

export function DefaultCaptions() {
const { translations } = useDefaultLayoutContext();
return html`
<media-captions
class="vds-captions"
.exampleText=${$i18n(translations, 'Captions look like this')}
></media-captions>
`;
}

export function DefaultVolumeSlider({ orientation }: { orientation?: SliderOrientation } = {}) {
const { translations } = useDefaultLayoutContext(),
$label = $i18n(translations, 'Volume');
Expand Down
Loading

0 comments on commit 8ceae6b

Please sign in to comment.