import type { VideoWidgetNode } from '../schema'; import { pickUrlLike } from './urlLike'; /** * goView video option shape varies across versions. * Keep it permissive and normalize the common fields. */ export interface GoViewVideoOption { dataset?: unknown; src?: unknown; url?: unknown; loop?: boolean; muted?: boolean; autoplay?: boolean; autoPlay?: boolean; fit?: unknown; objectFit?: unknown; borderRadius?: number; } /** * Back-compat alias (older code used "LegacyVideoOption"). */ export type LegacyVideoOption = GoViewVideoOption; function pickSrc(option: GoViewVideoOption): string { // Prefer the whole option first (covers videoUrl/mp4/m3u8/flv/etc directly on the object). return pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url); } function asString(v: unknown): string { if (typeof v === 'string') return v; if (!v) return ''; return ''; } function toMaybeBoolean(v: unknown): boolean | undefined { if (typeof v === 'boolean') return v; if (typeof v === 'number') return v !== 0; if (typeof v === 'string') { const s = v.trim().toLowerCase(); if (s === 'true' || s === '1') return true; if (s === 'false' || s === '0') return false; } return undefined; } function toMaybeNumber(v: unknown): number | undefined { if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string') { const n = Number(v); if (Number.isFinite(n)) return n; } return undefined; } function pickFromNested(input: unknown, picker: (obj: Record) => T | undefined, depth: number): T | undefined { if (!input || typeof input !== 'object') return undefined; const obj = input as Record; const direct = picker(obj); if (direct !== undefined) return direct; if (depth <= 0) return undefined; for (const key of ['style', 'config', 'option', 'options', 'props', 'dataset', 'data']) { const nested = pickFromNested(obj[key], picker, depth - 1); if (nested !== undefined) return nested; } return undefined; } function pickBorderRadius(option: GoViewVideoOption): number | undefined { const direct = toMaybeNumber(option.borderRadius); if (direct !== undefined) return direct; return pickFromNested(option, (obj) => toMaybeNumber(obj.borderRadius ?? obj.radius ?? obj.r), 2); } function pickBooleanLike(option: GoViewVideoOption, keys: string[]): boolean | undefined { return pickFromNested( option, (obj) => { for (const key of keys) { const v = toMaybeBoolean(obj[key]); if (v !== undefined) return v; } return undefined; }, 2, ); } function pickFitFromNested(option: GoViewVideoOption): string { const direct = asString(option.fit) || asString(option.objectFit); if (direct) return direct; const nested = pickFromNested(option, (obj) => asString(obj.fit) || asString(obj.objectFit), 2); return nested ?? ''; } function pickFit(option: GoViewVideoOption): VideoWidgetNode['props']['fit'] | undefined { const raw = pickFitFromNested(option); if (!raw) return undefined; // normalize common variants const v = raw.toLowerCase(); if (v === 'contain' || v === 'cover' || v === 'fill' || v === 'none' || v === 'scale-down') { return v as VideoWidgetNode['props']['fit']; } return undefined; } export function convertGoViewVideoOptionToNodeProps(option: GoViewVideoOption): VideoWidgetNode['props'] { return { src: pickSrc(option), loop: pickBooleanLike(option, ['loop', 'isLoop']), muted: pickBooleanLike(option, ['muted', 'isMuted', 'mute']), fit: pickFit(option), borderRadius: pickBorderRadius(option), }; } /** * Back-compat export. */ export const convertLegacyVideoOptionToNodeProps = convertGoViewVideoOptionToNodeProps;