diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts index a84606938..677c90f73 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts @@ -591,6 +591,12 @@ export class UnifiedLiveTabComponent { this.activeDetail.set(null); this.activeTimeshift.set(null); this.isSelecting.set(true); + // A previously owned radio override must not survive into a + // selection that never mounts a player surface of its own — external + // video playback and failed resolutions would otherwise keep the old + // radio credentials installed for that origin. + this.streamHeaders.clear(this.radioHeaderScopeUrl); + this.radioHeaderScopeUrl = null; try { const detail = diff --git a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.spec.ts index 259726302..5f53d3eb7 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.spec.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.spec.ts @@ -1214,6 +1214,35 @@ describe('StalkerLiveStreamLayoutComponent', () => { ); expect(component.activePlayback()).toBeNull(); }); + + it('releases the radio override when the next selection never mounts a player', async () => { + // Switching from a playing radio to a channel whose resolution fails + // (or plays externally) mounts no player surface — the selection + // itself must release the previously installed credentials. + stalkerStore.selectedContentType.set('radio'); + selectedCategoryId.set('radio-all'); + selectedItem.set(null); + selectedItvId.set(undefined); + fixture.detectChanges(); + resolveRadioPlayback.mockResolvedValue({ + streamUrl: 'http://portal.example/radio_2.mpg', + title: 'Portal FM', + headers: { Cookie: 'mac=00:1A:79:00:00:01' }, + }); + await component.playChannel(radioChannels()[0]); + await fixture.whenStable(); + + stalkerStore.selectedContentType.set('itv'); + resolveItvPlayback.mockRejectedValueOnce(new Error('portal down')); + await component.playChannel(itvChannels()[0]); + await fixture.whenStable(); + + expect(window.electron?.setUserAgent).toHaveBeenLastCalledWith( + undefined, + undefined, + 'http://portal.example/radio_2.mpg' + ); + }); }); function buildProgram(channelId: string, title: string): EpgProgram { diff --git a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts index a2f0c470f..56af7e4da 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts @@ -582,6 +582,12 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy { const channelId = normalizeStalkerEntityId(item.id); this.stalkerStore.setSelectedItem(item); this.ensureChannelWithinRenderWindow(channelId); + // A previously owned radio override must not survive into a + // selection that never mounts a player surface of its own — external + // video playback and failed resolutions would otherwise keep the old + // radio credentials installed for that origin. + this.streamHeaders.clear(this.radioHeaderScopeUrl); + this.radioHeaderScopeUrl = null; try { const isRadioMode = this.isRadioMode();