Skip to content

Commit ebc5771

Browse files
committed
fix(player): respect playsinline on all touch devices
1 parent 75e1113 commit ebc5771

4 files changed

Lines changed: 146 additions & 43 deletions

File tree

packages/vidstack/src/components/player.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ export class MediaPlayer
564564
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play}
565565
*/
566566
@method
567-
async play() {
568-
return this._requestMgr._play();
567+
async play(trigger?: Event) {
568+
return this._requestMgr._play(trigger);
569569
}
570570

571571
/**
@@ -575,8 +575,8 @@ export class MediaPlayer
575575
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause}
576576
*/
577577
@method
578-
async pause() {
579-
return this._requestMgr._pause();
578+
async pause(trigger?: Event) {
579+
return this._requestMgr._pause(trigger);
580580
}
581581

582582
/**
@@ -586,8 +586,8 @@ export class MediaPlayer
586586
* @see {@link https://vidstack.io/docs/player/core-concepts/fullscreen}
587587
*/
588588
@method
589-
async enterFullscreen(target?: MediaFullscreenRequestTarget) {
590-
return this._requestMgr._enterFullscreen(target);
589+
async enterFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) {
590+
return this._requestMgr._enterFullscreen(target, trigger);
591591
}
592592

593593
/**
@@ -597,8 +597,8 @@ export class MediaPlayer
597597
* @see {@link https://vidstack.io/docs/player/core-concepts/fullscreen}
598598
*/
599599
@method
600-
async exitFullscreen(target?: MediaFullscreenRequestTarget) {
601-
return this._requestMgr._exitFullscreen(target);
600+
async exitFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) {
601+
return this._requestMgr._exitFullscreen(target, trigger);
602602
}
603603

604604
/**
@@ -609,8 +609,8 @@ export class MediaPlayer
609609
* @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture}
610610
*/
611611
@method
612-
enterPictureInPicture() {
613-
return this._requestMgr._enterPictureInPicture();
612+
enterPictureInPicture(trigger?: Event) {
613+
return this._requestMgr._enterPictureInPicture(trigger);
614614
}
615615

616616
/**
@@ -620,8 +620,8 @@ export class MediaPlayer
620620
* @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture}
621621
*/
622622
@method
623-
exitPictureInPicture() {
624-
return this._requestMgr._exitPictureInPicture();
623+
exitPictureInPicture(trigger?: Event) {
624+
return this._requestMgr._exitPictureInPicture(trigger);
625625
}
626626

627627
/**
@@ -631,8 +631,8 @@ export class MediaPlayer
631631
* @see {@link https://vidstack.io/docs/player/core-concepts/live#live-edge}
632632
*/
633633
@method
634-
seekToLiveEdge(): void {
635-
this._requestMgr._seekToLiveEdge();
634+
seekToLiveEdge(trigger?: Event): void {
635+
this._requestMgr._seekToLiveEdge(trigger);
636636
}
637637

638638
/**
@@ -642,8 +642,8 @@ export class MediaPlayer
642642
* @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies}
643643
*/
644644
@method
645-
startLoading(): void {
646-
this._media.delegate._dispatch('can-load');
645+
startLoading(trigger?: Event): void {
646+
this._media.delegate._dispatch('can-load', { trigger });
647647
}
648648

649649
/**

packages/vidstack/src/core/state/media-player-delegate.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,27 @@ export class MediaPlayerDelegate {
4848
}
4949

5050
if ($state.canPlay() && $state.autoplay() && !$state.started()) {
51-
await this._attemptAutoplay();
51+
await this._attemptAutoplay(trigger);
5252
}
5353
}
5454

55-
private async _attemptAutoplay() {
55+
private async _attemptAutoplay(trigger?: Event) {
5656
const { player, $state } = this._media;
5757

5858
$state.autoplaying.set(true);
5959

60+
const attemptEvent = new DOMEvent('autoplay-attempt', {
61+
detail: { muted: $state.muted() },
62+
trigger,
63+
});
64+
6065
try {
61-
await player.play();
62-
this._dispatch('autoplay', { detail: { muted: $state.muted() } });
66+
await player.play(attemptEvent);
67+
68+
this._dispatch('autoplay', {
69+
detail: { muted: $state.muted() },
70+
trigger: attemptEvent,
71+
});
6372
} catch (error) {
6473
if (__DEV__ && !seenAutoplayWarning) {
6574
const muteMsg = !$state.muted()
@@ -72,6 +81,7 @@ export class MediaPlayerDelegate {
7281
'Message',
7382
`Autoplay was requested but failed most likely due to browser autoplay policies.${muteMsg}`,
7483
)
84+
.labelledLog('Trigger Event', trigger)
7585
.labelledLog('Error', error)
7686
.labelledLog('See', 'https://developer.chrome.com/blog/autoplay')
7787
.dispatch();
@@ -84,6 +94,7 @@ export class MediaPlayerDelegate {
8494
muted: $state.muted(),
8595
error: error as Error,
8696
},
97+
trigger: attemptEvent,
8798
});
8899
} finally {
89100
$state.autoplaying.set(false);

packages/vidstack/src/core/state/media-request-manager.ts

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { effect, peek, type ReadSignal } from 'maverick.js';
2-
import { isUndefined } from 'maverick.js/std';
2+
import { DOMEvent, isUndefined } from 'maverick.js/std';
33

44
import {
55
FullscreenController,
@@ -97,13 +97,26 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
9797
if (peek(this._provider)) this[event.type]?.(event);
9898
}
9999

100-
async _play(requestEvent?: Event) {
100+
async _play(trigger?: Event) {
101101
if (__SERVER__) return;
102102

103103
const { canPlay, paused, ended, autoplaying, seekableStart } = this.$state;
104104

105105
if (!peek(paused)) return;
106106

107+
const requestEvent =
108+
trigger?.type === 'media-play-request'
109+
? (trigger as RE.MediaPlayRequestEvent)
110+
: this.createEvent('media-play-request', {
111+
trigger,
112+
});
113+
114+
if (trigger?.type !== 'media-play-request') {
115+
this.dispatchEvent(requestEvent);
116+
}
117+
118+
this._request._queue._enqueue('play', requestEvent);
119+
107120
try {
108121
const provider = peek(this._provider);
109122
throwIfNotReadyForPlayback(provider, peek(canPlay));
@@ -117,32 +130,50 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
117130
if (__DEV__) {
118131
this._media.logger
119132
?.errorGroup('play request failed')
120-
.labelledLog('Request', requestEvent)
133+
.labelledLog('Trigger', trigger)
121134
.labelledLog('Error', error)
122135
.dispatch();
123136
}
124137

125-
const errorEvent = this.createEvent('play-fail', { detail: coerceToError(error) });
138+
const errorEvent = this.createEvent('play-fail', {
139+
detail: coerceToError(error),
140+
trigger,
141+
});
142+
126143
errorEvent.autoplay = autoplaying();
144+
127145
this._stateMgr._handle(errorEvent);
128146
throw error;
129147
}
130148
}
131149

132-
async _pause() {
150+
async _pause(trigger?: Event) {
133151
if (__SERVER__) return;
134152

135153
const { canPlay, paused } = this.$state;
136154

137155
if (peek(paused)) return;
138156

157+
const requestEvent =
158+
trigger?.type === 'media-pause-request'
159+
? (trigger as RE.MediaPauseRequestEvent)
160+
: this.createEvent('media-pause-request', {
161+
trigger,
162+
});
163+
164+
if (trigger?.type !== 'media-pause-request') {
165+
this.dispatchEvent(requestEvent);
166+
}
167+
168+
this._request._queue._enqueue('pause', requestEvent);
169+
139170
const provider = peek(this._provider);
140171
throwIfNotReadyForPlayback(provider, peek(canPlay));
141172

142173
return provider!.pause();
143174
}
144175

145-
_seekToLiveEdge() {
176+
_seekToLiveEdge(trigger?: Event) {
146177
if (__SERVER__) return;
147178

148179
const { canPlay, live, liveEdge, canSeek, liveSyncPosition, seekableEnd, userBehindLiveEdge } =
@@ -159,7 +190,10 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
159190
}
160191

161192
private _wasPIPActive = false;
162-
async _enterFullscreen(target: RE.MediaFullscreenRequestTarget = 'prefer-media') {
193+
async _enterFullscreen(
194+
target: RE.MediaFullscreenRequestTarget = 'prefer-media',
195+
trigger?: Event,
196+
) {
163197
if (__SERVER__) return;
164198
const provider = peek(this._provider);
165199

@@ -174,13 +208,27 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
174208

175209
if (peek(this.$state.pictureInPicture)) {
176210
this._wasPIPActive = true;
177-
await this._exitPictureInPicture();
211+
await this._exitPictureInPicture(trigger);
212+
}
213+
214+
const requestEvent =
215+
trigger?.type === 'media-enter-fullscreen-request'
216+
? (trigger as RE.MediaEnterFullscreenRequestEvent)
217+
: this.createEvent('media-enter-fullscreen-request', {
218+
detail: target,
219+
trigger,
220+
});
221+
222+
if (trigger?.type !== 'media-enter-fullscreen-request') {
223+
this.dispatchEvent(requestEvent);
178224
}
179225

226+
this._request._queue._enqueue('fullscreen', requestEvent);
227+
180228
return adapter!.enter();
181229
}
182230

183-
async _exitFullscreen(target: RE.MediaFullscreenRequestTarget = 'prefer-media') {
231+
async _exitFullscreen(target: RE.MediaFullscreenRequestTarget = 'prefer-media', trigger?: Event) {
184232
if (__SERVER__) return;
185233
const provider = peek(this._provider);
186234

@@ -195,7 +243,21 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
195243

196244
if (this._orientation.locked) await this._orientation.unlock();
197245

246+
const requestEvent =
247+
trigger?.type === 'media-exit-fullscreen-request'
248+
? (trigger as RE.MediaExitFullscreenRequestEvent)
249+
: this.createEvent('media-exit-fullscreen-request', {
250+
detail: target,
251+
trigger,
252+
});
253+
254+
if (trigger?.type !== 'media-exit-fullscreen-request') {
255+
this.dispatchEvent(requestEvent);
256+
}
257+
198258
try {
259+
this._request._queue._enqueue('fullscreen', requestEvent);
260+
199261
const result = await adapter!.exit();
200262

201263
if (this._wasPIPActive && peek(this.$state.canPictureInPicture)) {
@@ -208,17 +270,49 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
208270
}
209271
}
210272

211-
async _enterPictureInPicture() {
273+
async _enterPictureInPicture(trigger?: Event) {
212274
if (__SERVER__) return;
275+
213276
this._throwIfPIPNotSupported();
277+
214278
if (this.$state.pictureInPicture()) return;
279+
280+
const requestEvent =
281+
trigger?.type === 'media-enter-pip-request'
282+
? (trigger as RE.MediaEnterPIPRequestEvent)
283+
: this.createEvent('media-enter-pip-request', {
284+
trigger,
285+
});
286+
287+
if (trigger?.type !== 'media-enter-pip-request') {
288+
this.dispatchEvent(requestEvent);
289+
}
290+
291+
this._request._queue._enqueue('pip', requestEvent);
292+
215293
return await this._provider()!.pictureInPicture!.enter();
216294
}
217295

218-
async _exitPictureInPicture() {
296+
async _exitPictureInPicture(trigger?: Event) {
219297
if (__SERVER__) return;
298+
220299
this._throwIfPIPNotSupported();
300+
221301
if (!this.$state.pictureInPicture()) return;
302+
303+
const requestEvent =
304+
trigger?.type === 'media-exit-pip-request'
305+
? (trigger as RE.MediaExitPIPRequestEvent)
306+
: this.createEvent('media-exit-pip-request', {
307+
trigger,
308+
});
309+
310+
if (trigger?.type !== 'media-exit-pip-request') {
311+
this.dispatchEvent(requestEvent);
312+
}
313+
314+
this._request._queue._enqueue('pip', requestEvent);
315+
222316
return await this._provider()!.pictureInPicture!.exit();
223317
}
224318

@@ -283,17 +377,15 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
283377

284378
async ['media-enter-fullscreen-request'](event: RE.MediaEnterFullscreenRequestEvent) {
285379
try {
286-
this._request._queue._enqueue('fullscreen', event);
287-
await this._enterFullscreen(event.detail);
380+
await this._enterFullscreen(event.detail, event);
288381
} catch (error) {
289382
this._onFullscreenError(error, event);
290383
}
291384
}
292385

293386
async ['media-exit-fullscreen-request'](event: RE.MediaExitFullscreenRequestEvent) {
294387
try {
295-
this._request._queue._enqueue('fullscreen', event);
296-
await this._exitFullscreen(event.detail);
388+
await this._exitFullscreen(event.detail, event);
297389
} catch (error) {
298390
this._onFullscreenError(error, event);
299391
}
@@ -335,17 +427,15 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
335427

336428
async ['media-enter-pip-request'](event: RE.MediaEnterPIPRequestEvent) {
337429
try {
338-
this._request._queue._enqueue('pip', event);
339-
await this._enterPictureInPicture();
430+
await this._enterPictureInPicture(event);
340431
} catch (error) {
341432
this._onPictureInPictureError(error, event);
342433
}
343434
}
344435

345436
async ['media-exit-pip-request'](event: RE.MediaExitPIPRequestEvent) {
346437
try {
347-
this._request._queue._enqueue('pip', event);
348-
await this._exitPictureInPicture();
438+
await this._exitPictureInPicture(event);
349439
} catch (error) {
350440
this._onPictureInPictureError(error, event);
351441
}
@@ -394,8 +484,7 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
394484
async ['media-pause-request'](event: RE.MediaPauseRequestEvent) {
395485
if (this.$state.paused()) return;
396486
try {
397-
this._request._queue._enqueue('pause', event);
398-
await this._provider()!.pause();
487+
await this._pause(event);
399488
} catch (error) {
400489
if (__DEV__) {
401490
this._media.logger
@@ -413,7 +502,6 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
413502
async ['media-play-request'](event: RE.MediaPlayRequestEvent) {
414503
if (!this.$state.paused()) return;
415504
try {
416-
this._request._queue._enqueue('play', event);
417505
await this._play(event);
418506
} catch (e) {
419507
// no-op

0 commit comments

Comments
 (0)