Skip to content

Commit

Permalink
fix(player): replace player query lists with callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Jan 11, 2024
1 parent 67de0e9 commit 3644a74
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 291 deletions.
15 changes: 6 additions & 9 deletions packages/react/src/components/layouts/default/audio-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import * as React from 'react';
import { DefaultAudioLargeLayout } from './audio-layout-large';
import { DefaultAudioSmallLayout } from './audio-layout-small';
import { DefaultLayoutContext } from './context';
import {
createDefaultMediaLayout,
DefaultPlayButton,
type DefaultMediaLayoutProps,
} from './shared-layout';
import { createDefaultMediaLayout, type DefaultMediaLayoutProps } from './media-layout';
import { DefaultPlayButton } from './shared-layout';
import { slot, useDefaultVideoLayoutSlots, type DefaultAudioLayoutSlots } from './slots';

/* -------------------------------------------------------------------------------------------------
Expand All @@ -16,7 +13,7 @@ import { slot, useDefaultVideoLayoutSlots, type DefaultAudioLayoutSlots } from '

const MediaLayout = createDefaultMediaLayout({
type: 'audio',
smLayoutWhen: '(width < 576)',
smLayoutWhen: ({ width }) => width < 576,
LoadLayout: DefaultAudioLoadLayout,
SmallLayout: DefaultAudioSmallLayout,
LargeLayout: DefaultAudioLargeLayout,
Expand All @@ -26,10 +23,10 @@ export interface DefaultAudioLayoutProps extends DefaultMediaLayoutProps<Default

/**
* The audio layout is our production-ready UI that's displayed when the media view type is set to
* 'audio'. It includes support for audio tracks, slider chapters, and captions out of the box. It
* doesn't support live streams just yet.
* 'audio'. It includes support for audio tracks, slider chapters, captions, live streams
* and more out of the box.
*
* @attr data-match - Whether this layout is being used (query match).
* @attr data-match - Whether this layout is being used.
* @attr data-size - The active layout size.
* @example
* ```tsx
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/layouts/default/font-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ function DefaultFontSettingSubmenu({
}, []);

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

resets.all.add(onReset);
return () => {
Expand Down
209 changes: 209 additions & 0 deletions packages/react/src/components/layouts/default/media-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import * as React from 'react';

import { computed } from 'maverick.js';
import { useReactContext, useSignal } from 'maverick.js/react';
import { isBoolean } from 'maverick.js/std';
import {
mediaContext,
type DefaultLayoutTranslations,
type MediaPlayerQueryCallback,
type ThumbnailSrc,
} from 'vidstack';

import { useMediaState } from '../../../hooks/use-media-state';
import type { PrimitivePropsWithRef } from '../../primitives/nodes';
import { DefaultLayoutContext } from './context';
import type { DefaultLayoutIcons } from './icons';

/* -------------------------------------------------------------------------------------------------
* DefaultMediaLayout
* -----------------------------------------------------------------------------------------------*/

