Skip to content

Commit eeed0e4

Browse files
committed
fix(player): do not render layouts when ios controls are shown
1 parent 479df95 commit eeed0e4

12 files changed

Lines changed: 52 additions & 64 deletions

File tree

packages/react/src/components/provider.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,10 @@ interface MediaOutletProps extends React.HTMLAttributes<HTMLMediaElement> {
6767
}
6868

6969
function MediaOutlet({ provider, ...props }: MediaOutletProps) {
70-
const { controls, crossOrigin, poster, remotePlaybackInfo } = useStateContext(mediaState),
70+
const { crossOrigin, poster, remotePlaybackInfo, nativeControls } = useStateContext(mediaState),
7171
{ loader } = provider.$state,
72-
{
73-
$iosControls: $$iosControls,
74-
$provider: $$provider,
75-
$providerSetup: $$providerSetup,
76-
} = useMediaContext(),
77-
$controls = useSignal(controls),
78-
$iosControls = useSignal($$iosControls),
79-
$nativeControls = $controls || $iosControls,
72+
{ $provider: $$provider, $providerSetup: $$providerSetup } = useMediaContext(),
73+
$nativeControls = useSignal(nativeControls),
8074
$crossOrigin = useSignal(crossOrigin),
8175
$poster = useSignal(poster),
8276
$loader = useSignal(loader),

packages/vidstack/src/components/player.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ export class MediaPlayer
171171
__DEV__ ? context.logger : undefined,
172172
);
173173
context.remote.setPlayer(this);
174-
context.$iosControls = computed(this._isIOSControls.bind(this));
175174
context.textTracks = new TextTrackList();
176175
context.textTracks[TextTrackSymbol._crossOrigin] = this.$state.crossOrigin;
177176
context.textRenderers = new TextRenderers(context);
@@ -309,7 +308,7 @@ export class MediaPlayer
309308
return !!track && isTrackCaptionKind(track);
310309
},
311310
'data-ios-controls': function (this: MediaPlayer) {
312-
return this._media.$iosControls();
311+
return this.$state.iOSControls();
313312
},
314313
'data-controls': function (this: MediaPlayer) {
315314
return this.controls.showing;
@@ -377,16 +376,6 @@ export class MediaPlayer
377376
this._onResize();
378377
}
379378

380-
private _isIOSControls() {
381-
const { playsInline, fullscreen } = this.$state;
382-
return (
383-
IS_IPHONE &&
384-
!canFullscreen() &&
385-
this.$state.mediaType() === 'video' &&
386-
(!playsInline() || fullscreen())
387-
);
388-
}
389-
390379
/**
391380
* The current media provider.
392381
*/

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,10 @@ export class Captions extends Component<CaptionsProps> {
7979
}
8080

