Merge branch 'feature/t45-live-line-logo' into develop (#45)

This commit is contained in:
blomios
2026-10-08 19:39:46 +02:00
3 changed files with 135 additions and 1 deletions

View File

@ -100,7 +100,7 @@
language, language,
selectedVariantId: logicalChannel.selectedVariantId || `${preferredVariant.sourceId}:${preferredVariant.id}`, selectedVariantId: logicalChannel.selectedVariantId || `${preferredVariant.sourceId}:${preferredVariant.id}`,
groupTitle: preferredVariant.groupTitle, groupTitle: preferredVariant.groupTitle,
tvgLogo: preferredVariant.tvgLogo, tvgLogo: logicalChannel.tvgLogo ?? preferredVariant.tvgLogo,
tvgId: logicalChannel.epg?.channelId || preferredVariant.tvgId, tvgId: logicalChannel.epg?.channelId || preferredVariant.tvgId,
isFavorite: mappedVariants.some(variant => variant.isFavorite), isFavorite: mappedVariants.some(variant => variant.isFavorite),
isHidden: mappedVariants.every(variant => variant.isHidden) isHidden: mappedVariants.every(variant => variant.isHidden)

View File

@ -486,6 +486,16 @@ function pageMetadata(total, bounds) {
function listChannel(channel) { function listChannel(channel) {
const preferredVariant = channel.preferredVariant || channel.variants[0] || null; const preferredVariant = channel.preferredVariant || channel.variants[0] || null;
const variantLogo = variant => variant?.tvgLogo || variant?.streamIcon || variant?.stream_icon || null;
const selectedVariant = channel.variants.find(variant =>
`${variant.sourceId}:${variant.id}` === String(channel.selectedVariantId)
);
const logoVariant = (selectedVariant && variantLogo(selectedVariant) && selectedVariant)
|| (preferredVariant && variantLogo(preferredVariant) && preferredVariant)
|| channel.variants.find(variant => !variant.isHidden && variantLogo(variant))
|| channel.variants.find(variant => variant.isHidden && variantLogo(variant))
|| null;
const logo = variantLogo(logoVariant);
const variants = []; const variants = [];
const addPreview = variant => { const addPreview = variant => {
if (variant && variants.length < 3 && !variants.some(candidate => if (variant && variants.length < 3 && !variants.some(candidate =>
@ -498,6 +508,8 @@ function listChannel(channel) {
return { return {
...channel, ...channel,
preferredVariant, preferredVariant,
tvgLogo: logo,
streamIcon: logo,
variantCount: channel.variants.length, variantCount: channel.variants.length,
variants variants
}; };

View File

@ -0,0 +1,122 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Database = require('better-sqlite3');
const liveProjection = require('../server/services/liveProjectionService');
const {
ensureColumns,
LIVE_PROJECTION_COLUMNS,
LIVE_PROJECTION_SCHEMA_SQL
} = require('../server/db/liveProjectionSchema');
function createProjectionDb() {
const db = new Database(':memory:');
db.exec(`
CREATE TABLE categories (
id TEXT PRIMARY KEY,
source_id INTEGER NOT NULL,
category_id TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
is_hidden INTEGER DEFAULT 0,
data JSON
);
CREATE TABLE playlist_items (
id TEXT PRIMARY KEY,
source_id INTEGER NOT NULL,
item_id TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT NOT NULL,
category_id TEXT,
parent_id TEXT,
stream_icon TEXT,
stream_url TEXT,
container_extension TEXT,
rating REAL,
year TEXT,
added_at TEXT,
is_hidden INTEGER DEFAULT 0,
is_favorite INTEGER DEFAULT 0,
data JSON
);
CREATE TABLE favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
source_id INTEGER NOT NULL,
item_id TEXT NOT NULL,
item_type TEXT NOT NULL
);
`);
ensureColumns(db, 'playlist_items', LIVE_PROJECTION_COLUMNS);
db.exec(LIVE_PROJECTION_SCHEMA_SQL);
db.prepare(`
INSERT INTO categories (id, source_id, category_id, type, name)
VALUES ('sports', 2, 'sports', 'live', 'Sports')
`).run();
return db;
}
function seedProjectedVariant(db, { itemId, name, icon, rank }) {
db.prepare(`
INSERT INTO playlist_items
(id, source_id, item_id, type, name, category_id, stream_icon, stream_url, data,
logical_channel_id, grouping_fingerprint, grouping_input_hash, quality_label, quality_rank,
variant_rank, language_code, language_confidence, language_detector, language_input_hash)
VALUES (?, 2, ?, 'live', ?, 'sports', ?, ?, ?, 'logical:bein-sports-1',
'bein sports 1', 'group-hash', 'HD', 2, ?, 'fr', 0.97, 'laya', ?)
`).run(
`2:${itemId}`,
itemId,
name,
icon,
`https://streams.test/${itemId}.m3u8`,
JSON.stringify({ groupTitle: 'Sports', tvgId: `bein1.${itemId}` }),
rank,
`language-${itemId}`
);
}
test('projected live channel exposes a row logo from the selected variant stream_icon', () => {
const db = createProjectionDb();
const preferredLogo = 'http://57.128.182.77:8080/0tv_logo/119.png';
db.prepare(`
INSERT INTO logical_channels
(id, fingerprint, display_name, preferred_variant_id, variant_count, created_at, updated_at)
VALUES ('logical:bein-sports-1', 'bein sports 1', 'beIN Sports 1', '2:146116', 2, 1, 1)
`).run();
seedProjectedVariant(db, {
itemId: '146116',
name: 'beIN Sports 1 HD FR',
icon: preferredLogo,
rank: 0
});
seedProjectedVariant(db, {
itemId: '146117',
name: 'beIN Sports 1 Backup FR',
icon: 'http://57.128.182.77:8080/0tv_logo/backup.png',
rank: 1
});
try {
const page = liveProjection.getChannelPage({
q: 'bein sports 1',
languages: ['fr'],
includeHidden: false,
page: 1,
limit: 10
}, db);
assert.equal(page.channels.length, 1);
const [channel] = page.channels;
assert.equal(channel.variantCount, 2);
assert.equal(channel.selectedVariantId, '2:146116');
assert.equal(channel.preferredVariant.streamIcon, preferredLogo);
assert.equal(
channel.tvgLogo || channel.streamIcon,
preferredLogo,
'ChannelList renders channel.tvgLogo, so the service response needs a row-level logo derived from the preferred variant stream_icon'
);
} finally {
db.close();
}
});