diff --git a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.html b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.html
index 00aa5cd13..e7d114b5a 100644
--- a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.html
+++ b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.html
@@ -4,15 +4,16 @@
[class.external-playback-dock--error]="session().status === 'error'"
>
- @if (session().thumbnail) {
+ @if (showArtwork()) {
![]()
} @else {
- play_circle
+ {{ artworkPlaceholderIcon() }}
}
diff --git a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.spec.ts b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.spec.ts
index f5c35531e..95078b6f6 100644
--- a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.spec.ts
+++ b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.spec.ts
@@ -46,4 +46,28 @@ describe('ExternalPlaybackDockComponent', () => {
expect(closeSpy).toHaveBeenCalled();
expect(buttons).toHaveLength(1);
});
+
+ it('falls back to a placeholder icon when artwork fails to load', () => {
+ fixture.componentRef.setInput('session', {
+ ...session,
+ thumbnail: 'https://example.com/broken.png',
+ contentInfo: {
+ playlistId: 'playlist-1',
+ contentXtreamId: 42,
+ contentType: 'vod',
+ },
+ });
+ fixture.detectChanges();
+
+ const image = fixture.debugElement.query(By.css('img'));
+ image.triggerEventHandler('error');
+ fixture.detectChanges();
+
+ expect(fixture.debugElement.query(By.css('img'))).toBeNull();
+ expect(
+ fixture.debugElement
+ .query(By.css('.external-playback-dock__placeholder mat-icon'))
+ .nativeElement.textContent.trim()
+ ).toBe('movie');
+ });
});
diff --git a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.ts b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.ts
index 9e4e531da..3ee2f67d6 100644
--- a/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.ts
+++ b/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.ts
@@ -2,8 +2,10 @@ import {
ChangeDetectionStrategy,
Component,
computed,
+ effect,
input,
output,
+ signal,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
@@ -22,8 +24,10 @@ export class ExternalPlaybackDockComponent {
readonly compact = input(false);
readonly closeClicked = output();
+ private readonly artworkFailed = signal(false);
readonly playerLabel = computed(() => this.session().player.toUpperCase());
+ readonly artworkUrl = computed(() => this.session().thumbnail?.trim() ?? '');
readonly statusLabel = computed(() => {
const session = this.session();
const player = this.playerLabel();
@@ -45,7 +49,33 @@ export class ExternalPlaybackDockComponent {
readonly showSpinner = 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 showCloseButton = computed(
() => this.session().canClose && this.session().status !== 'error'
);
+
+ constructor() {
+ effect(() => {
+ this.artworkUrl();
+ this.artworkFailed.set(false);
+ });
+ }
+
+ onArtworkError(): void {
+ this.artworkFailed.set(true);
+ }
}