Files
gradio-webrtc/frontend/shared/VideoChat/gaussianAvatar.ts
neil.xh f476f9cf29 gs对话接入
本次代码评审新增并完善了gs视频聊天功能,包括前后端接口定义、状态管理及UI组件实现,并引入了新的依赖库以支持更多互动特性。
Link: https://code.alibaba-inc.com/xr-paas/gradio_webrtc/codereview/21273476
* 更新python 部分

* 合并videochat前端部分

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 替换audiowave

* 导入路径修改

* 合并websocket mode逻辑

* feat: gaussian avatar chat

* 增加其他渲染的入参

* feat: ws连接和使用

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 右边距离超出容器宽度,则向左移动

* 配置传递

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 高斯包异常

* 同步webrtc_utils

* 更新webrtc_utils

* 兼容on_chat_datachannel

* 修复设备名称列表没有正常显示的问题

* copy 传递 webrtc_id

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 保证webrtc 完成后再进行websocket连接

* feat: 音频表情数据接入

* dist 上传

* canvas 隐藏

* feat: 高斯文件下载进度透出

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 修改无法获取权限问题

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 先获取权限再获取设备

* fix: gs资源下载完成前不处理ws数据

* fix: merge

* 话术调整

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 修复设备切换后重新对话,又切换回默认设备的问题

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 更新localvideo 尺寸

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 不能默认default

* 修改音频权限问题

* 更新打包结果

* fix: 对话按钮状态跟gs资源挂钩,删除无用代码

* fix: merge

* feat: gs渲染模块从npm包引入

* fix

* 新增对话记录

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 样式修改

* 更新包

* fix: gs数字人初始化位置和静音

* 对话记录滚到底部

* 至少100%高度

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 略微上移文本框

* 开始连接时清空对话记录

* fix: update gs render npm

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 逻辑保证

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* feat: 音频初始化配置是否静音

* actionsbar在有字幕时调整位置

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 样式优化

* feat: 增加readme

* fix: 资源图片

* fix: docs

* fix: update gs render sdk

* fix: gs模式下画面位置计算

* fix: update readme

* 设备判断,太窄处理

* Merge branch 'feature/update-fastrtc-0.0.19' of gitlab.alibaba-inc.com:xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* 是否有权限和是否有设备分开

* feat: gs 下载和加载钩子函数分离

* Merge branch 'feature/update-fastrtc-0.0.19' of http://gitlab.alibaba-inc.com/xr-paas/gradio_webrtc into feature/update-fastrtc-0.0.19

* fix: update gs render sdk

* 替换

* dist

* 上传文件

* del
2025-04-16 19:09:04 +08:00

178 lines
5.5 KiB
TypeScript

