mirror of
https://github.com/HumanAIGC-Engineering/gradio-webrtc.git
synced 2026-02-04 09:29:23 +08:00
本次代码评审新增并完善了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
178 lines
5.5 KiB
TypeScript
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()
|
|
})
|
|
}
|
|
}
|