fix(shorts): son par defaut, avance auto, anti-Forbidden DM, autoplay Twitch/Rumble
CI / build-and-test (push) Successful in 14m56s

Son active par defaut (YT/OD/DM/PT avec son ; clips Twitch en muet force, sans API de volume). Avance auto en fin de lecture (infoDelivery YT + minuteur duree generique garde par index/videoId, ignore si onglet cache). Dailymotion : allow_embed demande cote serveur, mappe en embeddable, propage en raw.allowEmbed pour le garde isPlayable (fini les Forbidden). Rumble autoplay=1/2 selon muet + nudge playVideo YT.
This commit is contained in:
2026-09-30 10:40:03 -04:00
parent 8e087daf93
commit 8e5f1d6c65
5 changed files with 144 additions and 12 deletions
+5
View File
@@ -35,6 +35,11 @@ export class DmAdapter implements ProviderAdapter<VideoItem> {
publishedAt: undefined, // Dailymotion API does not provide published date in search results
channelExternalId: serverRefValue(it.channelRef, 'dm') || it.channelId || undefined,
channelAvatarUrl: it.uploaderAvatar || undefined,
// Jouabilité : `allow_embed=false` (propriétaire) => le player répond
// "Forbidden". Propagé tel quel : `accepts()` l'exclut via le garde
// `raw.allowEmbed` (même garde que le chemin historique direct).
// Champ absent = on ne sait pas (jamais de `false` inventé).
embeddable: typeof it.embeddable === 'boolean' ? it.embeddable : undefined,
// Signaux de type pour le raffinement client des filtres.
type: typeof it.type === 'string' ? it.type : undefined,
isShort: it.isShort === true ? true : undefined,
+20
View File
@@ -84,6 +84,26 @@ assertEqual(formatCapturedAgo(NOW - 30000, NOW), 'capturé à l’instant', 'fra
assertEqual(formatCapturedAgo(NOW - 4 * 60000, NOW), 'capturé il y a 4 min', 'minutes');
assertEqual(formatCapturedAgo(NOW + 60000, NOW), 'capturé à l’instant', 'futur → à l’instant (jamais « dans 1 min »)');
console.log('--- allowEmbed (Dailymotion Forbidden) ---');
{
const v: any = catalogShortToVideo({
id: 'd1', provider: 'dailymotion', title: 'D', thumbnailUrl: '', channelName: 'C', embeddable: false,
} as any);
assertEqual(v?.raw?.allowEmbed, false, 'embeddable=false → raw.allowEmbed=false (garde isPlayable)');
}
{
const v: any = catalogShortToVideo({
id: 'd2', provider: 'dailymotion', title: 'D', thumbnailUrl: '', channelName: 'C', embeddable: true,
} as any);
assertEqual(v?.raw?.allowEmbed, true, 'embeddable=true propagé');
}
{
const v: any = catalogShortToVideo({
id: 'd3', provider: 'dailymotion', title: 'D', thumbnailUrl: '', channelName: 'C',
} as any);
assertEqual(v?.raw, undefined, 'embeddable absent → raw absent (doute profite à la vidéo)');
}
console.log('--- constantes ---');
assertEqual(SHORTS_CATALOG_PAGE_SIZE, 25, 'page catalogue 25 (serveur plafonne à 50)');
assertEqual(SHORTS_CATALOG_MIN_ITEMS, 3, 'seuil de repli legacy');
+14 -2
View File
@@ -49,6 +49,12 @@ export function catalogShortToVideo(item: VideoItem | null | undefined): Video |
const kind = kindRaw === 'clip' || kindRaw === 'vod' || kindRaw === 'short' || kindRaw === 'channel'
? kindRaw
: undefined;
// Jouabilité Dailymotion : `embeddable === false` (serveur lit `allow_embed`)
// => le player répondrait "Forbidden". Transmis dans `raw` pour que le garde
// existant `isPlayable` (`v?.raw?.allowEmbed === false`) l'exclue du flux,
// exactement comme le chemin historique direct. Absent = on ne sait pas.
const embeddableRaw = (item as { embeddable?: unknown }).embeddable;
const allowEmbed = typeof embeddableRaw === 'boolean' ? embeddableRaw : undefined;
return {
url: provider === 'odysee' && slug ? `https://odysee.com/${slug}` : '',
type: item.isLive === true ? 'live' : (item.isShort === true ? 'short' : 'video'),
@@ -71,8 +77,14 @@ export function catalogShortToVideo(item: VideoItem | null | undefined): Video |
language: typeof item.language === 'string' ? item.language : undefined,
// Provenance catalogue : le flux l'affiche (« capturé il y a X »), le
// modèle historique n'a pas ces champs → repli `raw`, jamais de perte.
raw: (slug || capturedAt !== undefined || source)
? { ...(slug ? { slug } : {}), ...(capturedAt !== undefined ? { capturedAt } : {}), ...(source ? { source } : {}) }
// (`allowEmbed` y voyage aussi : c'est la clé lue par `isPlayable`.)
raw: (slug || capturedAt !== undefined || source || allowEmbed !== undefined)
? {
...(slug ? { slug } : {}),
...(capturedAt !== undefined ? { capturedAt } : {}),
...(source ? { source } : {}),
...(allowEmbed !== undefined ? { allowEmbed } : {}),
}
: undefined,
};
}