From ea505a6a9ad8af165233b8b416af18cbbed206e3 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 2 Aug 2026 21:25:20 +0200 Subject: [PATCH] fix(portals): keep provider playback for a downloaded movie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once a movie is downloaded the primary button plays the local file, and the sources popover only exists when another playlist carries the same film. The icon-only rework left those as the only two paths, so a downloaded movie in a single-playlist library had no way at all to stream the provider's copy — the labeled action that used to do it was gone. Restores it as an icon button beside the downloaded checkmark, under the same condition the old one used. Co-Authored-By: Claude Fable 5 --- .../vod-details-route.actions.spec.ts | 30 +++++++++++++++++++ .../vod-details-route.component.html | 23 ++++++++++++++ .../vod-multi-source-host.service.spec.ts | 29 ++++++++++++++++++ .../vod-multi-source-host.service.ts | 19 ++++++++++++ 4 files changed, 101 insertions(+) diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.actions.spec.ts b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.actions.spec.ts index 86e962762..250f71fed 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.actions.spec.ts +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.actions.spec.ts @@ -624,6 +624,36 @@ describe('VodDetailsRouteComponent fallback actions', () => { expect(openResolvedPlayback).not.toHaveBeenCalled(); }); + it('still offers provider playback for a downloaded movie with no alternatives', async () => { + // The primary button plays the local file once downloaded, and the + // sources chip only appears when another playlist carries the film — + // so without this control a single-playlist library loses every way + // to stream the provider's copy. + selectedItem.set(sparseItem()); + downloadsAvailable.set(true); + isDownloaded.mockReturnValue(true); + const playPinned = jest + .spyOn(fixture.componentInstance.multiSource, 'playPinnedSource') + .mockResolvedValue('played'); + + fixture.detectChanges(); + + const host = fixture.nativeElement as HTMLElement; + expect(fixture.componentInstance.multiSource.hasAlternatives()).toBe( + false + ); + const providerButton = host.querySelector( + '[data-testid="vod-play-provider"]' + ); + expect(providerButton).not.toBeNull(); + + providerButton?.click(); + await fixture.whenStable(); + + expect(playPinned).toHaveBeenCalled(); + expect(playDownload).not.toHaveBeenCalled(); + }); + it('reveals the finished file from the downloaded state button', async () => { selectedItem.set(sparseItem()); downloadsAvailable.set(true); diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html index 27301423b..536d415e6 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html @@ -109,6 +109,29 @@ > check_circle + + @if (externalPrimaryButtonState() === 'idle') { + + } } @else if (isDownloading()) {