import EventEmitter from "eventemitter3";
import { Player } from "./helpers/player";
import { Processor } from "./helpers/processor";
import { type WS } from "./helpers/ws.js";
import { EventTypes, PlayerEventTypes } from "./interface/eventType";
import { TYVoiceChatState } from "./interface/voiceChat";
// import * as GaussianSplats3D from "./gaussian-splats-3d.module.js";
import * as GaussianSplats3D from "gaussian-splat-renderer-for-lam";
import { WsEventTypes } from "./interface/eventType";
interface GaussianOptions {
container: HTMLDivElement
assetsPath: string
ws: WS,
downloadProgress?: (percent: number) => void;
loadProgress?: (percent: number) => void;
}
export class GaussianAvatar extends EventEmitter {
private _avatarDivEle: HTMLDivElement;
private _assetsPath = "";
private _ws: WS;
private _downloadProgress: (percent: number) => void;
private _loadProgress: (percent: number) => void;
private _loadPercent = 0;
private _downloadPercent = 0;
private _processor: Processor;
private _renderer: any;
private _audioMute = false;
public curState = TYVoiceChatState.Idle;
constructor(options: GaussianOptions) {
const { container, assetsPath, ws, downloadProgress, loadProgress } = options
super();
this._avatarDivEle = container;
this._assetsPath = assetsPath;
this._ws = ws;
if (downloadProgress) {
this._downloadProgress = (percent: number) => {
this._downloadPercent = percent;
downloadProgress(percent)
};
} else {
this._downloadProgress = (percent: number) => {
this._downloadPercent = percent;
};
}
if(loadProgress) {
this._loadProgress = (percent: number) => {
this._loadPercent = percent;
loadProgress(percent)
};
} else {
this._loadProgress = (percent: number) => {
this._loadPercent = percent;
};
}
this._init();
}
private _init() {
if (!this._avatarDivEle || !this._assetsPath || !this._ws) {
throw new Error(
"Lack of necessary initialization parameters for gaussian render",
);
}
this._processor = new Processor(this);
this._bindEventTypes();
}
public start() {
this.getData();
this.render();
}
public async getData() {
this._ws.on(WsEventTypes.WS_MESSAGE, (data: Blob) => {
if (this._downloadPercent < 1 || this._loadPercent < 1) { // 本地数字人未加载完成前,不处理数据
return
}
this.emit(EventTypes.MessageReceived, this.curState);
this._processor.add({
avatar_motion_data: {
first_package: true, // 是否首包
segment_num: 1, // 分片数量,首包存在该值
binary_size: data.size, // 数据大小,首包存在该值
use_binary_frame: false, // 是否使用二进制帧,首包存在该值
},
});
this._processor.add({
avatar_motion_data: {
first_package: false,
motion_data_slice: data, // 数据分片,非首包存在该值
is_audio_mute: this._audioMute, // 音频片段是否静音,非首包存在该值
},
});
});
}
public async render() {
this._renderer = await GaussianSplats3D.GaussianSplatRenderer.getInstance(
this._avatarDivEle,
this._assetsPath,
{
getChatState: this.getChatState.bind(this),
getExpressionData: this.getArkitFaceFrame.bind(this),
downloadProgress: this._downloadProgress.bind(this),
loadProgress: this._loadProgress.bind(this),
},
);
}
public setAvatarMute(isMute: boolean) {
this._processor.setMute(isMute);
this._audioMute = isMute;
}
public getChatState() {
return this.curState;
}
public getArkitFaceFrame() {
return this._processor?.getArkitFaceFrame().arkitFace;
}
public interrupt(): void {
this._ws.send("%interrupt%"); // 约定的打断标识
this._processor?.interrupt();
this.curState = TYVoiceChatState.Idle;
this.emit(EventTypes.StateChanged, this.curState);
}
public sendSpeech(data: string | Int8Array | Uint8Array) {
this._ws.send(data);
this.curState = TYVoiceChatState.Listening;
this.emit(EventTypes.StateChanged, this.curState);
this._processor?.clear();
}
public exit() {
this._renderer?.dispose();
this.curState = TYVoiceChatState.Idle;
this._downloadPercent = 0;
this._loadPercent = 0;
this._processor?.clear();
this.removeAllListeners();
}
private _bindEventTypes() {
this.on(PlayerEventTypes.Player_StartSpeaking, (player: Player) => {
console.log('startSpeach')
this.curState = TYVoiceChatState.Responding;
this.emit(EventTypes.StateChanged, this.curState);
this._ws.send(
JSON.stringify({
header: { name: EventTypes.StartSpeech },
payload: {},
}),
);
});
this.on(PlayerEventTypes.Player_EndSpeaking, (player: Player) => {
console.log('endSpeach')
this.curState = TYVoiceChatState.Idle;
this.emit(EventTypes.StateChanged, this.curState);
this._ws.send(
JSON.stringify({ header: { name: EventTypes.EndSpeech }, payload: {} }),
);
});
this.on(EventTypes.ErrorReceived, (data) => {
console.log('ErrorReceived', data)
this.curState = TYVoiceChatState.Idle;
this.emit(EventTypes.StateChanged, this.curState);
this._ws.send(
JSON.stringify({
header: { name: EventTypes.ErrorReceived },
payload: { ...data },
}),
);
});
this._ws.on(WsEventTypes.WS_CLOSE, () => {
this.exit()
})
}
}