export interface DefaultMediaLayoutProps<Slots = unknown> extends PrimitivePropsWithRef<'div'> {
children?: React.ReactNode;
/**
* The icons to be rendered and displayed inside the layout.
*/
icons: DefaultLayoutIcons;
/**
* The thumbnails resource.
*
* @see {@link https://www.vidstack.io/docs/player/core-concepts/loading#thumbnails}
*/
thumbnails?: ThumbnailSrc;
/**
* Translation map from english to your desired language for words used throughout the layout.
*/
translations?: DefaultLayoutTranslations | null;
/**
* Specifies the number of milliseconds to wait before tooltips are visible after interacting
* with a control.
*
* @defaultValue 700
*/
showTooltipDelay?: number;
/**
* Specifies the number of milliseconds to wait before menus are visible after opening them.
*
* @defaultValue 0
*/
showMenuDelay?: number;
/**
* Whether the bitrate should be hidden in the settings quality menu next to each option.
*
* @defaultValue false
*/
hideQualityBitrate?: boolean;
/**
* Determines when the small (e.g., mobile) UI should be displayed.
*
* @defaultValue `({ width, height }) => width < 576 || height < 380`
*/
smallLayoutWhen?: boolean | MediaPlayerQueryCallback;
/**
* Specifies whether menu buttons should be placed in the top or bottom controls group. This
* only applies to the large video layout.
*
* @defaultValue 'bottom'
*/
menuGroup?: 'top' | 'bottom';
/**
* Whether modal menus should be disabled when the small layout is active. A modal menu is
* a floating panel that floats up from the bottom of the screen (outside of the player). It's
* enabled by default as it provides a better user experience for touch devices.
*
* @defaultValue false
*/
noModal?: boolean;
/**
* Provide additional content to be inserted in specific positions.
*/
slots?: Slots;
/**
* The minimum width to start displaying slider chapters when available.
*
* @defaultValue 600
*/
sliderChaptersMinWidth?: number;
/**
* Whether the time slider should be disabled.
*/
disableTimeSlider?: boolean;
/**
* Whether all gestures such as pressing to play or seek should not be active.
*/
noGestures?: boolean;
/**
* Whether keyboard actions should not be displayed.
*/
noKeyboardActionDisplay?: boolean;
}

export interface CreateDefaultMediaLayout {
type: 'audio' | 'video';
smLayoutWhen: MediaPlayerQueryCallback;
LoadLayout: React.FC;
SmallLayout: React.FC;
LargeLayout: React.FC;
UnknownStreamType?: React.FC;
}

export function createDefaultMediaLayout({
type,
smLayoutWhen,
LoadLayout,
SmallLayout,
LargeLayout,
UnknownStreamType,
}: CreateDefaultMediaLayout) {
const Layout = React.forwardRef<HTMLDivElement, DefaultMediaLayoutProps>(
(
{
className,
icons,
thumbnails = null,
translations,
showMenuDelay,
showTooltipDelay = type === 'video' ? 500 : 700,
smallLayoutWhen = smLayoutWhen,
noModal = false,
menuGroup = 'bottom',
hideQualityBitrate = false,
sliderChaptersMinWidth = 600,
disableTimeSlider = false,
noGestures = false,
noKeyboardActionDisplay = false,
slots,
children,
...props
},
forwardRef,
) => {
const media = useReactContext(mediaContext)!,
$load = useSignal(media.$props.load),
$canLoad = useMediaState('canLoad'),
$viewType = useMediaState('viewType'),
$streamType = useMediaState('streamType'),
$smallWhen = React.useMemo(() => {
return computed(() =>
isBoolean(smallLayoutWhen) ? smallLayoutWhen : smallLayoutWhen(media.player.state),
);
}, [smallLayoutWhen]),
isMatch = $viewType === type,
isSmallLayout = $smallWhen(),
isForcedLayout = isBoolean(smallLayoutWhen),
isLoadLayout = $load === 'play' && !$canLoad,
canRender = $canLoad || isForcedLayout || isLoadLayout;

useSignal($smallWhen);

return (
<div
{...props}
className={`vds-${type}-layout` + (className ? ` ${className}` : '')}
data-match={isMatch ? '' : null}
data-size={isSmallLayout ? 'sm' : null}
ref={forwardRef}
>
{}
{canRender && isMatch ? (
<DefaultLayoutContext.Provider
value={{
disableTimeSlider,
hideQualityBitrate,
Icons: icons,
isSmallLayout,
menuGroup,
noGestures,
noKeyboardActionDisplay,
noModal,
showMenuDelay,
showTooltipDelay,
sliderChaptersMinWidth,
slots,
thumbnails,
translations,
}}
>
{isLoadLayout ? (
<LoadLayout />
) : $streamType === 'unknown' ? (
UnknownStreamType ? (
<UnknownStreamType />
) : null
) : isSmallLayout ? (
<SmallLayout />
) : (
<LargeLayout />
)}
{children}
</DefaultLayoutContext.Provider>
) : null}
</div>
);
},
);

Layout.displayName = 'DefaultMediaLayout';
return Layout;
}
Loading

0 comments on commit 3644a74

Please sign in to comment.