Skip to content

Commit

Permalink
fix(player): always include keyboard status updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Feb 25, 2024
1 parent b63fa16 commit 82ec4a3
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { createComputed } from '../../../hooks/use-signals';
import * as Controls from '../../ui/controls';
import { useLayoutName } from '../utils';
import { i18n, useDefaultLayoutContext } from './context';
import { DefaultKeyboardStatus } from './keyboard-action-display';
import { createDefaultMediaLayout, type DefaultLayoutProps } from './media-layout';
import {
DefaultCaptionButton,
Expand Down Expand Up @@ -112,6 +113,7 @@ function AudioLayout() {
return (
<>
<DefaultCaptions />
<DefaultKeyboardStatus className="vds-sr-only" />
<Controls.Root className="vds-controls">
<Controls.Group className="vds-controls-group">
{slot(slots, 'seekBackwardButton', <DefaultSeekButton backward tooltip="top start" />)}
Expand Down
10 changes: 6 additions & 4 deletions packages/react/src/components/layouts/default/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export * from './icons';
export * from './context';
export * from './ui';
export {
DefaultVideoKeyboardActionDisplay,
type DefaultVideoKeyboardActionDisplayProps,
type DefaultVideoKeyboardActionDisplayWords,
type DefaultVideoKeyboardActionDisplayTranslations,
DefaultKeyboardActionDisplay,
DefaultKeyboardStatus,
type DefaultKeyboardActionDisplayProps,
type DefaultKeyboardActionDisplayWords,
type DefaultKeyboardActionDisplayTranslations,
type DefaultKeyboardStatusProps,
} from './keyboard-action-display';
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { mediaContext, type DefaultLayoutTranslations } from 'vidstack';
import { useMediaState } from '../../../hooks/use-media-state';
import { createComputed, createEffect } from '../../../hooks/use-signals';
import { Primitive, type PrimitivePropsWithRef } from '../../primitives/nodes';
import { i18n } from './context';
import { i18n, useDefaultLayoutContext } from './context';
import type { DefaultKeyboardActionIcons } from './icons';

export type DefaultVideoKeyboardActionDisplayWords =
export type DefaultKeyboardActionDisplayWords =
| 'Play'
| 'Pause'
| 'Enter Fullscreen'
Expand All @@ -21,22 +21,23 @@ export type DefaultVideoKeyboardActionDisplayWords =
| 'Closed-Captions On'
| 'Closed-Captions Off'
| 'Mute'
| 'Volume';
| 'Volume'
| 'Seek Forward'
| 'Seek Backward';

export interface DefaultVideoKeyboardActionDisplayTranslations
extends Pick<DefaultLayoutTranslations, DefaultVideoKeyboardActionDisplayWords> {}
export interface DefaultKeyboardActionDisplayTranslations
extends Pick<DefaultLayoutTranslations, DefaultKeyboardActionDisplayWords> {}

export interface DefaultVideoKeyboardActionDisplayProps
export interface DefaultKeyboardActionDisplayProps
extends Omit<PrimitivePropsWithRef<'div'>, 'disabled'> {
icons?: DefaultKeyboardActionIcons;
noAnimations?: boolean;
translations?: Partial<DefaultVideoKeyboardActionDisplayTranslations> | null;
translations?: Partial<DefaultKeyboardActionDisplayTranslations> | null;
}

const DefaultVideoKeyboardActionDisplay = React.forwardRef<
const DefaultKeyboardActionDisplay = React.forwardRef<
HTMLElement,
DefaultVideoKeyboardActionDisplayProps
>(({ icons: Icons, noAnimations = false, translations, ...props }, forwardRef) => {
DefaultKeyboardActionDisplayProps
>(({ icons: Icons, translations, ...props }, forwardRef) => {
const [visible, setVisible] = React.useState(false),
[Icon, setIcon] = React.useState<any>(null),
[count, setCount] = React.useState(0),
Expand All @@ -60,9 +61,6 @@ const DefaultVideoKeyboardActionDisplay = React.forwardRef<
const $$text = createComputed(getText),
$text = useSignal($$text);

const $$statusLabel = createComputed(() => getStatusLabel(translations!), [translations]),
$statusLabel = useSignal($$statusLabel);

createEffect(() => {
const Icon = getIcon(Icons);
setIcon(() => Icon);
Expand All @@ -82,25 +80,24 @@ const DefaultVideoKeyboardActionDisplay = React.forwardRef<
{...props}
className={className}
data-action={actionDataAttr}
data-animated={!noAnimations ? '' : null}
ref={forwardRef as any}
>
<div className="vds-kb-text-wrapper">
<div className="vds-kb-text">{$text}</div>
</div>
<div className="vds-kb-bezel" role="status" aria-label={$statusLabel} key={count}>
{Icon && !noAnimations ? (
<DefaultKeyboardStatus className="vds-kb-bezel" key={count}>
{Icon ? (
<div className="vds-kb-icon">
<Icon />
</div>
) : null}
</div>
</DefaultKeyboardStatus>
</Primitive.div>
);
});

DefaultVideoKeyboardActionDisplay.displayName = 'DefaultVideoKeyboardActionDisplay';
export { DefaultVideoKeyboardActionDisplay };
DefaultKeyboardActionDisplay.displayName = 'DefaultKeyboardActionDisplay';
export { DefaultKeyboardActionDisplay };

function getText() {
const { $state } = useContext(mediaContext),
Expand Down Expand Up @@ -156,12 +153,53 @@ function getIcon(Icons?: DefaultKeyboardActionIcons) {
}
}

function getStatusLabel(translations?: Partial<DefaultVideoKeyboardActionDisplayTranslations>) {
/* -------------------------------------------------------------------------------------------------
* DefaultKeyboardStatus
* -----------------------------------------------------------------------------------------------*/

export interface DefaultKeyboardStatusProps extends PrimitivePropsWithRef<'div'> {}

const DefaultKeyboardStatus = React.forwardRef<HTMLDivElement, DefaultKeyboardStatusProps>(
({ children, ...props }, forwardRef) => {
const { translations } = useDefaultLayoutContext(),
[isBusy, setIsBusy] = React.useState(false),
$$statusLabel = createComputed(() => getStatusLabel(translations!), [translations]),
$statusLabel = useSignal($$statusLabel);

React.useEffect(() => {
setIsBusy(true);

const id = window.setTimeout(() => {
setIsBusy(false);
}, 150);

return () => window.clearTimeout(id);
}, [$statusLabel]);

return (
<div
role="status"
aria-label={$statusLabel}
aria-live="assertive"
aria-busy={isBusy ? 'true' : undefined}
{...props}
ref={forwardRef}
>
{children}
</div>
);
},
);

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

function getStatusLabel(translations?: Partial<DefaultKeyboardActionDisplayTranslations>) {
const text = getStatusText(translations);
return text ? i18n(translations, text) : null;
}

function getStatusText(translations?: Partial<DefaultVideoKeyboardActionDisplayTranslations>): any {
function getStatusText(translations?: Partial<DefaultKeyboardActionDisplayTranslations>): any {
const { $state } = useContext(mediaContext),
action = $state.lastKeyboardAction()?.action;
switch (action) {
Expand All @@ -179,6 +217,10 @@ function getStatusText(translations?: Partial<DefaultVideoKeyboardActionDisplayT
return $state.muted() || $state.volume() === 0
? 'Mute'
: `${Math.round($state.volume() * ($state.audioGain() ?? 1) * 100)}% ${i18n(translations, 'Volume')}`;
case 'seekForward':
return 'Seek Forward';
case 'seekBackward':
return 'Seek Backward';
default:
return null;
}
Expand Down
25 changes: 12 additions & 13 deletions packages/react/src/components/layouts/default/video-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as Spinner from '../../ui/spinner';
import { Time } from '../../ui/time';
import { useLayoutName } from '../utils';
import { useDefaultLayoutContext } from './context';
import { DefaultVideoKeyboardActionDisplay } from './keyboard-action-display';
import { DefaultKeyboardActionDisplay, DefaultKeyboardStatus } from './keyboard-action-display';
import { createDefaultMediaLayout, type DefaultLayoutProps } from './media-layout';
import {
DefaultAirPlayButton,
Expand Down Expand Up @@ -94,7 +94,7 @@ function DefaultVideoLargeLayout() {
return (
<>
<DefaultVideoGestures />
<DefaultKeyboardActionDisplay />
<DefaultVideoKeyboardActionDisplay />
{slot(slots, 'bufferingIndicator', <DefaultBufferingIndicator />)}
{slot(slots, 'captions', <DefaultCaptions />)}
<Controls.Root className="vds-controls">
Expand Down Expand Up @@ -154,6 +154,7 @@ function DefaultVideoSmallLayout() {
return (
<>
<DefaultVideoGestures />
<DefaultVideoKeyboardActionDisplay />
{slot(slots, 'bufferingIndicator', <DefaultBufferingIndicator />)}
{slot(slots, 'captions', <DefaultCaptions />)}
<Controls.Root className="vds-controls">
Expand Down Expand Up @@ -311,21 +312,19 @@ function DefaultVideoLoadLayout() {
DefaultVideoLoadLayout.displayName = 'DefaultVideoLoadLayout';

/* -------------------------------------------------------------------------------------------------
* DefaultKeyboardActionDisplay
* DefaultVideoKeyboardActionDisplay
* -----------------------------------------------------------------------------------------------*/

function DefaultKeyboardActionDisplay() {
function DefaultVideoKeyboardActionDisplay() {
const { noKeyboardAnimations, icons, translations, userPrefersKeyboardAnimations } =
useDefaultLayoutContext(),
$userPrefersKeyboardAnimations = useSignal(userPrefersKeyboardAnimations);

return (
<DefaultVideoKeyboardActionDisplay
icons={icons.KeyboardAction}
noAnimations={noKeyboardAnimations || !$userPrefersKeyboardAnimations}
translations={translations}
/>
$userPrefersKeyboardAnimations = useSignal(userPrefersKeyboardAnimations),
noAnimations = noKeyboardAnimations || !$userPrefersKeyboardAnimations;
return noAnimations ? (
<DefaultKeyboardStatus className="vds-sr-only" />
) : (
<DefaultKeyboardActionDisplay icons={icons.KeyboardAction} translations={translations} />
);
}

DefaultKeyboardActionDisplay.displayName = 'DefaultKeyboardActionDisplay';
DefaultVideoKeyboardActionDisplay.displayName = 'DefaultVideoKeyboardActionDisplay';
17 changes: 10 additions & 7 deletions packages/vidstack/player/styles/default/keyboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
opacity: 0;
}

:where(.vds-sr-only) {
position: absolute;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}

/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Keyboard Text
Expand Down Expand Up @@ -59,15 +67,10 @@
margin-left: calc(-1 * calc(var(--size) / 2));
margin-right: calc(-1 * calc(var(--size) / 2));
z-index: 20;
opacity: 0;
border-radius: var(--media-kb-bezel-border-radius, calc(var(--size) / 2));
pointer-events: none;
}

:where(.vds-kb-action[data-animated] .vds-kb-bezel) {
opacity: 1;
background: var(--media-kb-bezel-bg, rgba(0, 0, 0, 0.5));
animation: var(--media-kb-bezel-animation, vds-bezel-fade 0.35s linear 1 normal forwards);
border-radius: var(--media-kb-bezel-border-radius, calc(var(--size) / 2));
pointer-events: none;
}

:where(.vds-kb-bezel:has(slot:empty)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useTransitionActive,
} from '../../../../utils/dom';
import { $signal } from '../../../lit/directives/signal';
import { DefaultKeyboardStatus } from './keyboard-action-display';
import {
DefaultCaptionButton,
DefaultCaptions,
Expand All @@ -32,6 +33,7 @@ import {
export function DefaultAudioLayout() {
return [
DefaultCaptions(),
DefaultKeyboardStatus({ className: 'vds-sr-only' }),
html`
<media-controls class="vds-controls">
<media-controls-group class="vds-controls-group">
Expand Down
Loading

0 comments on commit 82ec4a3

Please sign in to comment.