------3 行接入 SDK,10 分钟搞定可交互数字人,低配机也能流畅跑。
一、传统数字人:看着智能,实则「哑巴播放器」
实际开发才发现,市面上数字人大屏全是硬伤:
- ⏱ 延迟 3--5 秒,对话完全脱节
- 🎭 表情口型对不上,全程机械脸
- ❌ 不能打断,只能念固定脚本
本质就是:套了层数字人皮的视频播放器,根本不是真正的交互。
二、核心架构:端侧渲染,是真能打的关键
一句话说透:云端算逻辑,端侧渲染数字人,从底层解决延迟和性能问题。
- 只传几十 KB 参数流,不用传视频
- 3D 素材本地缓存,首次加载后秒开
- 百元芯片、集成显卡就能跑,不用贵 GPU
三、实战:10 分钟从零搭建(附完整可运行源码)
3.1 准备工作
- 注册【魔珐星云】开发者账号
- 创建驱动应用:选角色、音色、表演风格 → 拿到 AppID、AppSecret
- CDN 直接引入 SDK(无需 npm)
xml
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
3.2 项目初始化(Vue3+Vite)
bash
# 使用 pnpm 创建项目pnpm create vite xingyun-demo --template vue-ts
cd xingyun-demo
pnpm install
然后在 index.html 的 <body> 中引入SDK:
xml
<body><div id="app"></div><!-- 引入魔珐星云SDK(必须在body中) --><script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script><script type="module" src="/src/main.ts"></script></body>
3.3 封装数字人服务(AvatarService.ts)
SDK的所有交互都围绕一个 XmovAvatar 实例展开。我将它封装成一个单例服务类,方便在整个项目中复用。
创建 src/services/AvatarService.ts:
typescript
/**
* 魔珐星云 SDK 服务封装
* 基于官方文档:https://www.xingyun3d.com/developers/52-183
*/export class AvatarService {private static instance: AvatarService | null = null;private avatar: any = null;private constructor() {}public static getInstance(): AvatarService {if (!AvatarService.instance) {
AvatarService.instance = new AvatarService();}return AvatarService.instance;}/**
* 初始化数字人
* @param containerId 渲染容器ID(如 '#sdk')
* @param appId 驱动应用的 appId
* @param appSecret 驱动应用的 appSecret
*/public async init(containerId: string, appId: string, appSecret: string) {if (this.avatar) return;// 1. 创建实例this.avatar = new (window as any).XmovAvatar({
containerId,
appId,
appSecret,// 网关地址
gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',// 开启硬件加速
hardwareAcceleration: 'prefer-hardware',// 接收SDK消息onMessage(message: any) {console.log('[SDK] onMessage:', message);},// 监听数字人状态变化onStateChange(state: string) {console.log('[SDK] 状态变化:', state);},// 监听语音播放状态onVoiceStateChange(status: string) {console.log('[SDK] 语音状态:', status);// status === 'voice_start' → 开始说话// status === 'voice_end' → 说话结束},// 是否开启日志
enableLogger: true,
});// 2. 初始化连接(异步,会下载3D模型资源)await this.avatar.init({onDownloadProgress: (progress: number) => {console.log(`[SDK] 资源加载: ${progress}%`);},});}/**
* 让数字人说话(非流式)
* 官方API: speak(ssml: string, is_start: boolean, is_end: boolean)
*/public speak(text: string) {this.avatar?.speak(text, true, true);}/**
* 流式说话(对接大模型流式输出)
* 第一句: speak(text, true, false)
* 中间句: speak(text, false, false)
* 最后句: speak(text, false, true)
*/public speakStream(text: string, isStart: boolean, isEnd: boolean) {this.avatar?.speak(text, isStart, isEnd);}// === 状态切换 ===public idle() { this.avatar?.idle(); } // 待机等待public interactiveIdle() { this.avatar?.interactiveidle(); } // 待机互动(也可用于打断)public listen() { this.avatar?.listen(); } // 倾听状态public think() { this.avatar?.think(); } // 思考状态// === 其他工具 ===public setVolume(v: number) { this.avatar?.setVolume(v); } // 音量 0~1public showDebugInfo() { this.avatar?.showDebugInfo(); } // 显示调试面板public hideDebugInfo() { this.avatar?.hideDebugInfo(); } // 隐藏调试面板/** 销毁实例,释放资源(页面离开时必须调用) */public destroy() {this.avatar?.destroy();this.avatar = null;}}export const avatarService = AvatarService.getInstance();
3.4 页面组件(AvatarScreen.vue)
创建 src/components/AvatarScreen.vue:
xml
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { avatarService } from '../services/AvatarService'
const isInitialized = ref(false)
const isLoading = ref(false)
const inputText = ref('你好,欢迎使用魔珐星云数字人助手!')
// 从 .env 读取(Vite 要求 VITE_ 前缀)
const APP_ID = import.meta.env.VITE_XINGYUN_APP_ID
const APP_SECRET = import.meta.env.VITE_XINGYUN_APP_SECRET
const initAvatar = async () => {
isLoading.value = true
try {
// 容器ID对应页面中的 <div id="sdk">
await avatarService.init('#sdk', APP_ID, APP_SECRET)
isInitialized.value = true
} catch (e) {
console.error('初始化失败:', e)
alert('初始化失败,请检查控制台')
} finally {
isLoading.value = false
}
}
// 非流式播报
const handleSpeak = () => {
if (!inputText.value) return
avatarService.speak(inputText.value)
}
// 页面卸载时销毁实例
onUnmounted(() => avatarService.destroy())
</script>
<template>
<div class="container">
<h1>魔珐星云 AI 屏幕助手</h1>
<!-- 容器必须有明确的宽高,否则无法渲染 -->
<div id="sdk" style="width: 540px; height: 960px;"></div>
<button v-if="!isInitialized" @click="initAvatar" :disabled="isLoading">
{{ isLoading ? '加载中...' : '初始化数字人' }}
</button>
<div v-if="isInitialized">
<textarea v-model="inputText" placeholder="输入文本..."></textarea>
<button @click="handleSpeak">让TA说</button>
<button @click="avatarService.idle()">待机</button>
<button @click="avatarService.listen()">倾听</button>
<button @click="avatarService.think()">思考</button>
<button @click="avatarService.interactiveIdle()">打断</button>
</div>
</div>
</template>
3.5 环境变量 .env
创建 .env 文件(不要提交到Git):
ini
VITE_XINGYUN_APP_ID=你的AppID
VITE_XINGYUN_APP_SECRET=你的AppSecret
3.6 运行


