refactor(playback): use signal APIs in VideoJS player

This commit is contained in:
4gray committed 2026-05-10 13:18:09 +02:00
1 parent c6a87aa507
commit c53799e2bc
2 files changed
+39 -19

No files matched your search

@@ -1,4 +1,5 @@
import { SimpleChange } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import type { VjsPlayerComponent as VjsPlayerComponentInstance } from './vjs-player.component';
const videoJsMock = jest.fn();
@@ -23,21 +24,41 @@ describe('VjsPlayerComponent', () => {
let VjsPlayerComponent: typeof import('./vjs-player.component').VjsPlayerComponent;
let component: VjsPlayerComponentInstance;
let player: VjsPlayerComponentInstance['player'];
type SignalApiShape = {
options: () => unknown;
volume: () => number;
startTime: () => number;
timeUpdate: { emit: (event: unknown) => void };
};
beforeAll(async () => {
({ VjsPlayerComponent } = await import('./vjs-player.component'));
});
beforeEach(() => {
component = new VjsPlayerComponent();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VjsPlayerComponent],
}).compileComponents();
component = TestBed.createComponent(VjsPlayerComponent).componentInstance;
player = {
src: jest.fn(),
reset: jest.fn(),
volume: jest.fn(),
dispose: jest.fn(),
} as unknown as VjsPlayerComponentInstance['player'];
component.player = player;
});
it('uses signal-based inputs and outputs', () => {
const signalComponent = component as unknown as SignalApiShape;
expect(typeof signalComponent.options).toBe('function');
expect(signalComponent.volume()).toBe(1);
expect(signalComponent.startTime()).toBe(0);
expect(typeof signalComponent.timeUpdate.emit).toBe('function');
});
it('does not reset VideoJS when options change without changing the source', () => {
const previousOptions = {
sources: [
@@ -1,15 +1,14 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
ViewChild,
ViewEncapsulation,
input,
output,
viewChild,
} from '@angular/core';
import '@yangkghjh/videojs-aspect-ratio-panel';
import { getExtensionFromUrl } from 'm3u-utils';
@@ -88,16 +87,16 @@ type VideoJsPlayer = Omit<
})
export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
/** DOM-element reference */
@ViewChild('target', { static: true }) target!: ElementRef<Element>;
readonly target = viewChild.required<ElementRef<Element>>('target');
/** Options of VideoJs player */
@Input() options!: VideoPlayerOptions;
readonly options = input.required<VideoPlayerOptions>();
/** VideoJs object */
player!: VideoJsPlayer;
/** mpegts.js player for raw MPEG-TS streams */
private mpegtsPlayer: mpegts.Player | null = null;
@Input() volume = 1;
@Input() startTime = 0;
@Output() timeUpdate = new EventEmitter<{
readonly volume = input(1);
readonly startTime = input(0);
readonly timeUpdate = output<{
currentTime: number;
duration: number;
}>();
@@ -106,27 +105,27 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
* Instantiate Video.js on component init
*/
ngOnInit(): void {
const source = this.options?.sources?.[0];
const source = this.options().sources?.[0];
const isMpegTs = this.isMpegTsSource(source?.src);
// For raw MPEG-TS streams, init Video.js without a source (UI/controls only)
const vjsOptions = isMpegTs
? { ...this.options, sources: [], autoplay: false }
: { ...this.options, autoplay: true };
? { ...this.options(), sources: [], autoplay: false }
: { ...this.options(), autoplay: true };
this.player = videoJs(
this.target.nativeElement,
this.target().nativeElement,
vjsOptions,
() => {
console.log(
'Setting VideoJS player initial volume to:',
this.volume
this.volume()
);
this.player.volume(this.volume);
this.player.volume(this.volume());
this.player.on('loadedmetadata', () => {
if (this.startTime > 0) {
this.player.currentTime(this.startTime);
if (this.startTime() > 0) {
this.player.currentTime(this.startTime());
}
this.logAudioTracks();
this.setupAudioTrackMenu();