feat(live): show language-scoped stream variants
Keep live stream variant expansion scoped to the selected language while preserving the legacy all-language variants response when no language is provided. Display projected current EPG programme data in live rows and distinguish selected streams from actively playing streams in the collapsed variant UI. Refs #43 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -311,6 +311,14 @@ class ChannelList {
|
||||
*/
|
||||
getProgramInfo(channel) {
|
||||
try {
|
||||
const projectedProgram = channel?.epg?.currentProgram;
|
||||
if (projectedProgram?.title) {
|
||||
return {
|
||||
...projectedProgram,
|
||||
start: projectedProgram.start ?? projectedProgram.startTime,
|
||||
end: projectedProgram.end ?? projectedProgram.endTime
|
||||
};
|
||||
}
|
||||
if (!window.app || !window.app.epgGuide) return null;
|
||||
|
||||
// Cache key: channel_id + current_minute (invalidate every minute)
|
||||
@ -335,7 +343,11 @@ class ChannelList {
|
||||
if (program) break;
|
||||
}
|
||||
}
|
||||
const result = program ? program.title : null;
|
||||
const result = program ? {
|
||||
...program,
|
||||
start: program.start ?? program.startTime,
|
||||
end: program.end ?? program.endTime
|
||||
} : null;
|
||||
|
||||
this._programInfoCache.set(cacheKey, result);
|
||||
return result;
|
||||
@ -346,8 +358,8 @@ class ChannelList {
|
||||
}
|
||||
|
||||
getProgramStatusText(channel) {
|
||||
const title = this.getProgramInfo(channel);
|
||||
if (title) return `Live: ${title}`;
|
||||
const program = this.getProgramInfo(channel);
|
||||
if (program?.title) return `Live: ${program.title}`;
|
||||
return window.app?.epgGuide?.loadError
|
||||
? 'Program information unavailable'
|
||||
: 'No program information';
|
||||
@ -895,13 +907,13 @@ class ChannelList {
|
||||
const playerVariant = window.app?.player?.currentChannel;
|
||||
const currentVariant = this.currentChannel?.preferredVariant;
|
||||
const playingKeys = new Set([
|
||||
channel.selectedVariantId,
|
||||
playerVariant?.sourceId !== undefined && playerVariant?.id !== undefined
|
||||
? `${playerVariant.sourceId}:${playerVariant.id}` : '',
|
||||
currentVariant?.sourceId !== undefined && currentVariant?.id !== undefined
|
||||
? `${currentVariant.sourceId}:${currentVariant.id}` : ''
|
||||
].filter(Boolean).map(String));
|
||||
const isPlaying = playingKeys.has(variantKey);
|
||||
const isSelected = !isPlaying && String(channel.selectedVariantId) === variantKey;
|
||||
return `<button class="channel-variant ${hidden ? 'is-hidden' : ''} ${isPlaying ? 'is-playing' : ''}" type="button"
|
||||
data-logical-id="${this.escapeHtml(channel.fingerprint)}" data-variant-id="${this.escapeHtml(String(variant.id))}"
|
||||
data-source-id="${variant.sourceId}" data-render-id="${renderId}" data-render-group="${this.escapeHtml(renderGroup)}">
|
||||
@ -910,6 +922,7 @@ class ChannelList {
|
||||
<span class="variant-source">${this.escapeHtml(source?.name || variant.sourceType)}</span>
|
||||
<span class="variant-name">${this.escapeHtml(variant.name)}</span>
|
||||
${isPlaying ? '<span class="variant-playing-badge">Playing</span>' : ''}
|
||||
${isSelected ? '<span class="variant-selected-badge">Selected</span>' : ''}
|
||||
</button>`;
|
||||
}).join('') : '';
|
||||
const variantCount = Number(channel.variantCount) || channel.variants.length;
|
||||
@ -928,7 +941,7 @@ class ChannelList {
|
||||
<span class="channel-language-badge" aria-label="${this.escapeHtml(languageAccessibleName)}" title="${this.escapeHtml(languageAccessibleName)}">${this.escapeHtml(languageBadge)}</span></div>
|
||||
<div class="channel-info"><div class="channel-name">${this.escapeHtml(channel.displayName)}</div>
|
||||
<div class="channel-program">${this.escapeHtml(programLabel)}</div></div>
|
||||
${variantCount > 1 ? `<button class="variant-toggle" type="button" aria-expanded="${expanded}" aria-pressed="${expanded}" title="Show streams"><span>${variantCount} streams</span><span class="variant-chevron">⌄</span></button>` : ''}
|
||||
${variantCount > 1 ? `<button class="variant-toggle" type="button" aria-expanded="${expanded}" aria-label="${this.escapeHtml(expanded ? `Hide streams for ${channel.displayName}` : `Show ${variantCount} streams for ${channel.displayName}`)}" title="${expanded ? 'Hide streams' : 'Show streams'}"><span>${variantCount} streams</span><span class="variant-chevron" aria-hidden="true">⌄</span></button>` : ''}
|
||||
<button class="favorite-btn ${channel.isFavorite ? 'active' : ''}" title="${selectedIsFavorite ? 'Remove selected stream from Favorites' : 'Add selected stream to Favorites'}">${channel.isFavorite ? Icons.favorite : Icons.favoriteOutline}</button>
|
||||
</div>
|
||||
${variantCount > 1 && expanded ? `<div class="channel-variants">${variants}${hasMoreVariants ? `<button type="button" class="btn btn-ghost load-more-variants" data-logical-id="${this.escapeHtml(channel.fingerprint)}">Load more streams</button>` : ''}</div>` : ''}
|
||||
@ -944,6 +957,7 @@ class ChannelList {
|
||||
const offset = append ? (current?.page?.nextOffset || 0) : 0;
|
||||
const response = await API.live.getChannelVariants(channel.logicalChannelId || logicalId, {
|
||||
includeHidden: Boolean(this.showHiddenCheckbox?.checked),
|
||||
language: channel.language?.code || '',
|
||||
limit: 100,
|
||||
offset
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user