Files
iptvnator/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.ts
T

136 lines
4.1 KiB
TypeScript

import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
output,
signal,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslatePipe } from '@ngx-translate/core';
import { ExternalPlayerSession } from '@iptvnator/shared/interfaces';
import { SettingsStore } from '@iptvnator/services';
import { applyChannelNameStrip } from '@iptvnator/shared/m3u-utils';
@Component({
selector: 'app-external-playback-dock',
imports: [
MatButtonModule,
MatIcon,
MatProgressSpinnerModule,
TranslatePipe,
],
templateUrl: './external-playback-dock.component.html',
styleUrl: './external-playback-dock.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExternalPlaybackDockComponent {
private readonly settingsStore = inject(SettingsStore);
readonly session = input.required<ExternalPlayerSession>();
readonly compact = input(false);
readonly closeClicked = output<void>();
readonly dismissClicked = output<void>();
readonly artworkClicked = output<void>();
private readonly artworkFailed = signal(false);
/**
* Sessions with contentInfo are tracked VOD/episode playback — their
* titles are movie/episode names, so the channel-prefix strip is
* applied only to sessions without it (live channels).
*/
readonly displayTitle = computed(() =>
applyChannelNameStrip(
this.session().title,
!this.session().contentInfo &&
this.settingsStore.stripCountryPrefix?.()
)
);
readonly playerLabel = computed(() => this.session().player.toUpperCase());
readonly artworkUrl = computed(
() => this.session().thumbnail?.trim() ?? ''
);
readonly statusLabelKey = computed(() => {
const session = this.session();
switch (session.status) {
case 'launching':
return 'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_OPENING';
case 'opened':
return 'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_STARTED';
case 'playing':
return 'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_PLAYING';
case 'error':
return 'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_FAILED';
default:
return 'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_CLOSED';
}
});
readonly statusIcon = computed(() => {
const status = this.session().status;
if (status === 'error') return 'error_outline';
if (status === 'playing') return 'play_circle';
return 'open_in_new';
});
readonly showSpinner = computed(
() => this.session().status === 'launching'
);
readonly showStatusIcon = computed(
() => this.session().status !== 'launching'
);
readonly showArtwork = computed(
() => !!this.artworkUrl() && !this.artworkFailed()
);
readonly artworkPlaceholderIcon = computed(() => {
const contentType = this.session().contentInfo?.contentType;
switch (contentType) {
case 'vod':
return 'movie';
case 'episode':
return 'video_library';
default:
return 'live_tv';
}
});
readonly artworkInteractive = computed(
() => !!this.session().contentInfo?.playlistId
);
readonly showCloseAction = computed(() => this.session().canClose);
readonly showDismissAction = computed(
() => this.session().status === 'error' && !this.session().canClose
);
constructor() {
effect(() => {
this.artworkUrl();
this.artworkFailed.set(false);
});
}
onArtworkError(): void {
this.artworkFailed.set(true);
}
onArtworkClick(): void {
if (!this.artworkInteractive()) return;
this.artworkClicked.emit();
}
onCloseClick(): void {
this.closeClicked.emit();
}
onDismissClick(): void {
this.dismissClicked.emit();
}
}