Skip to content

Commit

Permalink
fix(player): stop expensive updates when not visible
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Mar 11, 2024
1 parent c0b30d7 commit be96bd2
Show file tree
Hide file tree
Showing 20 changed files with 345 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,51 +31,67 @@ function DefaultChaptersMenu({ tooltip, placement, portalClass }: DefaultMediaMe
$src = useMediaState('currentSrc'),
$viewType = useMediaState('viewType'),
$offset = !isSmallLayout && menuGroup === 'bottom' && $viewType === 'video' ? 26 : 0,
$RemotionThumbnail = useSignal(RemotionThumbnail);
$RemotionThumbnail = useSignal(RemotionThumbnail),
[isOpen, setIsOpen] = React.useState(false);

if (disabled) return null;

function onOpen() {
setIsOpen(true);
}

function onClose() {
setIsOpen(false);
}

const Content = (
<Menu.Content
className="vds-chapters-menu-items vds-menu-items"
placement={placement}
offset={$offset}
>
<Menu.RadioGroup
className="vds-chapters-radio-group vds-radio-group"
value={options.selectedValue}
data-thumbnails={thumbnails ? '' : null}
>
{options.map(
({ cue, label, value, startTimeText, durationText, select, setProgressVar }) => (
<Menu.Radio
className="vds-chapter-radio vds-radio"
value={value}
key={value}
onSelect={select}
ref={setProgressVar}
>
{thumbnails ? (
<Thumbnail.Root src={thumbnails} className="vds-thumbnail" time={cue.startTime}>
<Thumbnail.Img />
</Thumbnail.Root>
) : $RemotionThumbnail && isRemotionSource($src) ? (
<$RemotionThumbnail className="vds-thumbnail" frame={cue.startTime * $src.fps!} />
) : null}
<div className="vds-chapter-radio-content">
<span className="vds-chapter-radio-label">{label}</span>
<span className="vds-chapter-radio-start-time">{startTimeText}</span>
<span className="vds-chapter-radio-duration">{durationText}</span>
</div>
</Menu.Radio>
),
)}
</Menu.RadioGroup>
{isOpen ? (
<Menu.RadioGroup
className="vds-chapters-radio-group vds-radio-group"
value={options.selectedValue}
data-thumbnails={thumbnails ? '' : null}
>
{options.map(
({ cue, label, value, startTimeText, durationText, select, setProgressVar }) => (
<Menu.Radio
className="vds-chapter-radio vds-radio"
value={value}
key={value}
onSelect={select}
ref={setProgressVar}
>
{thumbnails ? (
<Thumbnail.Root src={thumbnails} className="vds-thumbnail" time={cue.startTime}>
<Thumbnail.Img />
</Thumbnail.Root>
) : $RemotionThumbnail && isRemotionSource($src) ? (
<$RemotionThumbnail className="vds-thumbnail" frame={cue.startTime * $src.fps!} />
) : null}
<div className="vds-chapter-radio-content">
<span className="vds-chapter-radio-label">{label}</span>
<span className="vds-chapter-radio-start-time">{startTimeText}</span>
<span className="vds-chapter-radio-duration">{durationText}</span>
</div>
</Menu.Radio>
),
)}
</Menu.RadioGroup>
) : null}
</Menu.Content>
);

return (
<Menu.Root className="vds-chapters-menu vds-menu" showDelay={showMenuDelay}>
<Menu.Root
className="vds-chapters-menu vds-menu"
showDelay={showMenuDelay}
onOpen={onOpen}
onClose={onClose}
>
<DefaultTooltip content={chaptersText} placement={tooltip}>
<Menu.Button
className="vds-menu-button vds-button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,43 @@ function DefaultSettingsMenu({ tooltip, placement, portalClass, slots }: Default
} = useDefaultLayoutContext(),
settingsText = useDefaultLayoutWord('Settings'),
$viewType = useMediaState('viewType'),
$offset = !isSmallLayout && menuGroup === 'bottom' && $viewType === 'video' ? 26 : 0;
$offset = !isSmallLayout && menuGroup === 'bottom' && $viewType === 'video' ? 26 : 0,
[isOpen, setIsOpen] = React.useState(false);

function onOpen() {
setIsOpen(true);
}

function onClose() {
setIsOpen(false);
}

const Content = (
<Menu.Content
className="vds-settings-menu-items vds-menu-items"
placement={placement}
offset={$offset}
>
{slot(slots, 'settingsMenuStartItems', null)}
<DefaultPlaybackMenu />
<DefaultAccessibilityMenu />
<DefaultAudioMenu />
<DefaultCaptionMenu />
{slot(slots, 'settingsMenuEndItems', null)}
{isOpen ? (
<>
{slot(slots, 'settingsMenuStartItems', null)}
<DefaultPlaybackMenu />
<DefaultAccessibilityMenu />
<DefaultAudioMenu />
<DefaultCaptionMenu />
{slot(slots, 'settingsMenuEndItems', null)}
</>
) : null}
</Menu.Content>
);

return (
<Menu.Root className="vds-settings-menu vds-menu" showDelay={showMenuDelay}>
<Menu.Root
className="vds-settings-menu vds-menu"
showDelay={showMenuDelay}
onOpen={onOpen}
onClose={onClose}
>
<DefaultTooltip content={settingsText} placement={tooltip}>
<Menu.Button className="vds-menu-button vds-button" aria-label={settingsText}>
<Icons.Menu.Settings className="vds-icon vds-rotate-icon" />
Expand Down
12 changes: 11 additions & 1 deletion packages/vidstack/mangle.json
Original file line number Diff line number Diff line change
Expand Up @@ -822,5 +822,15 @@
"_submenu": "no",
"_watchChapterTitle": "po",
"_watchColorScheme": "qo",
"_watchStep": "ro"
"_watchStep": "ro",
"_onSourceQualitiesChange": "vo",
"_stopQualityResizeEffect": "to",
"_stopWatchingQualityResize": "so",
"_watchQualityResize": "uo",
"_watchSource": "wo",
"_focusActive": "Ao",
"_isSubmenuOpen": "zo",
"_isTransitionActive": "yo",
"_updateFocus": "Bo",
"_wasKeyboardExpand": "xo"
}
20 changes: 10 additions & 10 deletions packages/vidstack/src/components/layouts/plyr/plyr-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@ export class PlyrLayout extends Component<PlyrLayoutProps> {

export function usePlyrLayoutClasses(el: HTMLElement, media: MediaContext) {
const {
fullscreen,
canAirPlay,
canFullscreen,
canPictureInPicture,
pictureInPicture,
controlsHidden,
currentTime,
fullscreen,
hasCaptions,
textTrack,
canAirPlay,
isAirPlayConnected,
viewType,
playing,
paused,
controlsVisible,
pictureInPicture,
playing,
pointer,
waiting,
currentTime,
poster,
textTrack,
viewType,
waiting,
} = media.$state;

el.classList.add('plyr');
Expand All @@ -48,7 +48,7 @@ export function usePlyrLayoutClasses(el: HTMLElement, media: MediaContext) {
'plyr--airplay-supported': canAirPlay,
'plyr--fullscreen-active': fullscreen,
'plyr--fullscreen-enabled': canFullscreen,
'plyr--hide-controls': () => !controlsVisible(),
'plyr--hide-controls': controlsHidden,
'plyr--is-touch': () => pointer() === 'coarse',
'plyr--loading': waiting,
'plyr--paused': paused,
Expand Down
25 changes: 16 additions & 9 deletions packages/vidstack/src/components/ui/menu/menu-focus-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export class MenuFocusController {

_attachMenu(el: HTMLElement) {
listenEvent(el, 'focus', this._onFocus.bind(this));

this._el = el;
onDispose(() => {
this._el = null;
});
return this;
}

_listen() {
Expand Down Expand Up @@ -81,23 +81,30 @@ export class MenuFocusController {
}
}

_focusActive() {
const index = this._findActiveIndex();
this._focusAt(index >= 0 ? index : 0);
}

protected _focusAt(index: number) {
this._index = index;
this._elements[index]?.focus();
this._scroll(index);
if (this._elements[index]) {
this._elements[index].focus();
this._scroll(index);
} else {
this._el?.focus();
}
}

protected _findActiveIndex() {
return this._elements.findIndex((el) => el.getAttribute('aria-checked') === 'true');
return this._elements.findIndex(
(el) => document.activeElement === el || el.getAttribute('aria-checked') === 'true',
);
}

protected _onFocus() {
this._update();
// Timeout to allow size to be updated via transition.
setTimeout(() => {
const index = this._findActiveIndex();
this._focusAt(index >= 0 ? index : 0);
}, 100);
this._focusActive();
}

protected _onKeyUp(event: KeyboardEvent) {
Expand Down
52 changes: 28 additions & 24 deletions packages/vidstack/src/components/ui/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {

protected override onAttach(el: HTMLElement) {
el.style.setProperty('display', 'contents');
this._focus._attachMenu(el);
}

protected override onConnect(el: HTMLElement) {
Expand Down Expand Up @@ -294,7 +293,10 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
this._isTriggerDisabled.set(disabled);
}

private _wasKeyboardExpand = false;
private _onExpandedChange(isExpanded: boolean, event?: Event) {
this._wasKeyboardExpand = isKeyboardEvent(event);

event?.stopPropagation();

if (this._expanded() === isExpanded) return;
Expand Down Expand Up @@ -325,12 +327,9 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
this._toggleMediaControls(event);
tick();

if (isKeyboardEvent(event)) {
if (isExpanded) {
content?.focus();
} else {
trigger?.focus();
}
if (this._wasKeyboardExpand) {
if (isExpanded) content?.focus();
else trigger?.focus();

for (const el of [this.el, content]) {
el && el.setAttribute('data-keyboard', '');
Expand All @@ -352,28 +351,26 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
this._menuObserver?._onOpen?.(event);
} else {
if (this.isSubmenu) {
// A little delay so submenu closing doesn't jump menu size when closing.
setTimeout(() => {
for (const el of this._submenus) el.close(event);
}, 300);
for (const el of this._submenus) el.close(event);
} else {
this._media.activeMenu = null;
}

this._menuObserver?._onClose?.(event);
}

if (isExpanded && !isKeyboardEvent(event)) {
requestAnimationFrame(() => {
this._focus._update();
// Timeout to allow size to be updated via transition.
setTimeout(() => {
this._focus._scroll();
}, 100);
});
if (isExpanded) {
requestAnimationFrame(this._updateFocus.bind(this));
}
}

private _updateFocus() {
if (this._isTransitionActive || this._isSubmenuOpen) return;
this._focus._update();
if (this._wasKeyboardExpand) this._focus._focusActive();
this._focus._scroll();
}

private _isExpanded() {
return !this._isDisabled() && this._expanded();
}
Expand Down Expand Up @@ -404,9 +401,7 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
return;
}

// A little delay so submenu closing doesn't jump menu size when closing.
if (this.isSubmenu) return setTimeout(this.close.bind(this, event), 800);
else this.close(event);
this.close(event);
}

private _getCloseTarget() {
Expand Down Expand Up @@ -453,8 +448,11 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
this._submenus.delete(menu);
}

private _isSubmenuOpen = false;
private _onSubmenuOpenBind = this._onSubmenuOpen.bind(this);
private _onSubmenuOpen(event: MenuOpenEvent) {
this._isSubmenuOpen = true;

const content = this._content();

if (this.isSubmenu) {
Expand Down Expand Up @@ -483,6 +481,8 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {

private _onSubmenuCloseBind = this._onSubmenuClose.bind(this);
private _onSubmenuClose(event: MenuCloseEvent) {
this._isSubmenuOpen = false;

const content = this._content();

if (this.isSubmenu) {
Expand Down Expand Up @@ -533,11 +533,15 @@ export class Menu extends Component<MenuProps, {}, MenuEvents> {
setStyle(content, '--menu-height', height + 'px');
});

protected _isTransitionActive = false;
protected _onResizeTransition(event: TransitionEvent) {
const content = this._content();
if (content && event.propertyName === 'height') {
const hasStarted = event.type === 'transitionstart';
setAttribute(content, 'data-resizing', hasStarted);
this._isTransitionActive = event.type === 'transitionstart';

setAttribute(content, 'data-resizing', this._isTransitionActive);

if (this._expanded()) this._updateFocus();
}
}

Expand Down
Loading

0 comments on commit be96bd2

Please sign in to comment.