/** * 视频通话美颜设置 * * 全部效果(磨皮/美白/红润/瘦脸/口红/腮红)默认由本地管线处理: * MediaPipe Face Landmarker(开源免费)识别人脸关键点 + WebGL/Canvas 逐帧渲染, * 经 getUserMedia 拦截注入通话视频流(见 utils/beauty/ 目录),预览所见即所得。 * * 仅当浏览器不支持 WebGL 时,磨皮/美白/红润回退到腾讯云 TRTC 免费 BasicBeauty * 插件(TUICallEngine.setBeautyLevel)。不依赖付费的腾讯特效 SDK。 */ /** 高级美颜(本地 AI 处理)设置 */ export interface AdvancedBeautySettings { /** 总开关;通话中途开启需重新开关摄像头才能接管视频流 */ enabled: boolean /** 瘦脸强度 0-100 */ slimStrength: number /** 口红浓度 0-100(0 为关闭) */ lipstickStrength: number /** 口红颜色(hex) */ lipstickColor: string /** 腮红浓度 0-100(0 为关闭) */ blushStrength: number /** 腮红颜色(hex) */ blushColor: string } export interface CallBeautySettings { /** 是否启用基础美颜(关闭时以全 0 参数下发,引擎会停用 BasicBeauty 插件) */ enabled: boolean /** 磨皮算法:0 光滑(TRTCBeautyStyleSmooth)/ 1 自然(TRTCBeautyStyleNature) */ style: number /** 磨皮级别 0-9 */ beautyLevel: number /** 美白级别 0-9 */ whitenessLevel: number /** 红润级别 0-9 */ ruddinessLevel: number /** 高级美颜(瘦脸/口红/腮红,本地 AI) */ advanced: AdvancedBeautySettings } export const BEAUTY_STYLE_SMOOTH = 0 export const BEAUTY_STYLE_NATURE = 1 export const BEAUTY_LEVEL_MAX = 9 const STORAGE_KEY = 'tcm_call_beauty_settings' export const defaultAdvancedBeautySettings = (): AdvancedBeautySettings => ({ enabled: false, slimStrength: 30, lipstickStrength: 0, lipstickColor: '#c94f5e', blushStrength: 0, blushColor: '#e88193' }) export const defaultBeautySettings = (): CallBeautySettings => ({ enabled: false, style: BEAUTY_STYLE_NATURE, beautyLevel: 5, whitenessLevel: 3, ruddinessLevel: 2, advanced: defaultAdvancedBeautySettings() }) const clampLevel = (value: unknown): number => { const num = Math.round(Number(value)) if (!Number.isFinite(num)) return 0 return Math.min(BEAUTY_LEVEL_MAX, Math.max(0, num)) } const clampPercent = (value: unknown, fallback: number): number => { const num = Math.round(Number(value)) if (!Number.isFinite(num)) return fallback return Math.min(100, Math.max(0, num)) } const normalizeHexColor = (value: unknown, fallback: string): string => { if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) return value return fallback } /** 高级美颜是否有任一效果实际开启 */ export function hasAnyAdvancedEffect(advanced: AdvancedBeautySettings): boolean { return ( advanced.enabled && (advanced.slimStrength > 0 || advanced.lipstickStrength > 0 || advanced.blushStrength > 0) ) } /** 全部美颜(基础 + 高级)是否有任一效果实际开启(决定是否接管摄像头流) */ export function hasAnyBeautyEffect(settings: CallBeautySettings): boolean { const basicOn = settings.enabled && (settings.beautyLevel > 0 || settings.whitenessLevel > 0 || settings.ruddinessLevel > 0) return basicOn || hasAnyAdvancedEffect(settings.advanced) } export function loadBeautySettings(): CallBeautySettings { const defaults = defaultBeautySettings() try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return defaults const parsed = JSON.parse(raw) const advDefaults = defaults.advanced const adv = parsed?.advanced ?? {} return { enabled: Boolean(parsed?.enabled), style: parsed?.style === BEAUTY_STYLE_SMOOTH ? BEAUTY_STYLE_SMOOTH : BEAUTY_STYLE_NATURE, beautyLevel: clampLevel(parsed?.beautyLevel ?? defaults.beautyLevel), whitenessLevel: clampLevel(parsed?.whitenessLevel ?? defaults.whitenessLevel), ruddinessLevel: clampLevel(parsed?.ruddinessLevel ?? defaults.ruddinessLevel), advanced: { enabled: Boolean(adv?.enabled), slimStrength: clampPercent(adv?.slimStrength, advDefaults.slimStrength), lipstickStrength: clampPercent(adv?.lipstickStrength, advDefaults.lipstickStrength), lipstickColor: normalizeHexColor(adv?.lipstickColor, advDefaults.lipstickColor), blushStrength: clampPercent(adv?.blushStrength, advDefaults.blushStrength), blushColor: normalizeHexColor(adv?.blushColor, advDefaults.blushColor) } } } catch { return defaults } } export function saveBeautySettings(settings: CallBeautySettings) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)) } catch { // localStorage 不可用时静默忽略,仅本次会话生效 } } /** * 将美颜设置应用到 TUICallEngine 实例。 * 引擎内部对 setBeautyLevel 自带 try-catch(摄像头未开启等场景不会抛错), * 这里只兜底引擎不存在 / 接口缺失的情况。 */ export async function applyBeautyToEngine( engine: any, settings: CallBeautySettings ): Promise { if (!engine || typeof engine.setBeautyLevel !== 'function') { return false } const enabled = settings.enabled try { await engine.setBeautyLevel({ style: settings.style === BEAUTY_STYLE_SMOOTH ? BEAUTY_STYLE_SMOOTH : BEAUTY_STYLE_NATURE, beautyLevel: enabled ? clampLevel(settings.beautyLevel) : 0, whitenessLevel: enabled ? clampLevel(settings.whitenessLevel) : 0, ruddinessLevel: enabled ? clampLevel(settings.ruddinessLevel) : 0 }) return true } catch (error) { console.warn('[call-beauty] 应用美颜设置失败', error) return false } }