pnpm dev
打开 http://localhost:5173,10 分钟跑通完整交互 Demo。
四、流式对接:边生成边说话(大模型必备)
4.7 实测结果
- 语音播报
经过在 onVoiceStateChange 和 speak 的方法进行语音的监听,在点击播报按钮与语音播报的时间基本稳定在 1000ms 左右,速度是可以的。

- 动作表情
经过在 onStateChange 和 idle、interactiveIdle、listen、think 方法中进行埋点,在点击切换状态时的延迟统计如下,少数在1s以内,大多均在2s左右。

五、关键技术解析
5.1 性能实测:
在开发 Demo 时,我通过监听 speak() 调用到 onVoiceStateChange(start) 事件,实测了 "从点击播报到数字人开口" 的真实延迟。
实测结果:
- 稳定在 900ms - 1100ms 之间(公网环境)。
- 对比传统方案:传统视频流驱动方案通常需要 3000ms 以上,星云在响应速度上快了近 3 倍。
为什么是 1000ms 左右? 这 1 秒钟内,SDK 完成了网络往返、云端 TTS 实时解算以及端侧为了保证播放流畅预留的微量缓冲(Buffer)。
5.2 speak 的流式调用:对接大模型
这是实际开发中最常用的模式。大模型(如豆包、通义千问)是流式输出的,你不需要等它全部生成完再让数字人开口。
css
// 模拟大模型流式输出const chunks = ['今天天气不错,', '适合出去走走,', '你有什么计划吗?'];for (let i = 0; i < chunks.length; i++) {const isStart = (i === 0);const isEnd = (i === chunks.length - 1);
avatarService.speakStream(chunks[i], isStart, isEnd);}
关键规则:
- 第一句:
is_start = true - 最后一句:
is_end = true - 中间所有句子:
is_start = false, is_end = false - 一段 speak 结束后,必须先调用
interactiveIdle()或listen()切换状态,才能开始下一段 speak
5.3 SSML:让数字人做动作

星云支持通过 SSML 标记语言,在说话的同时触发预设动作(KA,Key Action):
xml
// 让数字人一边挥手打招呼,一边说欢迎语const ssml = `<speak>
<ue4event>
<type>ka</type>
<data><action_semantic>invite01</action_semantic></data>
</ue4event>
欢迎来到星云具身 3D 数字人平台,这里有超多精彩内容等你发现~
</speak>`;
avatarService.speak(ssml);
你可以通过 KA查询接口 获取当前角色支持的所有动作列表,比如 Welcome(欢迎)、dance(跳舞)等。示例如下:

通过 devtools 观察,当切换状态时,第一次加载会请求若干 mp4 表情动作素材,大小均在 100kb 左右,加载时间100ms。

后续的拉取则会直接走缓存 disk cache ,耗时基本10ms 左右。

总体而言,通过素材下载,本地渲染模式,动作流畅度也会显著提升,效果很好。
5.4 端侧渲染的硬件要求
星云SDK支持多平台,而且对硬件要求出乎意料地低:
| 平台 | 部署方式 | 硬件要求 |
| Web(PC/移动端) |