Skip to content

Commit c20a626

Browse files
authored
fix: guard HLS load target and PiP null video (#1859)
Snapshot the HLS loader target before the provider dynamic import so an unmount cannot construct HLSProvider with null. Require a real video element in canUsePictureInPicture, matching #989. Fixes #1771 Signed-off-by: Yi Zhan <stevesough@gmail.com>
1 parent d13db51 commit c20a626

4 files changed

Lines changed: 79 additions & 4 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { HLSProviderLoader } from './loader';
2+
3+
describe('HLSProviderLoader.load', function () {
4+
afterEach(function () {
5+
Reflect.deleteProperty(document, 'pictureInPictureEnabled');
6+
});
7+
8+
it('does not construct the provider with a null target if unmounted during import', async function () {
9+
Object.defineProperty(document, 'pictureInPictureEnabled', {
10+
configurable: true,
11+
value: true,
12+
});
13+
14+
const loader = new HLSProviderLoader(),
15+
video = document.createElement('video'),
16+
ctx = { notify() {} } as any;
17+
18+
loader.target = video;
19+
20+
const load = loader.load(ctx);
21+
loader.target = null as any;
22+
23+
const provider = await load;
24+
25+
expect(provider).to.have.property('type', 'hls');
26+
expect(provider.video).to.equal(video);
27+
});
28+
});

‎packages/vidstack/src/providers/hls/loader.ts‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,22 @@ export class HLSProviderLoader
2222
throw Error('[vidstack] can not load hls provider server-side');
2323
}
2424

25-
if (__DEV__ && !this.target) {
25+
const target = this.target;
26+
27+
if (__DEV__ && !target) {
28+
throw Error(
29+
'[vidstack] `<video>` element was not found - did you forget to include `<media-provider>`?',
30+
);
31+
}
32+
33+
const { HLSProvider } = await import('./provider');
34+
35+
if (!target) {
2636
throw Error(
2737
'[vidstack] `<video>` element was not found - did you forget to include `<media-provider>`?',
2838
);
2939
}
3040

31-
return new (await import('./provider')).HLSProvider(this.target, context);
41+
return new HLSProvider(target, context);
3242
}
3343
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { canUsePictureInPicture } from './support';
2+
3+
describe(canUsePictureInPicture.name, function () {
4+
afterEach(function () {
5+
Reflect.deleteProperty(document, 'pictureInPictureEnabled');
6+
});
7+
8+
it('returns false when video is null even if picture-in-picture is enabled', function () {
9+
Object.defineProperty(document, 'pictureInPictureEnabled', {
10+
configurable: true,
11+
value: true,
12+
});
13+
14+
expect(canUsePictureInPicture(null)).to.equal(false);
15+
});
16+
17+
it('returns true for a video element when picture-in-picture is enabled', function () {
18+
Object.defineProperty(document, 'pictureInPictureEnabled', {
19+
configurable: true,
20+
value: true,
21+
});
22+
23+
expect(canUsePictureInPicture(document.createElement('video'))).to.equal(true);
24+
});
25+
26+
it('returns false when the video has picture-in-picture disabled', function () {
27+
Object.defineProperty(document, 'pictureInPictureEnabled', {
28+
configurable: true,
29+
value: true,
30+
});
31+
32+
const video = document.createElement('video');
33+
video.disablePictureInPicture = true;
34+
35+
expect(canUsePictureInPicture(video)).to.equal(false);
36+
});
37+
});

‎packages/vidstack/src/utils/support.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export function canPlayHLSNatively(video?: HTMLVideoElement | null): boolean {
8383
* @see {@link https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture}
8484
*/
8585
export function canUsePictureInPicture(video: HTMLVideoElement | null): boolean {
86-
if (__SERVER__) return false;
87-
return !!document.pictureInPictureEnabled && !video?.disablePictureInPicture;
86+
if (__SERVER__ || !video) return false;
87+
return !!document.pictureInPictureEnabled && !video.disablePictureInPicture;
8888
}
8989

9090
/**

0 commit comments

Comments
 (0)