From c6a87aa50700a413010f85480af361e8d693edab Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 10 May 2026 13:12:31 +0200 Subject: [PATCH] fix(playback): clean up cleared VideoJS sources --- .../vjs-player/vjs-player.component.spec.ts | 33 +++++++++++++++++++ .../lib/vjs-player/vjs-player.component.ts | 17 +++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts index 78b08315a..9e48a9506 100644 --- a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts +++ b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts @@ -114,4 +114,37 @@ describe('VjsPlayerComponent', () => { expect(player.src).not.toHaveBeenCalled(); expect(player.volume).toHaveBeenCalledWith(0.75); }); + + it('tears down mpegts playback when options clear the source', () => { + const mpegtsPlayer = { + pause: jest.fn(), + unload: jest.fn(), + detachMediaElement: jest.fn(), + destroy: jest.fn(), + }; + const componentInternals = component as unknown as { + mpegtsPlayer: typeof mpegtsPlayer | null; + }; + componentInternals.mpegtsPlayer = mpegtsPlayer; + const previousOptions = { + sources: [ + { + src: 'https://example.com/live/stream.ts', + type: 'video/mp2t', + }, + ], + }; + + component.ngOnChanges({ + options: new SimpleChange(previousOptions, { sources: [] }, false), + }); + + expect(mpegtsPlayer.pause).toHaveBeenCalled(); + expect(mpegtsPlayer.unload).toHaveBeenCalled(); + expect(mpegtsPlayer.detachMediaElement).toHaveBeenCalled(); + expect(mpegtsPlayer.destroy).toHaveBeenCalled(); + expect(componentInternals.mpegtsPlayer).toBeNull(); + expect(player.reset).toHaveBeenCalled(); + expect(player.src).not.toHaveBeenCalled(); + }); }); diff --git a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts index f14f1f70b..8d868aeda 100644 --- a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts +++ b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts @@ -201,13 +201,14 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy { 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(); + if (this.hasSourceChanged(previousSource, newSource)) { + this.destroyMpegTs(); + if (!newSource) { + this.player.reset(); + } else if (this.isMpegTsSource(newSource.src)) { this.player.reset(); this.initMpegTs(newSource.src); } else { - this.destroyMpegTs(); this.player.src(newSource); } } @@ -236,13 +237,13 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy { return getExtensionFromUrl(url) === 'ts' && mpegts.isSupported(); } - private isSameSource( + private hasSourceChanged( previousSource: VideoPlayerSource | undefined, - newSource: VideoPlayerSource + newSource: VideoPlayerSource | undefined ): boolean { return ( - previousSource?.src === newSource.src && - previousSource?.type === newSource.type + previousSource?.src !== newSource?.src || + previousSource?.type !== newSource?.type ); }