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.
53 lines
2.9 KiB
TypeScript
53 lines
2.9 KiB
TypeScript
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
import { firstValueFrom } from 'rxjs';
|
|
import type { ProviderAdapter, ProviderSearchParams, SearchResult } from '../models';
|
|
import { VideoItem } from 'src/app/shared/models/video-item.model';
|
|
import { withFilterParams } from './filter-params';
|
|
import { readSearchGroup } from '../search-contract';
|
|
import { mapProvenance } from './provenance';
|
|
import { serverRefValue } from '../../shared/providers/channel-ref';
|
|
|
|
// TODO: Make SearchResult generic
|
|
export class DmAdapter implements ProviderAdapter<VideoItem> {
|
|
key = 'dm' as const;
|
|
label = 'Dailymotion';
|
|
constructor(private http: HttpClient) {}
|
|
async search(params: ProviderSearchParams, _signal: AbortSignal): Promise<SearchResult<VideoItem>> {
|
|
let httpParams = new HttpParams().set('q', params.q).set('providers', 'dm');
|
|
if (params.pageToken) httpParams = httpParams.set('page', params.pageToken);
|
|
if (params.sort) httpParams = httpParams.set('sort', params.sort);
|
|
// Filtres (type / durée / période) : appliqués côté serveur quand le provider
|
|
// le sait, affinés en post-traitement sinon.
|
|
httpParams = withFilterParams(httpParams, params.filters);
|
|
const res = await firstValueFrom(this.http.get<any>(`/api/search`, { params: httpParams }));
|
|
const { items: rawItems, providerError } = readSearchGroup(res, 'dm');
|
|
const items: VideoItem[] = rawItems.map((it: any) => ({
|
|
// Phase 7.3 — provenance (instant de collecte + voie) : propriété de la
|
|
// donnée, pas d'un provider — mapping commun aux 6 adaptateurs.
|
|
...mapProvenance(it),
|
|
id: it.id,
|
|
provider: 'dailymotion',
|
|
title: it.title,
|
|
channelName: it.uploaderName,
|
|
durationSec: it.duration,
|
|
thumbnailUrl: it.thumbnail,
|
|
viewCount: undefined, // Dailymotion API does not provide view count in search results
|
|
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,
|
|
// Dimensions (signal d'orientation pour la détection Short).
|
|
width: typeof it.width === 'number' ? it.width : undefined,
|
|
height: typeof it.height === 'number' ? it.height : undefined,
|
|
} as VideoItem));
|
|
return { items, total: rawItems.length, providerError };
|
|
}
|
|
}
|