Skip to content

Commit 671cdaf

Browse files
committed
fix(player): scroll and zoom gestures not working on iphone
closes #987
1 parent dbf7785 commit 671cdaf

5 files changed

Lines changed: 50 additions & 22 deletions

File tree

packages/vidstack/src/components/provider/provider.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Component, method, onDispose, peek, signal, State, tick } from 'maverick.js';
2-
import { animationFrameThrottle, listenEvent, setStyle } from 'maverick.js/std';
2+
import { animationFrameThrottle, setStyle } from 'maverick.js/std';
33
import type { CaptionsFileFormat } from 'media-captions';
44

55
import type { MediaSrc, TextTrackInit } from '../../core';
66
import { useMediaContext, type MediaContext } from '../../core/api/media-context';
77
import type { MediaProviderLoader } from '../../providers';
8-
import { IS_SAFARI } from '../../utils/support';
98
import { SourceSelection } from './source-select';
109
import { Tracks } from './tracks';
1110

@@ -51,11 +50,6 @@ export class MediaProvider extends Component<MediaProviderProps, MediaProviderSt
5150
const mutation = new MutationObserver(this._onMutation.bind(this));
5251
mutation.observe(el, { attributes: true, childList: true });
5352

54-
if (IS_SAFARI) {
55-
// Prevent delay on pointer/click events.
56-
listenEvent(el, 'touchstart', (e) => e.preventDefault(), { passive: false });
57-
}
58-
5953
this._onResize();
6054
this._onMutation();
6155

packages/vidstack/src/components/ui/gesture.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from 'maverick.js/std';
1010

1111
import { useMediaContext, type MediaContext } from '../../core/api/media-context';
12+
import { isTouchPinchEvent } from '../../utils/dom';
1213

