From c0e3cbd52e79fe8e7ad3f43a5069ae1c828f05b2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 10 Jul 2026 09:23:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/call-beauty-panel/index.vue | 144 +++++- .../src/utils/beauty/face-beauty-pipeline.ts | 453 ++++++++++++++++-- admin/src/utils/call-beauty.ts | 31 +- admin/tsconfig.tsbuildinfo | 2 +- 4 files changed, 569 insertions(+), 61 deletions(-) diff --git a/admin/src/components/call-beauty-panel/index.vue b/admin/src/components/call-beauty-panel/index.vue index 98fa7edb..b2d1c84c 100644 --- a/admin/src/components/call-beauty-panel/index.vue +++ b/admin/src/components/call-beauty-panel/index.vue @@ -94,7 +94,9 @@
- 磨皮 + + 磨皮 +
+
+ + 祛斑 + + +
+
+ + 去痣 + + +
+
+ + 去黑眼圈 + + +
+
+ + 亮度 + + +
+
+ + 清晰度 + + +
@@ -261,7 +323,8 @@ installBeautyMediaInterceptor() const settings = reactive(loadBeautySettings()) const advancedSupported = isAdvancedBeautySupported() -if (settings.advanced.enabled) preloadBeautyModel() +// 面板挂载即预热模型,避免首次打开预览时同步加载 WASM/模型卡死 +if (advancedSupported) preloadBeautyModel() /** 美颜已开启但当前没有活跃通话管线 → 摄像头是在开启前用原始画面打开的 */ const needCameraRestartHint = computed(() => { @@ -304,13 +367,18 @@ const onDragStart = (e: MouseEvent) => { e.preventDefault() } -const openPanel = () => { +const openPanel = async () => { if (visible.value) return windowPos.x = Math.max(16, Math.floor((window.innerWidth - WINDOW_WIDTH) / 2)) windowPos.y = Math.max(16, Math.floor(window.innerHeight * 0.12)) visible.value = true - // 打开面板即自动开启预览,省一次点击 - if (advancedSupported) void startPreview() + // 先预热模型,并等面板先画出「正在打开摄像头」再启预览,避免首开卡死 + if (advancedSupported) { + preloadBeautyModel() + await nextTick() + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + void startPreview() + } } const closePanel = () => { @@ -325,11 +393,15 @@ let applyTimer: ReturnType | null = null /** * 基础美颜下发到 TUICallEngine(TRTC BasicBeauty 插件)。 - * 仅作为 WebGL 不可用时的回退——正常情况下磨皮/美白/红润统一走本地管线, - * 若再叠加引擎插件会造成双重美颜。 + * 仅作为 WebGL 不可用时的回退——正常情况下全部效果走本地管线。 + * WebGL 可用时始终向引擎下发全 0,确保引擎侧插件保持关闭, + * 不会与本地管线叠加,也保证「关闭美颜」时引擎侧残留效果被清掉。 */ const applyNow = async () => { - if (advancedSupported) return + if (advancedSupported) { + await applyBeautyToEngine(props.getEngine(), { ...settings, enabled: false }) + return + } await applyBeautyToEngine(props.getEngine(), settings) } @@ -379,14 +451,22 @@ const startPreview = async () => { previewError.value = '' previewLoading.value = true try { - // 走原始 getUserMedia,避免被拦截器再处理一遍 + // 720p 采集保证预览清晰;管线内部会做适度降采样控制处理开销 const raw = await getRawUserMedia({ - video: { width: { ideal: 1280 }, height: { ideal: 720 } }, + video: { + width: { ideal: 1280 }, + height: { ideal: 720 }, + frameRate: { ideal: 30 } + }, audio: false }) const track = raw.getVideoTracks()[0] if (!track) throw new Error('未获取到摄像头画面') + // 让出一帧,保证 loading 文案先渲染 + await nextTick() + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + previewPipeline = new FaceBeautyPipeline( track, { ...settings, advanced: { ...settings.advanced } }, @@ -443,7 +523,12 @@ const handleReset = () => { style: defaults.style, beautyLevel: defaults.beautyLevel, whitenessLevel: defaults.whitenessLevel, - ruddinessLevel: defaults.ruddinessLevel + ruddinessLevel: defaults.ruddinessLevel, + spotLevel: defaults.spotLevel, + moleLevel: defaults.moleLevel, + darkCircleLevel: defaults.darkCircleLevel, + brightnessLevel: defaults.brightnessLevel, + clarityLevel: defaults.clarityLevel }) } @@ -475,7 +560,8 @@ defineExpose({