8181
private _isHidden() {
82-
const { textTrack, remotePlaybackState } = this._media.$state,
82+
const { textTrack, remotePlaybackState, iOSControls } = this._media.$state,
8383
track = textTrack();
8484
return (
85-
this._media.$iosControls() ||
86-
remotePlaybackState() === 'connected' ||
87-
!track ||
88-
!isTrackCaptionKind(track)
85+
iOSControls() || remotePlaybackState() === 'connected' || !track || !isTrackCaptionKind(track)
8986
);
9087
}
9188

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,8 @@ export class Controls extends Component<ControlsProps, {}, ControlsEvents> {
5454
private _hideControls() {
5555
if (!this.el) return;
5656

57-
const { $iosControls } = this._media,
58-
{ controls } = this._media.$state,
59-
isHidden = controls() || $iosControls();
57+
const { nativeControls } = this._media.$state,
58+
isHidden = nativeControls();
6059

6160
setAttribute(this.el, 'aria-hidden', isHidden ? 'true' : null);
6261
setStyle(this.el, 'display', isHidden ? 'none' : null);

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,9 @@ export class Poster extends Component<PosterProps, PosterState> {
106106

107107
private _watchHidden() {
108108
const { src } = this.$props,
109-
{ $iosControls } = this._media,
110-
{ poster } = this._media.$state;
111-
112-
this.el && setAttribute(this.el, 'display', $iosControls() ? 'none' : null);
113-
this.$state.hidden.set(this._hasError() || !(src() || poster()) || $iosControls());
109+
{ poster, nativeControls } = this._media.$state;
110+
this.el && setAttribute(this.el, 'display', nativeControls() ? 'none' : null);
111+
this.$state.hidden.set(this._hasError() || !(src() || poster()) || nativeControls());
114112
}
115113

116114
private _isLoading() {

packages/vidstack/src/core/api/media-context.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export interface MediaContext {
3333
logger?: Logger;
3434
$provider: WriteSignal<MediaProviderAdapter | null>;
3535
$providerSetup: WriteSignal<boolean>;
36-
$iosControls: ReadSignal<boolean>;
3736
$props: ReadSignalRecord<MediaPlayerProps>;
3837
$state: PlayerStore;
3938
activeMenu?: {

packages/vidstack/src/core/api/player-state.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import fscreen from 'fscreen';
12
import { State, tick, type Store } from 'maverick.js';
23

34
import type { LogLevel } from '../../foundation/logger/log-level';
45
import type { MediaProviderLoader } from '../../providers/types';
5-
import { canOrientScreen } from '../../utils/support';
6+
import { canOrientScreen, IS_IPHONE } from '../../utils/support';
67
import type { VideoQuality } from '../quality/video-quality';
78
import { getTimeRangesEnd, getTimeRangesStart, TimeRange } from '../time-ranges';
89
import type { AudioTrack } from '../tracks/audio-tracks';
@@ -38,6 +39,16 @@ export const mediaState = new State<MediaState>({
3839
clipStartTime: 0,
3940
clipEndTime: 0,
4041
controls: false,
42+
get iOSControls() {
43+
return (
44+
IS_IPHONE &&
45+
this.mediaType === 'video' &&
46+
(!this.playsInline || (!fscreen.fullscreenEnabled && this.fullscreen))
47+
);
48+
},
49+
get nativeControls() {
50+
return this.controls || this.iOSControls;
51+
},
4152
controlsVisible: false,
4253
get controlsHidden() {
4354
return !this.controlsVisible;
@@ -410,6 +421,15 @@ export interface MediaState {
410421
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/controls}
411422
*/
412423
controls: boolean;
424+
/**
425+
* Whether iOS Safari video controls are visible. This will be true if `playsinline` is not set
426+
* or in fullscreen due to lack of a Fullscreen API.
427+
*/
428+
readonly iOSControls: boolean;
429+
/**
430+
* Whether native controls should be shown due to the `controls` state or `iOSControls` state.
431+
*/
432+
readonly nativeControls: boolean;
413433
/**
414434
* Defines how the media element handles cross-origin requests, thereby enabling the
415435
* configuration of the CORS requests for the element's fetched data.

packages/vidstack/src/core/tracks/text/render/text-renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export class TextRenderers {
2727
}
2828

2929
private _watchControls() {
30-
const { $state, $iosControls } = this._media;
31-
this._nativeDisplay = $state.controls() || $iosControls();
30+
const { nativeControls } = this._media.$state;
31+
this._nativeDisplay = nativeControls();
3232
this._update();
3333
}
3434

packages/vidstack/src/elements/define/layouts/default/video-layout-element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ export class MediaVideoLayoutElement
7676

7777
private _render() {
7878
const { load } = this._media.$props,
79-
{ canLoad, streamType } = this._media.$state;
79+
{ canLoad, streamType, nativeControls } = this._media.$state;
8080

81-
return this.isMatch
81+
return !nativeControls() && this.isMatch
8282
? load() === 'play' && !canLoad()
8383
? DefaultVideoLoadLayout()
8484
: streamType() === 'unknown'

packages/vidstack/src/elements/define/provider-element.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ export class MediaProviderElement extends Host(HTMLElement, MediaProvider) {
6161

6262
if (isEmbed && target) {
6363
effect(() => {
64-
const { $iosControls } = this._media,
65-
{ controls } = this._media.$state,
66-
showControls = controls() || $iosControls();
64+
const { nativeControls } = this._media.$state,
65+
showControls = nativeControls();
6766

6867
if (showControls) {
6968
this._blocker?.remove();
@@ -111,13 +110,12 @@ export class MediaProviderElement extends Host(HTMLElement, MediaProvider) {
111110
const video =
112111
this._target instanceof HTMLVideoElement ? this._target : document.createElement('video');
113112

114-
const { controls, crossOrigin, poster } = this._media.$state,
115-
{ $iosControls } = this._media,
116-
$nativeControls = computed(() => (controls() || $iosControls() ? 'true' : null)),
117-
$poster = computed(() => (poster() && (controls() || $iosControls()) ? poster() : null));
113+
const { crossOrigin, poster, nativeControls } = this._media.$state,
114+
$controls = computed(() => (nativeControls() ? 'true' : null)),
115+
$poster = computed(() => (poster() && nativeControls() ? poster() : null));
118116

119117
effect(() => {
120-
setAttribute(video, 'controls', $nativeControls());
118+
setAttribute(video, 'controls', $controls());
121119
setAttribute(video, 'crossorigin', crossOrigin());
122120
setAttribute(video, 'poster', $poster());
123121
});
@@ -127,13 +125,10 @@ export class MediaProviderElement extends Host(HTMLElement, MediaProvider) {
127125

128126
private _createIFrame() {
129127
const iframe =
130-
this._target instanceof HTMLIFrameElement ? this._target : document.createElement('iframe');
128+
this._target instanceof HTMLIFrameElement ? this._target : document.createElement('iframe'),
129+
{ nativeControls } = this._media.$state;
131130

132-
const { controls } = this._media.$state,
133-
{ $iosControls } = this._media,
134-
$nativeControls = computed(() => controls() || $iosControls());
135-
136-
effect(() => setAttribute(iframe, 'tabindex', !$nativeControls() ? -1 : null));
131+
effect(() => setAttribute(iframe, 'tabindex', !nativeControls() ? -1 : null));
137132

138133
return iframe;
139134
}

0 commit comments

Comments
 (0)