diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts index 9ffe560f6..5c96080fa 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts @@ -646,6 +646,22 @@ describe('VideoPlayerComponent', () => { expect(dataServiceMock.sendIpcEvent).not.toHaveBeenCalled(); }); + it('routes catch-up playback that resolves to a DASH URL inline as well', () => { + syncStoreState(sampleChannel); + activePlaybackUrl.set('http://localhost/archive/replay.mpd'); + player.set(VideoPlayer.MPV); + + fixture.detectChanges(); + + const playerView = fixture.debugElement.query( + By.directive(StubWebPlayerViewComponent) + ); + expect(playerView).not.toBeNull(); + const stub = + playerView.componentInstance as StubWebPlayerViewComponent; + expect(stub.playerOverride()).toBe(VideoPlayer.Html5Player); + }); + it('keeps ArtPlayer for DASH channels and forwards the ClearKey DRM config', () => { const drm = { licenseType: 'clearkey', diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts index 9d830c3a3..5de7d36c7 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts @@ -24,7 +24,7 @@ import { ResizableDirective } from '@iptvnator/ui/components'; import { applyChannelNameStrip, getM3uArchiveDays, - isDashChannel, + isDashStreamUrl, isM3uCatchupPlaybackSupported, } from '@iptvnator/shared/m3u-utils'; import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util'; @@ -174,9 +174,16 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { readonly archivePlaybackAvailable = computed(() => isM3uCatchupPlaybackSupported(this.activeChannel()) ); - /** DASH (.mpd) channels always play inline via the Shaka engine. */ + /** + * DASH (.mpd) playback always runs inline via the Shaka engine. Uses the + * effective playback URL, so catch-up/archive replays that resolve to a + * DASH manifest route inline too — matching the external-player guard in + * the m3u-state effects, which also checks the resolved URL. + */ readonly activeChannelIsDash = computed(() => - isDashChannel(this.activeChannel()) + isDashStreamUrl( + this.activePlaybackUrl() ?? this.activeChannel()?.url + ) ); /** * Player forced for DASH channels: ArtPlayer keeps ArtPlayer (it has a @@ -1009,9 +1016,10 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { return false; } - // DASH channels bypass the external-player setting (radio precedent): - // MPV/VLC cannot receive the KODIPROP ClearKey configuration. - if (isDashChannel(channel)) { + // DASH playback bypasses the external-player setting (radio + // precedent): MPV/VLC cannot receive the KODIPROP ClearKey + // configuration. Checked on the effective (possibly catch-up) URL. + if (this.activeChannelIsDash()) { return true; } diff --git a/libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts b/libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts index e114896f9..298f054bb 100644 --- a/libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts +++ b/libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts @@ -237,6 +237,9 @@ export function createPlaybackDiagnostic(options: { readonly details?: string; readonly nativeErrorCode?: number; readonly nativeErrorMessage?: string; + /** Overrides the code-derived recommendation, e.g. when external players + * are known to be unable to handle the stream either. */ + readonly externalFallbackRecommended?: boolean; }): PlaybackDiagnostic { const { code, @@ -259,7 +262,9 @@ export function createPlaybackDiagnostic(options: { details: details || undefined, nativeErrorCode, nativeErrorMessage, - externalFallbackRecommended: isExternalFallbackRecommended(code), + externalFallbackRecommended: + options.externalFallbackRecommended ?? + isExternalFallbackRecommended(code), }; } diff --git a/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts b/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts index f78f1dc83..9dbe76aca 100644 --- a/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts +++ b/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts @@ -86,7 +86,7 @@ describe('classifyShakaPlaybackIssue', () => { }); describe('createUnsupportedDrmDiagnostic', () => { - it('creates a DRM diagnostic carrying the license type', () => { + it('creates a DRM diagnostic without recommending unusable external fallbacks', () => { const issue = createUnsupportedDrmDiagnostic( 'com.widevine.alpha', metadata @@ -94,6 +94,8 @@ describe('createUnsupportedDrmDiagnostic', () => { expect(issue.code).toBe(PlaybackDiagnosticCode.DrmOrEncryption); expect(issue.source).toBe(PlaybackDiagnosticSource.Shaka); expect(issue.details).toContain('com.widevine.alpha'); - expect(issue.externalFallbackRecommended).toBe(true); + // MPV/VLC cannot receive KODIPROP license config, so the diagnostic + // must not offer them as a fallback. + expect(issue.externalFallbackRecommended).toBe(false); }); }); diff --git a/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.ts b/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.ts index b4cbec360..71b85b680 100644 --- a/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.ts +++ b/libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.ts @@ -111,6 +111,9 @@ export function createUnsupportedDrmDiagnostic( details: licenseType ? `Unsupported DRM license configuration: ${licenseType}` : 'Unsupported DRM license configuration', + // External MPV/VLC cannot receive the KODIPROP license config either, + // so offering them as a fallback would just fail differently. + externalFallbackRecommended: false, }); }