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({