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); + } }