fix(playback): avoid redundant VideoJS source resets

This commit is contained in:
4gray committed 2026-05-10 12:07:53 +02:00
1 parent 935ed1b1a9
commit d539de3dda
3 files changed
+154 -8

No files matched your search

+15
View File
@@ -6,6 +6,21 @@
"projectType": "library",
"tags": ["scope:shared", "domain:playback", "type:ui"],
"targets": {
"test": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"command": [
"node",
"./tools/testing/run-web-esm-lib-tests.mjs",
"libs/ui/playback/src"
],
"env": {
"NODE_OPTIONS": "--experimental-vm-modules"
},
"forwardAllArgs": true
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
@@ -0,0 +1,117 @@
import { SimpleChange } from '@angular/core';
import type { VjsPlayerComponent as VjsPlayerComponentInstance } from './vjs-player.component';
const videoJsMock = jest.fn();
const mpegTsIsSupportedMock = jest.fn(() => false);
jest.unstable_mockModule('video.js', () => ({
default: videoJsMock,
}));
jest.unstable_mockModule('@yangkghjh/videojs-aspect-ratio-panel', () => ({}));
jest.unstable_mockModule('videojs-contrib-quality-levels', () => ({}));
jest.unstable_mockModule('videojs-quality-selector-hls', () => ({}));
jest.unstable_mockModule('mpegts.js', () => ({
default: {
createPlayer: jest.fn(),
isSupported: mpegTsIsSupportedMock,
},
}));
describe('VjsPlayerComponent', () => {
let VjsPlayerComponent: typeof import('./vjs-player.component').VjsPlayerComponent;
let component: VjsPlayerComponentInstance;
let player: VjsPlayerComponentInstance['player'];
beforeAll(async () => {
({ VjsPlayerComponent } = await import('./vjs-player.component'));
});
beforeEach(() => {
component = new VjsPlayerComponent();
player = {
src: jest.fn(),
reset: jest.fn(),
volume: jest.fn(),
} as unknown as VjsPlayerComponentInstance['player'];
component.player = player;
});
it('does not reset VideoJS when options change without changing the source', () => {
const previousOptions = {
sources: [
{
src: 'https://example.com/live/playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
const currentOptions = {
sources: [
{
src: 'https://example.com/live/playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
component.ngOnChanges({
options: new SimpleChange(previousOptions, currentOptions, false),
});
expect(player.src).not.toHaveBeenCalled();
});
it('updates VideoJS when options change to a different source', () => {
const previousOptions = {
sources: [
{
src: 'https://example.com/live/playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
const currentOptions = {
sources: [
{
src: 'https://example.com/live/other-playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
component.ngOnChanges({
options: new SimpleChange(previousOptions, currentOptions, false),
});
expect(player.src).toHaveBeenCalledWith(currentOptions.sources[0]);
});
it('updates volume when options change without changing the source', () => {
const previousOptions = {
sources: [
{
src: 'https://example.com/live/playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
const currentOptions = {
sources: [
{
src: 'https://example.com/live/playlist.m3u8',
type: 'application/x-mpegURL',
},
],
};
component.ngOnChanges({
options: new SimpleChange(previousOptions, currentOptions, false),
volume: new SimpleChange(0.5, 0.75, false),
});
expect(player.src).not.toHaveBeenCalled();
expect(player.volume).toHaveBeenCalledWith(0.75);
});
});
@@ -198,14 +198,18 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
*/
ngOnChanges(changes: SimpleChanges): void {
if (changes['options']?.previousValue) {
const newSource = changes['options'].currentValue.sources[0];
if (this.isMpegTsSource(newSource?.src)) {
this.destroyMpegTs();
this.player.reset();
this.initMpegTs(newSource.src);
} else {
this.destroyMpegTs();
this.player.src(newSource);
const previousSource =
changes['options'].previousValue.sources?.[0];
const newSource = changes['options'].currentValue.sources?.[0];
if (newSource && !this.isSameSource(previousSource, newSource)) {
if (this.isMpegTsSource(newSource.src)) {
this.destroyMpegTs();
this.player.reset();
this.initMpegTs(newSource.src);
} else {
this.destroyMpegTs();
this.player.src(newSource);
}
}
}
if (changes['volume']?.currentValue !== undefined && this.player) {
@@ -232,6 +236,16 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
return getExtensionFromUrl(url) === 'ts' && mpegts.isSupported();
}
private isSameSource(
previousSource: VideoPlayerSource | undefined,
newSource: VideoPlayerSource
): boolean {
return (
previousSource?.src === newSource.src &&
previousSource?.type === newSource.type
);
}
private initMpegTs(url: string): void {
const videoEl = this.player.tech({ IWillNotUseThisInPlugins: true })?.el();
if (!videoEl) return;