mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
feat(external-playback-dock): enhance artwork handling with error fallback and dynamic placeholder icons
This commit is contained in:
1 parent
689c7bd949
commit
6f914a6d34
3 files changed
+58
-3
No files matched your search
+4
-3
@@ -4,15 +4,16 @@
|
||||
[class.external-playback-dock--error]="session().status === 'error'"
|
||||
>
|
||||
<div class="external-playback-dock__artwork">
|
||||
@if (session().thumbnail) {
|
||||
@if (showArtwork()) {
|
||||
<img
|
||||
[src]="session().thumbnail!"
|
||||
[src]="artworkUrl()"
|
||||
[alt]="session().title"
|
||||
loading="lazy"
|
||||
(error)="onArtworkError()"
|
||||
/>
|
||||
} @else {
|
||||
<div class="external-playback-dock__placeholder">
|
||||
<mat-icon>play_circle</mat-icon>
|
||||
<mat-icon>{{ artworkPlaceholderIcon() }}</mat-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
+24
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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<void>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user