fix(stalker): play radio collections with audio layout (#930)

* fix(stalker): play radio collections with audio layout

* fix(stalker): address radio review feedback
This commit is contained in:
4gray authored and GitHub committed 2026-05-12 19:48:38 +02:00
1 parent 6ae1251ad1
commit a33d506266
13 files changed
+588 -97

No files matched your search

@@ -1,4 +1,41 @@
import { normalizeStalkerDate } from './stalker-item.normalizer';
import {
extractStalkerItemType,
isStalkerRadioItem,
normalizeStalkerDate,
} from './stalker-item.normalizer';
describe('extractStalkerItemType', () => {
it('treats Stalker radio stations as live collection items', () => {
expect(
extractStalkerItemType({
id: '40001',
title: 'Jazz Radio',
category_id: 'radio',
radio: true,
})
).toBe('live');
expect(
extractStalkerItemType({
id: '40002',
title: 'Rock Radio',
category_id: 'radio-genre-1',
radio: 'true',
})
).toBe('live');
});
});
describe('isStalkerRadioItem', () => {
it('does not infer radio from a generic HTTP path segment', () => {
expect(
isStalkerRadioItem({
id: '77',
title: 'News Channel',
cmd: 'https://media.example.com/live/radio/news/index.m3u8',
})
).toBe(false);
});
});
describe('normalizeStalkerDate', () => {
it('normalizes SQLite UTC timestamps without relying on engine-specific parsing', () => {
@@ -49,7 +49,7 @@ export function extractStalkerItemPoster(
/**
* Determine the normalised activity type of a Stalker item.
*
* - `itv` / `live` → `'live'`
* - `itv` / `live` / radio → `'live'`
* - `series` or `is_series` truthy → `'series'`
* - everything else → `'movie'`
*/
@@ -60,7 +60,11 @@ export function extractStalkerItemType(
const categoryId = String(raw['category_id'] ?? '').toLowerCase();
const streamType = String(raw['stream_type'] ?? '').toLowerCase();
if (categoryId === 'itv' || streamType === 'live') {
if (
categoryId === 'itv' ||
streamType === 'live' ||
isStalkerRadioItem(raw)
) {
return 'live';
}
@@ -76,6 +80,39 @@ export function extractStalkerItemType(
return 'movie';
}
/**
* Detect radio station records from Stalker favorites/recently-viewed payloads.
*/
export function isStalkerRadioItem(
item: StalkerPortalItem | Record<string, unknown>
): boolean {
const raw = item as Record<string, unknown>;
const radio = raw['radio'];
const radioFlag =
radio === true ||
radio === 1 ||
String(radio ?? '')
.trim()
.toLowerCase() === 'true' ||
String(radio ?? '').trim() === '1';
const categoryId = String(raw['category_id'] ?? '')
.trim()
.toLowerCase();
const streamType = String(raw['stream_type'] ?? '')
.trim()
.toLowerCase();
const cmd = String(raw['cmd'] ?? '')
.trim()
.toLowerCase();
return (
radioFlag ||
categoryId === 'radio' ||
streamType === 'radio' ||
cmd.includes('://radio/')
);
}
/**
* Normalise a raw date value (epoch number, numeric string, or ISO string)
* into a consistent ISO 8601 string.
@@ -131,16 +168,8 @@ function normalizeSqliteUtcTimestamp(value: string): string | null {
return null;
}
const [
,
year,
month,
day,
hours,
minutes,
seconds,
milliseconds = '0',
] = match;
const [, year, month, day, hours, minutes, seconds, milliseconds = '0'] =
match;
const normalizedMs = milliseconds.padEnd(3, '0').slice(0, 3);
return new Date(
@@ -17,9 +17,11 @@ export interface StalkerPortalItem {
cover?: string;
logo?: string;
poster_url?: string;
/** 'itv' | 'vod' | 'series' — Stalker content type */
/** Stalker content/category type such as 'itv', 'radio', 'vod', or 'series'. */
category_id?: string | number;
stream_type?: string;
/** Radio marker returned by some Stalker portals for radio station lists. */
radio?: boolean | number | string;
is_series?: boolean | number | string;
/** Embedded VOD-series episode numbers preserved for VOD favorites. */
series?: unknown[];