1314
/**
1415
* This component enables actions to be performed on the media based on user gestures.
@@ -54,10 +55,18 @@ export class Gesture extends Component<GestureProps, {}, GestureEvents> {
5455
eventType = eventType.split(/^dbl/)[1] as keyof HTMLElementEventMap;
5556
}
5657

58+
if (eventType === 'pointerup' || eventType === 'pointerdown') {
59+
const pointer = this._media.$state.pointer();
60+
if (pointer === 'coarse') {
61+
eventType = eventType === 'pointerup' ? 'touchend' : 'touchstart';
62+
}
63+
}
64+
5765
listenEvent(
5866
this._provider,
5967
eventType as keyof HTMLElementEventMap,
6068
this._acceptEvent.bind(this),
69+
{ passive: false },
6170
);
6271
}
6372

@@ -66,8 +75,10 @@ export class Gesture extends Component<GestureProps, {}, GestureEvents> {
6675

6776
private _acceptEvent(event: Event) {
6877
if (
69-
!this._inBounds(event) ||
70-
(isPointerEvent(event) && (event.button !== 0 || this._media.activeMenu))
78+
(isPointerEvent(event) && (event.button !== 0 || this._media.activeMenu)) ||
79+
(isTouchEvent(event) && this._media.activeMenu) ||
80+
isTouchPinchEvent(event) ||
81+
!this._inBounds(event)
7182
) {
7283
return;
7384
}
@@ -76,7 +87,9 @@ export class Gesture extends Component<GestureProps, {}, GestureEvents> {
7687
event.MEDIA_GESTURE = true;
7788
event.preventDefault();
7889

79-
const isDblEvent = peek(this.$props.event)?.startsWith('dbl');
90+
const eventType = peek(this.$props.event),
91+
isDblEvent = eventType?.startsWith('dbl');
92+
8093
if (!isDblEvent) {
8194
if (this._presses === 0) {
8295
setTimeout(() => {
@@ -116,7 +129,7 @@ export class Gesture extends Component<GestureProps, {}, GestureEvents> {
116129
if (!this.el) return false;
117130

118131
if (isPointerEvent(event) || isMouseEvent(event) || isTouchEvent(event)) {
119-
const touch = isTouchEvent(event) ? event.touches[0] : undefined;
132+
const touch = isTouchEvent(event) ? event.changedTouches[0] ?? event.touches[0] : undefined;
120133

121134
const clientX = touch?.clientX ?? (event as MouseEvent).clientX;
122135
const clientY = touch?.clientY ?? (event as MouseEvent).clientY;

packages/vidstack/src/components/ui/sliders/slider/events-controller.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from 'maverick.js/std';
1212

1313
import type { MediaContext } from '../../../../core';
14+
import { isTouchPinchEvent } from '../../../../utils/dom';
1415
import { IS_SAFARI } from '../../../../utils/support';
1516
import type {
1617
SliderDragEndEvent,
@@ -88,7 +89,7 @@ export class SliderEventsController extends ViewController<
8889
}
8990

9091
private _onTouchMove(event: TouchEvent) {
91-
if (isNull(this._touch)) return;
92+
if (isNull(this._touch) || isTouchPinchEvent(event)) return;
9293

9394
const touch = event.touches[0],
9495
xDiff = touch.clientX - this._touch.clientX,
@@ -99,8 +100,6 @@ export class SliderEventsController extends ViewController<
99100
return;
100101
}
101102

102-
event.preventDefault();
103-
104103
if (isDragging) return;
105104

106105
if (Math.abs(xDiff) > 20) {

packages/vidstack/src/core/controls.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { effect } from 'maverick.js';
2-
import { isKeyboardEvent, listenEvent } from 'maverick.js/std';
2+
import { isKeyboardEvent, isTouchEvent, listenEvent } from 'maverick.js/std';
33

4+
import { isTouchPinchEvent } from '../utils/dom';
45
import { MediaPlayerController } from './api/player-controller';
56

67
export class MediaControls extends MediaPlayerController {
@@ -76,12 +77,17 @@ export class MediaControls extends MediaPlayerController {
7677
if (paused() || (autoplayError() && !started())) return;
7778

7879
const onStopIdle = this._onStopIdle.bind(this);
79-
for (const eventType of ['pointerup', 'keydown'] as const) {
80-
listenEvent(this.el!, eventType, onStopIdle);
81-
}
8280

8381
effect(() => {
84-
if (this.$state.pointer() === 'fine') {
82+
const pointer = this.$state.pointer(),
83+
isTouch = pointer === 'coarse',
84+
events = [isTouch ? 'touchend' : 'pointerup', 'keydown'] as const;
85+
86+
for (const eventType of events) {
87+
listenEvent(this.el!, eventType, onStopIdle, { passive: false });
88+
}
89+
90+
if (!isTouch) {
8591
listenEvent(this.el!, 'pointermove', onStopIdle);
8692
}
8793
});
@@ -102,8 +108,14 @@ export class MediaControls extends MediaPlayerController {
102108
}
103109

104110
private _onStopIdle(event: Event) {
105-
// @ts-expect-error
106-
if (event.MEDIA_GESTURE || this._pausedTracking) return;
111+
if (
112+
// @ts-expect-error
113+
event.MEDIA_GESTURE ||
114+
this._pausedTracking ||
115+
isTouchPinchEvent(event)
116+
) {
117+
return;
118+
}
107119

108120
if (isKeyboardEvent(event)) {
109121
if (event.key === 'Escape') {

packages/vidstack/src/utils/dom.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import {
55
type Placement,
66
} from '@floating-ui/dom';
77
import { effect, getScope, onDispose, scoped } from 'maverick.js';
8-
import { isKeyboardClick, listenEvent, setAttribute, setStyle } from 'maverick.js/std';
8+
import {
9+
isKeyboardClick,
10+
isTouchEvent,
11+
listenEvent,
12+
setAttribute,
13+
setStyle,
14+
} from 'maverick.js/std';
915

1016
export function setAttributeIfEmpty(target: Element, name: string, value: string) {
1117
if (!target.hasAttribute(name)) target.setAttribute(name, value);
@@ -52,6 +58,10 @@ export function onPress(
5258
});
5359
}
5460

61+
export function isTouchPinchEvent(event: Event) {
62+
return isTouchEvent(event) && (event.touches.length > 1 || event.changedTouches.length > 1);
63+
}
64+
5565
export function requestScopedAnimationFrame(callback: () => void) {
5666
if (__SERVER__) return callback();
5767

0 commit comments

Comments
 (0)