Skip to content

Commit

Permalink
fix(player): respect playsinline on all touch devices
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Oct 23, 2023
1 parent 75e1113 commit ebc5771
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 43 deletions.
32 changes: 16 additions & 16 deletions packages/vidstack/src/components/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ export class MediaPlayer
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play}
*/
@method
async play() {
return this._requestMgr._play();
async play(trigger?: Event) {
return this._requestMgr._play(trigger);
}

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

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

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

/**
Expand All @@ -609,8 +609,8 @@ export class MediaPlayer
* @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture}
*/
@method
enterPictureInPicture() {
return this._requestMgr._enterPictureInPicture();
enterPictureInPicture(trigger?: Event) {
return this._requestMgr._enterPictureInPicture(trigger);
}

/**
Expand All @@ -620,8 +620,8 @@ export class MediaPlayer
* @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture}
*/
@method
exitPictureInPicture() {
return this._requestMgr._exitPictureInPicture();
exitPictureInPicture(trigger?: Event) {
return this._requestMgr._exitPictureInPicture(trigger);
}

/**
Expand All @@ -631,8 +631,8 @@ export class MediaPlayer
* @see {@link https://vidstack.io/docs/player/core-concepts/live#live-edge}
*/
@method
seekToLiveEdge(): void {
this._requestMgr._seekToLiveEdge();
seekToLiveEdge(trigger?: Event): void {
this._requestMgr._seekToLiveEdge(trigger);
}

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

/**
Expand Down
19 changes: 15 additions & 4 deletions packages/vidstack/src/core/state/media-player-delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,27 @@ export class MediaPlayerDelegate {
}

if ($state.canPlay() && $state.autoplay() && !$state.started()) {
await this._attemptAutoplay();
await this._attemptAutoplay(trigger);
}
}

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

$state.autoplaying.set(true);

const attemptEvent = new DOMEvent('autoplay-attempt', {
detail: { muted: $state.muted() },
trigger,
});

try {
await player.play();
this._dispatch('autoplay', { detail: { muted: $state.muted() } });
await player.play(attemptEvent);

this._dispatch('autoplay', {
detail: { muted: $state.muted() },
trigger: attemptEvent,
});
} catch (error) {
if (__DEV__ && !seenAutoplayWarning) {
const muteMsg = !$state.muted()
Expand All @@ -72,6 +81,7 @@ export class MediaPlayerDelegate {
'Message',
`Autoplay was requested but failed most likely due to browser autoplay policies.${muteMsg}`,
)
.labelledLog('Trigger Event', trigger)
.labelledLog('Error', error)
.labelledLog('See', 'https://developer.chrome.com/blog/autoplay')
.dispatch();
Expand All @@ -84,6 +94,7 @@ export class MediaPlayerDelegate {
muted: $state.muted(),
error: error as Error,
},
trigger: attemptEvent,
});
} finally {
$state.autoplaying.set(false);
Expand Down
132 changes: 110 additions & 22 deletions packages/vidstack/src/core/state/media-request-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { effect, peek, type ReadSignal } from 'maverick.js';
import { isUndefined } from 'maverick.js/std';
import { DOMEvent, isUndefined } from 'maverick.js/std';

import {
FullscreenController,
Expand Down Expand Up @@ -97,13 +97,26 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
if (peek(this._provider)) this[event.type]?.(event);
}

async _play(requestEvent?: Event) {
async _play(trigger?: Event) {
if (__SERVER__) return;

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

if (!peek(paused)) return;

const requestEvent =
trigger?.type === 'media-play-request'
? (trigger as RE.MediaPlayRequestEvent)
: this.createEvent('media-play-request', {
trigger,
});

if (trigger?.type !== 'media-play-request') {
this.dispatchEvent(requestEvent);
}

this._request._queue._enqueue('play', requestEvent);

try {
const provider = peek(this._provider);
throwIfNotReadyForPlayback(provider, peek(canPlay));
Expand All @@ -117,32 +130,50 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
if (__DEV__) {
this._media.logger
?.errorGroup('play request failed')
.labelledLog('Request', requestEvent)
.labelledLog('Trigger', trigger)
.labelledLog('Error', error)
.dispatch();
}

const errorEvent = this.createEvent('play-fail', { detail: coerceToError(error) });
const errorEvent = this.createEvent('play-fail', {
detail: coerceToError(error),
trigger,
});

errorEvent.autoplay = autoplaying();

this._stateMgr._handle(errorEvent);
throw error;
}
}

async _pause() {
async _pause(trigger?: Event) {
if (__SERVER__) return;

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

if (peek(paused)) return;

const requestEvent =
trigger?.type === 'media-pause-request'
? (trigger as RE.MediaPauseRequestEvent)
: this.createEvent('media-pause-request', {
trigger,
});

if (trigger?.type !== 'media-pause-request') {
this.dispatchEvent(requestEvent);
}

this._request._queue._enqueue('pause', requestEvent);

const provider = peek(this._provider);
throwIfNotReadyForPlayback(provider, peek(canPlay));

return provider!.pause();
}

_seekToLiveEdge() {
_seekToLiveEdge(trigger?: Event) {
if (__SERVER__) return;

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

private _wasPIPActive = false;
async _enterFullscreen(target: RE.MediaFullscreenRequestTarget = 'prefer-media') {
async _enterFullscreen(
target: RE.MediaFullscreenRequestTarget = 'prefer-media',
trigger?: Event,
) {
if (__SERVER__) return;
const provider = peek(this._provider);

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

if (peek(this.$state.pictureInPicture)) {
this._wasPIPActive = true;
await this._exitPictureInPicture();
await this._exitPictureInPicture(trigger);
}

const requestEvent =
trigger?.type === 'media-enter-fullscreen-request'
? (trigger as RE.MediaEnterFullscreenRequestEvent)
: this.createEvent('media-enter-fullscreen-request', {
detail: target,
trigger,
});

if (trigger?.type !== 'media-enter-fullscreen-request') {
this.dispatchEvent(requestEvent);
}

this._request._queue._enqueue('fullscreen', requestEvent);

return adapter!.enter();
}

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

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

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

const requestEvent =
trigger?.type === 'media-exit-fullscreen-request'
? (trigger as RE.MediaExitFullscreenRequestEvent)
: this.createEvent('media-exit-fullscreen-request', {
detail: target,
trigger,
});

if (trigger?.type !== 'media-exit-fullscreen-request') {
this.dispatchEvent(requestEvent);
}

try {
this._request._queue._enqueue('fullscreen', requestEvent);

const result = await adapter!.exit();

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

async _enterPictureInPicture() {
async _enterPictureInPicture(trigger?: Event) {
if (__SERVER__) return;

this._throwIfPIPNotSupported();

if (this.$state.pictureInPicture()) return;

const requestEvent =
trigger?.type === 'media-enter-pip-request'
? (trigger as RE.MediaEnterPIPRequestEvent)
: this.createEvent('media-enter-pip-request', {
trigger,
});

if (trigger?.type !== 'media-enter-pip-request') {
this.dispatchEvent(requestEvent);
}

this._request._queue._enqueue('pip', requestEvent);

return await this._provider()!.pictureInPicture!.enter();
}

async _exitPictureInPicture() {
async _exitPictureInPicture(trigger?: Event) {
if (__SERVER__) return;

this._throwIfPIPNotSupported();

if (!this.$state.pictureInPicture()) return;

const requestEvent =
trigger?.type === 'media-exit-pip-request'
? (trigger as RE.MediaExitPIPRequestEvent)
: this.createEvent('media-exit-pip-request', {
trigger,
});

if (trigger?.type !== 'media-exit-pip-request') {
this.dispatchEvent(requestEvent);
}

this._request._queue._enqueue('pip', requestEvent);

return await this._provider()!.pictureInPicture!.exit();
}

Expand Down Expand Up @@ -283,17 +377,15 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR

async ['media-enter-fullscreen-request'](event: RE.MediaEnterFullscreenRequestEvent) {
try {
this._request._queue._enqueue('fullscreen', event);
await this._enterFullscreen(event.detail);
await this._enterFullscreen(event.detail, event);
} catch (error) {
this._onFullscreenError(error, event);
}
}

async ['media-exit-fullscreen-request'](event: RE.MediaExitFullscreenRequestEvent) {
try {
this._request._queue._enqueue('fullscreen', event);
await this._exitFullscreen(event.detail);
await this._exitFullscreen(event.detail, event);
} catch (error) {
this._onFullscreenError(error, event);
}
Expand Down Expand Up @@ -335,17 +427,15 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR

async ['media-enter-pip-request'](event: RE.MediaEnterPIPRequestEvent) {
try {
this._request._queue._enqueue('pip', event);
await this._enterPictureInPicture();
await this._enterPictureInPicture(event);
} catch (error) {
this._onPictureInPictureError(error, event);
}
}

async ['media-exit-pip-request'](event: RE.MediaExitPIPRequestEvent) {
try {
this._request._queue._enqueue('pip', event);
await this._exitPictureInPicture();
await this._exitPictureInPicture(event);
} catch (error) {
this._onPictureInPictureError(error, event);
}
Expand Down Expand Up @@ -394,8 +484,7 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
async ['media-pause-request'](event: RE.MediaPauseRequestEvent) {
if (this.$state.paused()) return;
try {
this._request._queue._enqueue('pause', event);
await this._provider()!.pause();
await this._pause(event);
} catch (error) {
if (__DEV__) {
this._media.logger
Expand All @@ -413,7 +502,6 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
async ['media-play-request'](event: RE.MediaPlayRequestEvent) {
if (!this.$state.paused()) return;
try {
this._request._queue._enqueue('play', event);
await this._play(event);
} catch (e) {
// no-op
Expand Down
Loading

0 comments on commit ebc5771

Please sign in to comment.