Skip to content

Commit

Permalink
feat(player): new noScrubGesture prop on default layout
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Feb 2, 2024
1 parent d533263 commit 5de514f
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export interface DefaultLayoutProps<Slots = unknown> extends PrimitivePropsWithR
* enabled by default as it provides a better user experience for touch devices.
*/
noModal?: boolean;
/**
* Whether to disable scrubbing by touch swiping left or right on the player canvas.
*/
noScrubGesture: boolean;
/**
* The minimum width of the slider to start displaying slider chapters when available.
*/
Expand Down Expand Up @@ -126,6 +130,7 @@ export function createDefaultMediaLayout({
noGestures = false,
noKeyboardActionDisplay = false,
noModal = false,
noScrubGesture,
seekStep = 10,
showMenuDelay,
showTooltipDelay = 700,
Expand Down Expand Up @@ -160,6 +165,7 @@ export function createDefaultMediaLayout({
className={`vds-${type}-layout` + (className ? ` ${className}` : '')}
data-match={isMatch ? '' : null}
data-size={isSmallLayout ? 'sm' : null}
data-no-scrub-gesture={noScrubGesture ? '' : null}
ref={forwardRef}
>
{canRender && isMatch ? (
Expand All @@ -173,6 +179,7 @@ export function createDefaultMediaLayout({
noGestures,
noKeyboardActionDisplay,
noModal,
noScrubGesture,
showMenuDelay,
showTooltipDelay,
sliderChaptersMinWidth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ function DefaultTimeSlider() {
const [instance, setInstance] = React.useState<TimeSliderInstance | null>(null),
[width, setWidth] = React.useState(0),
$src = useMediaState('currentSrc'),
{ thumbnails, sliderChaptersMinWidth, disableTimeSlider, seekStep } = useDefaultLayoutContext(),
{ thumbnails, sliderChaptersMinWidth, disableTimeSlider, seekStep, noScrubGesture } =
useDefaultLayoutContext(),
label = useDefaultLayoutWord('Seek'),
$RemotionSliderThumbnail = useSignal(RemotionSliderThumbnail);

Expand All @@ -347,6 +348,7 @@ function DefaultTimeSlider() {
className="vds-time-slider vds-slider"
aria-label={label}
disabled={disableTimeSlider}
noSwipeGesture={noScrubGesture}
keyStep={seekStep}
ref={setInstance}
>
Expand Down
28 changes: 14 additions & 14 deletions packages/vidstack/player/styles/default/layouts/video.css
Original file line number Diff line number Diff line change
Expand Up @@ -400,25 +400,25 @@
visibility: hidden;
}

:where([data-preview] .vds-video-layout[data-size='sm'])
:where(
.vds-button,
.vds-slider:not(.vds-time-slider),
.vds-time,
.vds-chapter-title,
.vds-time-divider,
.vds-captions,
.vds-live-button
) {
opacity: 0;
}

:where(.vds-video-layout[data-size='sm'] .vds-time-slider) {
transition: transform 0.1s linear;
}

@media (pointer: coarse) {
:where([data-preview] .vds-video-layout[data-size='sm'] .vds-time-slider) {
:where([data-preview] .vds-video-layout:not([data-no-scrub-gesture]))
:where(
.vds-button,
.vds-slider:not(.vds-time-slider),
.vds-time,
.vds-chapter-title,
.vds-time-divider,
.vds-captions,
.vds-live-button
) {
opacity: 0;
}

:where([data-preview] .vds-video-layout:not([data-no-scrub-gesture]) .vds-time-slider) {
--track-height: var(--video-sm-slider-focus-track-height, 12px);
transform: translateY(-6px);
transition: transform 0.1s linear;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class DefaultLayout extends Component<DefaultLayoutProps> {
this.setAttributes({
'data-match': this._when,
'data-size': () => (this._smallWhen() ? 'sm' : null),
'data-no-scrub-gesture': this.$props.noScrubGesture,
});

const self = this;
Expand Down
19 changes: 12 additions & 7 deletions packages/vidstack/src/components/layouts/default/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import type { ThumbnailSrc } from '../../ui/thumbnails/thumbnail-loader';
import type { DefaultLayoutTranslations } from './translations';

export const defaultLayoutProps: DefaultLayoutProps = {
when: false,
smallWhen: false,
thumbnails: null,
customIcons: false,
translations: null,
menuGroup: 'bottom',
noModal: false,
sliderChaptersMinWidth: 325,
disableTimeSlider: false,
menuGroup: 'bottom',
noGestures: false,
noKeyboardActionDisplay: false,
noModal: false,
noScrubGesture: false,
seekStep: 10,
sliderChaptersMinWidth: 325,
smallWhen: false,
thumbnails: null,
translations: null,
when: false,
};

export interface DefaultLayoutProps {
Expand Down Expand Up @@ -52,6 +53,10 @@ export interface DefaultLayoutProps {
* enabled by default as it provides a better user experience for touch devices.
*/
noModal: boolean;
/**
* Whether to disable scrubbing by touch swiping left or right on the player canvas.
*/
noScrubGesture: boolean;
/**
* The minimum width of the slider to start displaying slider chapters when available.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import throttle from 'just-throttle';
import { effect, ViewController } from 'maverick.js';
import { effect, ViewController, type ReadSignal } from 'maverick.js';
import { isNull, isNumber, isUndefined, listenEvent } from 'maverick.js/std';

import type { MediaContext } from '../../../../core';
import { isTouchPinchEvent } from '../../../../utils/dom';
import { IS_SAFARI } from '../../../../utils/support';
import type {
SliderDragEndEvent,
SliderDragStartEvent,
Expand All @@ -31,7 +30,7 @@ const SliderKeyDirection = {
} as const;

export interface SliderEventDelegate {
_swipeGesture?: boolean;
_swipeGesture?: ReadSignal<boolean>;
_isDisabled(): boolean;
_getStep(): number;
_getKeyStep(): number;
Expand All @@ -57,20 +56,30 @@ export class SliderEventsController extends ViewController<
protected override onConnect() {
effect(this._attachEventListeners.bind(this));
effect(this._attachPointerListeners.bind(this));
if (this._delegate._swipeGesture) {
const provider = this._media.player.el?.querySelector(
'media-provider,[data-media-provider]',
) as HTMLElement | null;
if (provider) {
this._provider = provider;
listenEvent(provider, 'touchstart', this._onTouchStart.bind(this), {
passive: true,
});
listenEvent(provider, 'touchmove', this._onTouchMove.bind(this), {
passive: false,
});
}
if (this._delegate._swipeGesture) effect(this._watchSwipeGesture.bind(this));
}

private _watchSwipeGesture() {
const { pointer } = this._media.$state;

if (pointer() !== 'coarse' || !this._delegate._swipeGesture!()) {
this._provider = null;
return;
}

this._provider = this._media.player.el?.querySelector(
'media-provider,[data-media-provider]',
) as HTMLElement | null;

if (!this._provider) return;

listenEvent(this._provider, 'touchstart', this._onTouchStart.bind(this), {
passive: true,
});

listenEvent(this._provider, 'touchmove', this._onTouchMove.bind(this), {
passive: false,
});
}

private _provider: HTMLElement | null = null;
Expand All @@ -88,12 +97,14 @@ export class SliderEventsController extends ViewController<
yDiff = touch.clientY - this._touch.clientY,
isDragging = this.$state.dragging();

if (!isDragging && Math.abs(yDiff) > 20) {
if (!isDragging && Math.abs(yDiff) > 5) {
return;
}

if (isDragging) return;

event.preventDefault();

if (Math.abs(xDiff) > 20) {
this._touch = touch;
this._touchStartValue = this.$state.value();
Expand All @@ -116,11 +127,9 @@ export class SliderEventsController extends ViewController<
if (this._delegate._isDisabled() || !this.$state.dragging()) return;
listenEvent(document, 'pointerup', this._onDocumentPointerUp.bind(this));
listenEvent(document, 'pointermove', this._onDocumentPointerMove.bind(this));
if (IS_SAFARI) {
listenEvent(document, 'touchmove', this._onDocumentTouchMove.bind(this), {
passive: false,
});
}
listenEvent(document, 'touchmove', this._onDocumentTouchMove.bind(this), {
passive: false,
});
}

private _onFocus() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class TimeSlider extends Component<
keyStep: 5,
shiftKeyMultiplier: 2,
pauseWhileDragging: false,
noSwipeGesture: false,
seekingRequestThrottle: 100,
};

Expand All @@ -71,8 +72,10 @@ export class TimeSlider extends Component<

constructor() {
super();

const { noSwipeGesture } = this.$props;
new SliderController({
_swipeGesture: true,
_swipeGesture: () => !noSwipeGesture(),
_getStep: this._getStep.bind(this),
_getKeyStep: this._getKeyStep.bind(this),
_isDisabled: this._isDisabled.bind(this),
Expand Down Expand Up @@ -291,6 +294,12 @@ export interface TimeSliderProps extends SliderControllerProps {
* The amount of milliseconds to throttle media seeking request events being dispatched.
*/
seekingRequestThrottle: number;
/**
* Whether touch swiping left or right on the player canvas should activate the time slider. This
* gesture makes it easier for touch users to drag anywhere on the player left or right to
* seek backwards or forwards, without directly interacting with time slider.
*/
noSwipeGesture: boolean;
}

interface ThrottledSeeking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,14 @@ export function DefaultVolumeSlider({ orientation }: { orientation?: SliderOrien
export function DefaultTimeSlider() {
const $ref = signal<Element | undefined>(undefined),
$width = signal(0),
{ thumbnails, translations, sliderChaptersMinWidth, disableTimeSlider, seekStep } =
useDefaultLayoutContext(),
{
thumbnails,
translations,
sliderChaptersMinWidth,
disableTimeSlider,
seekStep,
noScrubGesture,
} = useDefaultLayoutContext(),
$label = $i18n(translations, 'Seek'),
$isDisabled = $signal(disableTimeSlider),
$isChaptersDisabled = $signal(() => $width() < sliderChaptersMinWidth()),
Expand All @@ -260,6 +266,7 @@ export function DefaultTimeSlider() {
aria-label=${$label}
key-step=${$signal(seekStep)}
?disabled=${$isDisabled}
?no-swipe-gesture=${$signal(noScrubGesture)}
${ref($ref.set)}
>
<media-slider-chapters class="vds-slider-chapters" ?disabled=${$isChaptersDisabled}>
Expand Down

0 comments on commit 5de514f

Please sign in to comment.