diff --git a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.spec.ts b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.spec.ts index 3c2477389..4d79e1461 100644 --- a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.spec.ts +++ b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.spec.ts @@ -224,6 +224,35 @@ describe('HtmlVideoPlayerComponent', () => { ); }); + it('does not resume the previous source when a channel declares unsupported DRM', () => { + const video = component.videoPlayer.nativeElement; + const loadSpy = jest + .spyOn(video, 'load') + .mockImplementation(() => undefined); + const playSpy = jest.spyOn(video, 'play').mockResolvedValue(undefined); + + component.playChannel({ + ...TEST_CHANNEL, + url: 'https://stream.example/movie.mp4', + }); + playSpy.mockClear(); + loadSpy.mockClear(); + + component.playChannel({ + ...TEST_CHANNEL, + url: 'https://stream.example/enc.mpd', + drm: { + licenseType: 'com.widevine.alpha', + supported: false, + }, + }); + + // No source is loaded for unsupported DRM: play() must not run, and + // the element is reset so the old stream cannot keep playing. + expect(playSpy).not.toHaveBeenCalled(); + expect(loadSpy).toHaveBeenCalledTimes(1); + }); + it('emits a playback issue when the native video element reports an unsupported source', () => { const issues: unknown[] = []; component.channel = TEST_CHANNEL; diff --git a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts index 8643513a7..815f9881c 100644 --- a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts +++ b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts @@ -197,7 +197,14 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy { url, channel.drm ); - this.handlePlayOperation(); + if (channel.drm && !channel.drm.supported) { + // No source is loaded for unsupported DRM; reset the + // element so the previous stream cannot resume playing + // underneath the diagnostic banner. + this.videoPlayer.nativeElement.load(); + } else { + this.handlePlayOperation(); + } } else if ( (extension === 'ts' || !extension) && mpegts.isSupported() diff --git a/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts b/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts index c30988adf..e0c1b1379 100644 --- a/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts +++ b/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts @@ -114,7 +114,7 @@ describe('ShakaVideoSession', () => { expect(session.getPlayer()).toBeNull(); }); - it('drops the external-fallback hint for DRM errors on ClearKey channels', async () => { + it('drops the external-fallback hint for every failure on ClearKey channels', async () => { const environment = createFakeShakaEnvironment(); const session = createSession(environment); session.start(video, 'http://example.com/enc.mpd', { @@ -135,6 +135,24 @@ describe('ShakaVideoSession', () => { // MPV/VLC never receive the KODIPROP license config, so they are not // offered as a fallback for key failures on ClearKey channels. expect(issues[0].externalFallbackRecommended).toBe(false); + + // Non-DRM failures (media/codec/manifest/…) are equally unsolvable + // externally while the stream itself stays encrypted. + session.start(video, 'http://example.com/enc.mpd', { + licenseType: 'clearkey', + supported: true, + clearKeys: { abc: 'def' }, + }); + await flush(); + environment.instances[1].dispatch('error', { + severity: 2, + category: 3, + code: 3016, + }); + + expect(issues).toHaveLength(2); + expect(issues[1].code).toBe(PlaybackDiagnosticCode.MediaDecodeError); + expect(issues[1].externalFallbackRecommended).toBe(false); }); it('emits a diagnostic and tears the engine down when load rejects', async () => { diff --git a/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.ts b/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.ts index 8fc365f22..c7696023b 100644 --- a/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.ts +++ b/libs/ui/playback/src/lib/shaka-engine/shaka-video-session.ts @@ -228,15 +228,16 @@ export class ShakaVideoSession { } /** - * For channels carrying KODIPROP ClearKey config, DRM failures (wrong or - * rotated keys, …) cannot be solved by MPV/VLC either — they never - * receive the license config — so the diagnostic must not offer them. + * Channels carrying KODIPROP ClearKey config cannot be handed to MPV/VLC + * at all — external players never receive the license config, so the + * encrypted stream fails there regardless of what broke inline (DRM, + * manifest, codec, network, …). Suppress the fallback hint entirely. */ private withoutUnusableDrmFallback( issue: PlaybackDiagnostic, drmProvided: boolean ): PlaybackDiagnostic { - if (!drmProvided || issue.code !== DiagnosticCode.DrmOrEncryption) { + if (!drmProvided || !issue.externalFallbackRecommended) { return issue; } return { ...issue, externalFallbackRecommended: false };