+ return
{(!url || loaded !== url) && }
{url &&
setLoaded(url)} onError={() => { failChannelLogo(url); setFailed(old => [...old, url]); }} />}
;
}
diff --git a/web/components/livetv/LiveTvScreen.tsx b/web/components/livetv/LiveTvScreen.tsx
index 779dade0b..44212c1bc 100644
--- a/web/components/livetv/LiveTvScreen.tsx
+++ b/web/components/livetv/LiveTvScreen.tsx
@@ -489,6 +489,7 @@ export function LiveTvScreen() {
<>
+ {!activeChannel &&
}
{selectedChannel.group || "Live TV"}
{selectedChannel.name}{selectedChannel.qualityLabel ? ` · ${selectedChannel.qualityLabel}` : ""}
diff --git a/web/components/livetv/SportsGuidePane.tsx b/web/components/livetv/SportsGuidePane.tsx
index 06f6714fd..cd4397778 100644
--- a/web/components/livetv/SportsGuidePane.tsx
+++ b/web/components/livetv/SportsGuidePane.tsx
@@ -2,7 +2,7 @@
import { useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import { X, Tv, PanelLeft, ChevronRight, Play, RefreshCw } from "lucide-react";
-import { guideSports, isOnAir, isConfirmedLive, hasSportsChannels, availableEventChannels, sportsGuideRows, type SportsGuideEvent } from "@/lib/sportsGuide";
+import { guideSports, isOnAir, isConfirmedLive, hasSportsChannels, availableEventChannels, sportsGuideRows, sportsChannelSummary, type SportsGuideEvent } from "@/lib/sportsGuide";
import { sportsChannelKey, sportsBroadcasterKeys } from "@/lib/sportsCatalogue";
import type { InstalledAddon, IptvChannel, IptvNowNext } from "@/lib/types";
import { cachedSportsMetadata, loadSportsGuideArtwork, loadSportsMetadata, type SportsEventArtwork } from "@/lib/sportsArtwork";
@@ -85,7 +85,7 @@ export function SportsGuidePane({ channels, guide, onPlay, onEnter, onOpenCatego
}, [rows.length]);
const selected = illustratedEvents.find((event) => event.id === selectedId);
const confirmedChannels = selected ? (isOnAir(selected, now) ? availableEventChannels(selected, now) : selected.channels) : [];
- const possibleChannels = selected?.possibleChannels ?? [];
+ const possibleChannels = (selected?.possibleChannels ?? []).filter((ch, i, rows) => !confirmedChannels.some(match => match.id === ch.id) && rows.findIndex(other => other.id === ch.id) === i);
const sourceChannels = [...confirmedChannels, ...possibleChannels];
const close = () => { dialog.current?.close(); setSelectedId(null); origin.current?.focus({ preventScroll: true }); };
useEffect(() => {
@@ -146,7 +146,7 @@ export function SportsGuidePane({ channels, guide, onPlay, onEnter, onOpenCatego
}}
onClick={(click) => { origin.current = click.currentTarget; setShowScore(false); setSelectedId(event.id); }}>
setFailedArtwork(current => new Set([...current, event.id]))} />{stamp(event)}
- {event.title}{[sport.title, event.competition].filter(Boolean).join(" · ")}{isOnAir(event, now) && {channelCount(availableEventChannels(event, now).length + (event.possibleChannels?.length ?? 0))}}
+ {event.title}{[sport.title, event.competition].filter(Boolean).join(" · ")}{isOnAir(event, now) && {sportsChannelSummary(event, now)}}
;
})}
)}
@@ -155,7 +155,7 @@ export function SportsGuidePane({ channels, guide, onPlay, onEnter, onOpenCatego
{selected?.fixture && {[selected.competition, selected.fixture.venue, selected.fixture.round ? `Round ${selected.fixture.round}` : undefined].filter(Boolean).join(" · ")}
{selected.fixture.homeScore !== undefined && selected.fixture.awayScore !== undefined && now - selected.fixture.observedAt < 300_000 && }
}
- {selected && isOnAir(selected, now) ? "Channels" : "Scheduled channels"}{channelCount(sourceChannels.length)}
+ {selected && isOnAir(selected, now) ? "Channels" : "Scheduled channels"}{selected ? sportsChannelSummary(selected, now) : "No channels"}
{!sourceChannels.length && No matching channels in your playlists.
}
ch.id} label="Available channels" renderItem={(ch) =>