diff --git a/apps/web/src/app/app.component.spec.ts b/apps/web/src/app/app.component.spec.ts
index 37a8dc3d2..81150dc9e 100644
--- a/apps/web/src/app/app.component.spec.ts
+++ b/apps/web/src/app/app.component.spec.ts
@@ -36,6 +36,7 @@ const DEFAULT_SETTINGS: Settings = {
player: VideoPlayer.VideoJs,
epgUrl: [],
streamFormat: StreamFormat.M3u8StreamFormat,
+ openStreamOnDoubleClick: false,
language: Language.ENGLISH,
showCaptions: false,
showDashboard: true,
diff --git a/apps/web/src/app/settings/settings-playback-section.component.html b/apps/web/src/app/settings/settings-playback-section.component.html
index a29c1d4d2..f354c5da4 100644
--- a/apps/web/src/app/settings/settings-playback-section.component.html
+++ b/apps/web/src/app/settings/settings-playback-section.component.html
@@ -69,6 +69,21 @@
+
diff --git a/apps/web/src/app/settings/settings.component.spec.ts b/apps/web/src/app/settings/settings.component.spec.ts
index 7301de1e4..437dea3b0 100644
--- a/apps/web/src/app/settings/settings.component.spec.ts
+++ b/apps/web/src/app/settings/settings.component.spec.ts
@@ -68,6 +68,7 @@ export class MockRouter {
const DEFAULT_SETTINGS = {
player: VideoPlayer.VideoJs,
streamFormat: StreamFormat.M3u8StreamFormat,
+ openStreamOnDoubleClick: false,
language: Language.ENGLISH,
showCaptions: false,
showDashboard: true,
diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts
index 435032340..35dc758cf 100644
--- a/apps/web/src/app/settings/settings.component.ts
+++ b/apps/web/src/app/settings/settings.component.ts
@@ -179,6 +179,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
player: [VideoPlayer.VideoJs],
...(this.isDesktop ? { epgUrl: new FormArray([]) } : {}),
streamFormat: StreamFormat.M3u8StreamFormat,
+ openStreamOnDoubleClick: false,
language: Language.ENGLISH,
showCaptions: false,
showDashboard: true,
diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json
index 3b0e3bc14..09d4e2665 100644
--- a/apps/web/src/assets/i18n/en.json
+++ b/apps/web/src/assets/i18n/en.json
@@ -244,6 +244,8 @@
"NEW_VERSION_AVAILABLE": "There is a new version available",
"LATEST_VERSION": "You are using the latest version",
"STREAM_FORMAT": "Stream Format",
+ "OPEN_STREAM_ON_DOUBLE_CLICK": "Open streams with double-click",
+ "OPEN_STREAM_ON_DOUBLE_CLICK_DESCRIPTION": "Require a double-click on channel rows before playback starts.",
"LANGUAGE": "Language",
"SETTINGS_SAVED": "Success! Configuration was saved.",
"THEME": "Visual theme",
diff --git a/libs/m3u-state/src/lib/actions.ts b/libs/m3u-state/src/lib/actions.ts
index 3d10cd25f..6065795c8 100644
--- a/libs/m3u-state/src/lib/actions.ts
+++ b/libs/m3u-state/src/lib/actions.ts
@@ -35,7 +35,10 @@ export const ChannelActions = createActionGroup({
events: {
'Set Channels Loading': props<{ loading: boolean }>(),
'Set Channels': props<{ channels: Channel[] }>(),
- 'Set Active Channel': props<{ channel: Channel }>(),
+ 'Set Active Channel': props<{
+ channel: Channel;
+ startPlayback?: boolean;
+ }>(),
'Set Active Channel Success': props<{ channel: Channel }>(),
'Reset Active Channel': emptyProps(),
'Set Adjacent Channel As Active': props<{
diff --git a/libs/m3u-state/src/lib/effects.ts b/libs/m3u-state/src/lib/effects.ts
index bef31d0ff..5fbbdee04 100644
--- a/libs/m3u-state/src/lib/effects.ts
+++ b/libs/m3u-state/src/lib/effects.ts
@@ -170,9 +170,14 @@ export class PlaylistEffects {
firstValueFrom(this.storage.get(STORE_KEY.Settings)).then(
(settings: any) => {
+ const shouldOpenExternalPlayer =
+ !settings?.openStreamOnDoubleClick ||
+ action.startPlayback === true;
+
if (
settings &&
Object.keys(settings).length > 0 &&
+ shouldOpenExternalPlayer &&
settings.player === VideoPlayer.MPV &&
channel.radio !== 'true'
) {
@@ -186,6 +191,7 @@ export class PlaylistEffects {
} else if (
settings &&
Object.keys(settings).length > 0 &&
+ shouldOpenExternalPlayer &&
settings.player === VideoPlayer.VLC &&
channel.radio !== 'true'
)
diff --git a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.html b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.html
index d45da9181..ce4bbe8bf 100644
--- a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.html
+++ b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.html
@@ -44,6 +44,7 @@
[showProgramInfoButton]="true"
[showDetailsContextMenu]="hasChannelContextMenu(ch)"
(clicked)="onChannelClick(ch)"
+ (activated)="onChannelActivate(ch)"
(favoriteToggled)="onFavoriteToggled(ch)"
(contextMenuRequested)="onChannelContextMenu(ch, $event)"
/>
diff --git a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.spec.ts b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.spec.ts
index dcc976169..270cc1bdb 100644
--- a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.spec.ts
+++ b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.spec.ts
@@ -1,10 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { signal } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { ChannelDetailsDialogComponent } from 'components';
import { UnifiedFavoriteChannel } from '@iptvnator/portal/shared/util';
import { Channel } from 'shared-interfaces';
+import { SettingsStore } from 'services';
import { GlobalFavoritesListComponent } from './global-favorites-list.component';
describe('GlobalFavoritesListComponent', () => {
@@ -27,6 +29,12 @@ describe('GlobalFavoritesListComponent', () => {
provide: MatDialog,
useValue: dialog,
},
+ {
+ provide: SettingsStore,
+ useValue: {
+ openStreamOnDoubleClick: signal(false),
+ },
+ },
],
}).compileComponents();
diff --git a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.ts b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.ts
index 30024df67..26d519f2d 100644
--- a/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.ts
+++ b/libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.ts
@@ -20,6 +20,7 @@ import {
ChannelDetailsDialogComponent,
ChannelListItemComponent,
} from 'components';
+import { SettingsStore } from 'services';
import { EpgProgram } from 'shared-interfaces';
import {
DEFAULT_FAVORITES_CHANNEL_SORT_MODE,
@@ -51,9 +52,13 @@ export type GlobalFavoritesListMode = 'favorites' | 'recent';
})
export class GlobalFavoritesListComponent {
private readonly dialog = inject(MatDialog);
+ private readonly settingsStore = inject(SettingsStore);
readonly contextMenuTrigger =
viewChild.required('contextMenuTrigger');
+ readonly openStreamOnDoubleClick = computed(() =>
+ this.settingsStore.openStreamOnDoubleClick()
+ );
readonly channels = input.required();
readonly mode = input('favorites');
@@ -68,6 +73,7 @@ export class GlobalFavoritesListComponent {
);
readonly channelSelected = output();
+ readonly playbackRequested = output();
readonly channelsReordered = output();
readonly favoriteToggled = output();
readonly removeRequested = output();
@@ -126,6 +132,12 @@ export class GlobalFavoritesListComponent {
this.channelSelected.emit(channel);
}
+ onChannelActivate(channel: UnifiedFavoriteChannel): void {
+ if (this.openStreamOnDoubleClick()) {
+ this.playbackRequested.emit(channel);
+ }
+ }
+
onFavoriteToggled(channel: UnifiedFavoriteChannel): void {
this.favoriteToggled.emit(channel);
}
diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.html b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.html
index a3fd8b613..3d8c42fe7 100644
--- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.html
+++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.html
@@ -18,6 +18,7 @@
[draggable]="mode() === 'favorites'"
[sortMode]="sortMode()"
(channelSelected)="onChannelSelected($event)"
+ (playbackRequested)="onChannelPlaybackRequested($event)"
(channelsReordered)="onReorder($event)"
(favoriteToggled)="onFavoriteToggled($event)"
(removeRequested)="onRemoveRequested($event)"
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 e760c52c8..e9c3843e1 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
@@ -269,6 +269,17 @@ export class UnifiedLiveTabComponent {
}
}
+ async onChannelPlaybackRequested(
+ channel: UnifiedFavoriteChannel
+ ): Promise {
+ const item = this.items().find(
+ (candidate) => candidate.uid === channel.uid
+ );
+ if (item) {
+ await this.activateItem(item, false, true);
+ }
+ }
+
onFavoriteToggled(channel: UnifiedFavoriteChannel): void {
const item = this.items().find(
(candidate) => candidate.uid === channel.uid
@@ -330,9 +341,18 @@ export class UnifiedLiveTabComponent {
private async activateItem(
item: UnifiedCollectionItem,
- isAutoOpen = false
+ isAutoOpen = false,
+ startPlayback = false
): Promise {
if (this.activeUid() === item.uid && this.activeDetail()) {
+ if (
+ startPlayback &&
+ this.shouldOpenExternalPlayback(this.activeDetail()!, true)
+ ) {
+ void this.portalPlayer.openResolvedPlayback(
+ this.activeDetail()!.playback
+ );
+ }
if (isAutoOpen) {
this.autoOpenHandled.emit();
}
@@ -359,7 +379,7 @@ export class UnifiedLiveTabComponent {
void this.hydrateSelectedM3uPrograms(item, detail, requestId);
}
- if (this.shouldOpenExternalPlayback(detail)) {
+ if (this.shouldOpenExternalPlayback(detail, startPlayback)) {
void this.portalPlayer.openResolvedPlayback(detail.playback);
}
@@ -389,10 +409,15 @@ export class UnifiedLiveTabComponent {
}
private shouldOpenExternalPlayback(
- detail: ResolvedLiveCollectionDetail
+ detail: ResolvedLiveCollectionDetail,
+ startPlayback = false
): boolean {
+ if (this.isRadioDetail(detail) || this.portalPlayer.isEmbeddedPlayer()) {
+ return false;
+ }
+
return (
- !this.isRadioDetail(detail) && !this.portalPlayer.isEmbeddedPlayer()
+ !this.settingsStore.openStreamOnDoubleClick() || startPlayback
);
}
diff --git a/libs/portal/stalker/feature/src/lib/stalker-collection-channels-list/stalker-collection-channels-list.component.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-collection-channels-list/stalker-collection-channels-list.component.spec.ts
index 96d56d510..aefcf0f9d 100644
--- a/libs/portal/stalker/feature/src/lib/stalker-collection-channels-list/stalker-collection-channels-list.component.spec.ts
+++ b/libs/portal/stalker/feature/src/lib/stalker-collection-channels-list/stalker-collection-channels-list.component.spec.ts
@@ -39,7 +39,9 @@ describe('StalkerCollectionChannelsListComponent', () => {
await TestBed.configureTestingModule({
imports: [StalkerCollectionChannelsListComponent],
- providers: [{ provide: StalkerStore, useValue: stalkerStore }],
+ providers: [
+ { provide: StalkerStore, useValue: stalkerStore },
+ ],
})
.overrideComponent(StalkerCollectionChannelsListComponent, {
remove: {
diff --git a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.html b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.html
index 49297fd36..e9afb035a 100644
--- a/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.html
+++ b/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.html
@@ -73,6 +73,7 @@
false
"
(clicked)="playChannel(item)"
+ (activated)="playChannel(item, true)"
(favoriteToggled)="toggleFavorite(item)"
/>
}
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 7a8158922..989d63af7 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
@@ -14,7 +14,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { ChannelListItemComponent } from 'components';
import { MockPipe } from 'ng-mocks';
import { of } from 'rxjs';
-import { PlaylistsService } from 'services';
+import { PlaylistsService, SettingsStore } from 'services';
import { EpgProgram } from 'shared-interfaces';
import {
LiveEpgPanelComponent,
@@ -38,6 +38,7 @@ class StubChannelListItemComponent {
readonly showProgramInfoButton = input(false);
readonly isFavorite = input(false);
readonly clicked = output();
+ readonly activated = output();
readonly favoriteToggled = output();
}
@@ -194,6 +195,9 @@ describe('StalkerLiveStreamLayoutComponent', () => {
isEmbeddedPlayer: jest.fn(() => true),
openResolvedPlayback: jest.fn(),
};
+ const settingsStore = {
+ openStreamOnDoubleClick: signal(false),
+ };
beforeEach(async () => {
fetchChannelEpg = stalkerStore.fetchChannelEpg;
@@ -213,6 +217,7 @@ describe('StalkerLiveStreamLayoutComponent', () => {
hasMoreChannels.set(false);
page.set(0);
localStorage.removeItem(LIVE_EPG_PANEL_STATE_STORAGE_KEY);
+ settingsStore.openStreamOnDoubleClick.set(false);
resolveItvPlayback.mockReset();
resolveItvPlayback.mockResolvedValue({
@@ -254,6 +259,7 @@ describe('StalkerLiveStreamLayoutComponent', () => {
providers: [
{ provide: StalkerStore, useValue: stalkerStore },
{ provide: PlaylistsService, useValue: playlistService },
+ { provide: SettingsStore, useValue: settingsStore },
{ provide: PORTAL_PLAYER, useValue: portalPlayer },
{
provide: TranslateService,
@@ -368,6 +374,77 @@ describe('StalkerLiveStreamLayoutComponent', () => {
).toBe(false);
});
+ it('reuses a pending playback resolution during double-click activation', async () => {
+ settingsStore.openStreamOnDoubleClick.set(true);
+ portalPlayer.isEmbeddedPlayer.mockReturnValue(false);
+
+ let resolvePlayback!: (value: { streamUrl: string }) => void;
+ resolveItvPlayback.mockReturnValue(
+ new Promise((resolve) => {
+ resolvePlayback = resolve;
+ })
+ );
+
+ const firstClick = component.playChannel(itvChannels()[0], false);
+ const secondClick = component.playChannel(itvChannels()[0], false);
+ const doubleClick = component.playChannel(itvChannels()[0], true);
+
+ expect(resolveItvPlayback).toHaveBeenCalledTimes(1);
+
+ resolvePlayback({
+ streamUrl: 'https://example.com/alpha.m3u8',
+ });
+ await Promise.all([firstClick, secondClick, doubleClick]);
+
+ expect(portalPlayer.openResolvedPlayback).toHaveBeenCalledTimes(1);
+ expect(portalPlayer.openResolvedPlayback).toHaveBeenCalledWith(
+ { streamUrl: 'https://example.com/alpha.m3u8' },
+ true
+ );
+ });
+
+ it('starts external playback from remote channel navigation when double-click opening is enabled', async () => {
+ settingsStore.openStreamOnDoubleClick.set(true);
+ portalPlayer.isEmbeddedPlayer.mockReturnValue(false);
+ selectedItem.set(itvChannels()[0]);
+ selectedItvId.set('10001');
+
+ (
+ component as unknown as {
+ handleRemoteChannelChange(direction: 'up' | 'down'): void;
+ }
+ ).handleRemoteChannelChange('down');
+ await fixture.whenStable();
+
+ expect(portalPlayer.openResolvedPlayback).toHaveBeenCalledWith(
+ { streamUrl: 'https://example.com/alpha.m3u8' },
+ true
+ );
+ });
+
+ it('starts external playback from remote number selection when double-click opening is enabled', async () => {
+ settingsStore.openStreamOnDoubleClick.set(true);
+ portalPlayer.isEmbeddedPlayer.mockReturnValue(false);
+
+ (
+ component as unknown as {
+ handleRemoteControlCommand(command: {
+ type: 'channel-select-number';
+ number: number;
+ }): void;
+ }
+ ).handleRemoteControlCommand({
+ type: 'channel-select-number',
+ number: 2,
+ });
+ await fixture.whenStable();
+
+ expect(portalPlayer.openResolvedPlayback).toHaveBeenCalledWith(
+ { streamUrl: 'https://example.com/alpha.m3u8' },
+ true
+ );
+ });
+
it('renders the current EPG program in the collapsible panel summary', async () => {
fixture.detectChanges();
await fixture.whenStable();
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 3380e62c2..225132c9f 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
@@ -25,7 +25,7 @@ import {
ChannelListSkeletonComponent,
ResizableDirective,
} from 'components';
-import { PlaylistsService } from 'services';
+import { PlaylistsService, SettingsStore } from 'services';
import {
Channel,
EpgItem,
@@ -86,6 +86,7 @@ import {
export class StalkerLiveStreamLayoutComponent implements OnDestroy {
readonly stalkerStore = inject(StalkerStore);
private readonly playlistService = inject(PlaylistsService);
+ private readonly settingsStore = inject(SettingsStore);
private readonly portalPlayer = inject(PORTAL_PLAYER);
private readonly snackBar = inject(MatSnackBar);
private readonly translate = inject(TranslateService);
@@ -125,6 +126,9 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
readonly selectedChannelId = this.stalkerStore.selectedItvId;
protected readonly normalizeStalkerEntityId = normalizeStalkerEntityId;
+ readonly openStreamOnDoubleClick = computed(() =>
+ this.settingsStore.openStreamOnDoubleClick()
+ );
/** Player */
readonly usesEmbeddedPlayer = computed(() =>
@@ -207,6 +211,13 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
private unsubscribeRemoteChannelChange?: () => void;
private unsubscribeRemoteCommand?: () => void;
private epgLoadRequestId = 0;
+ private playbackRequestId = 0;
+ private playbackResolution:
+ | {
+ channelId: string;
+ promise: Promise;
+ }
+ | null = null;
private lastPlaylistId: string | null | undefined = undefined;
constructor() {
@@ -341,19 +352,38 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
);
}
- async playChannel(item: StalkerItvChannel) {
+ async playChannel(
+ item: StalkerItvChannel,
+ startPlayback = !this.settingsStore.openStreamOnDoubleClick()
+ ) {
+ const requestId = ++this.playbackRequestId;
+ const channelId = normalizeStalkerEntityId(item.id);
this.stalkerStore.setSelectedItem(item);
try {
- const playback = await this.stalkerStore.resolveItvPlayback(item);
+ const playback = await this.resolvePlaybackForChannel(
+ item,
+ channelId
+ );
+ if (
+ requestId !== this.playbackRequestId ||
+ this.selectedChannelId() !== channelId
+ ) {
+ return;
+ }
+
void this.loadEpgForChannel(item);
if (this.usesEmbeddedPlayer()) {
this.activePlayback.set(playback);
- } else {
+ } else if (startPlayback) {
void this.portalPlayer.openResolvedPlayback(playback, true);
}
} catch (error) {
+ if (requestId !== this.playbackRequestId) {
+ return;
+ }
+
this.logger.error('Playback failed', error);
const errorMessage =
error?.message === 'nothing_to_play'
@@ -363,6 +393,26 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
}
}
+ private resolvePlaybackForChannel(
+ item: StalkerItvChannel,
+ channelId: string
+ ): Promise {
+ if (this.playbackResolution?.channelId === channelId) {
+ return this.playbackResolution.promise;
+ }
+
+ const promise = this.stalkerStore.resolveItvPlayback(item);
+ this.playbackResolution = { channelId, promise };
+
+ void promise.finally(() => {
+ if (this.playbackResolution?.promise === promise) {
+ this.playbackResolution = null;
+ }
+ });
+
+ return promise;
+ }
+
toggleFavorite(item: StalkerItvChannel) {
const itemId = normalizeStalkerEntityId(item.id);
if (this.favorites.has(itemId)) {
@@ -680,7 +730,7 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
return;
}
- void this.playChannel(nextItem);
+ void this.playChannel(nextItem, true);
}
private handleRemoteControlCommand(command: {
@@ -703,6 +753,6 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
return;
}
- void this.playChannel(channel);
+ void this.playChannel(channel, true);
}
}
diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
index fcdfe651f..023f0623f 100644
--- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
+++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
@@ -74,6 +74,7 @@
[sortMode]="liveChannelSortMode()"
[searchTermInput]="workspaceSearchTerm()"
(playClicked)="playLive($event)"
+ (playbackRequested)="playLive($event, true)"
/>
}
@@ -98,6 +99,7 @@
[sortMode]="liveChannelSortMode()"
[searchTermInput]="workspaceSearchTerm()"
(playClicked)="playLive($event)"
+ (playbackRequested)="playLive($event, true)"
/>
} @else {
diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts
index dc1a5b2fc..2b0976c39 100644
--- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts
+++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts
@@ -27,6 +27,7 @@ import {
import { EpgItem, EpgProgram } from 'shared-interfaces';
import { PortalChannelsListComponent } from '../portal-channels-list/portal-channels-list.component';
import { LiveStreamLayoutComponent } from './live-stream-layout.component';
+import { SettingsStore } from 'services';
const LIVE_CHANNEL_SORT_STORAGE_KEY = 'xtream-live-channel-sort-mode';
@@ -39,6 +40,7 @@ class StubPortalChannelsListComponent {
readonly sortMode = input<'server' | 'name-asc' | 'name-desc'>('server');
readonly searchTermInput = input('');
readonly playClicked = output