魔珐星云-具身智能为大模型赋能实践全流程

150 阅读13分钟

一、前言介绍

AI 从“有大脑”升级到“有身体”,像真人一样自然表达交互! 目前各种大模型快速发展,AI 的“大脑”在某些领域上已经接近真人或者超远真人了,但是就目前而言,AI 并没有拥有过“身体”。 偶然情况下了解到了魔珐星云-具身智能数字人,它可以让大模型不再只是文字或者语音,而是有形象的 AI。抱着好奇心来体验了一下,着实让我十分的震撼! 接下来,就让我给你们介绍一下魔珐星云是什么吧,以及如何使用魔珐星云为你的 AI 赋予“身体”。 魔珐星云官网地址:魔珐星云具身智能3D数字人开放平台 - 全球领先的3D具身智能体基础设施

img

二、魔珐星云初体验

具身驱动

在魔珐星云平台可以通过文本输入,可以实时生成 3D 数字人的语音、表情、眼神、手势和身体动作等,进而达到能够像真人一样自然表达和交互。 交互界面的右下角提供了不同模型的选择。 成功连接后就可以与其进行交流了,除了文本交流外还可以直接进行电话沟通。不得不说这个动漫数字人的建模和声音确实好评,爱了爱了!快来亲自上手体验一下吧!

img

除了二次元数字人外,也提供了很多不同角色的数字人。

img

具身智能交互新生态

img

六大核心能力

魔珐星云具有六大核心能力

  • 高质量 逼真 3D 形象,实时生成自然生动的声音、表情与动作,赋予人物真实可信的表达力。
  • 低延时 500ms 驱动响应,交互实时流畅自然;支持随时打断,贴近真人对话体验。
  • 低成本 百元级芯片即可运行,大幅降低部署门槛,支持大规模普及。
  • 高并发 支持千万级设备同时驱动,轻松应对批量化接入,保障体验稳定可靠。
  • 多风格 覆盖超写实、二次元、卡通、美型等多样角色风格和人设,场景和角色可灵活选择。
  • 多终端 全面适配手机、车机、Pad、PC、电视与大屏,兼容 Android、iOS、鸿蒙等主流系统。

img

魔珐星云通过文生3D多模态大模型和AI端渲和解算,真正的打破了3D数字人生成的质量、成本、延时不可能三角,从而真正的支持了AI具身智能大规模应用!

三、如何进行应用创建、配置、管理和调试

  1. 找到 应用管理,然后点击 创建新应用。

img

  1. 输入你的应用名称以及应用备注,并选择预览模式。

img

  1. 挑选自己喜欢的形象、场景、音色、表演动作。

img

  1. 保存后可以自己进行测试。

选择驱动指令为:ssml 然后可以在中写入自己希望数字人能说的话

img

四、如果使用SDK进行快速配置开发应用

上一步我们已经在魔珐星云平台上创建了自己的应用,接下来,我们就要通过 SDK 的方式快速开发应用。 我们以 JS SDK 为例 Android 版本的同理。 详细文档:具身驱动SDK(JS版本)接入说明

1. 快速开始

1.1 主要功能

  1. 实时 3D 数字人渲染与驱动
  2. 语音合成(SSML 支持)与口型同步
  3. 多状态行为控制(Idle / Listen / Speak 等)
  4. Widget 组件展示(图片、字幕、视频等)
  5. 可自定义事件回调与日志系统

1.2 浏览器版本要求

img

1.3 引入依赖

<!DOCTYPE html>
<html lang="en">
<body>
  <div style="width: 400px;height: 600px">
    <div id="sdk"></div>
  </div>
  <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
</body>
</html>

1.4 创建实例

const LiteSDK = new XmovAvatar({
  containerId: '#sdk',
  appId: '',
  appSecret: '',
  gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
  // 自定义渲染器,传递该方法,所有事件sdk均返回,由该方法定义所以类型事件的实现逻辑
  onWidgetEvent(data) {
    // 处理widget事件
    console.log('Widget事件:', data)
  },
  // 代理渲染器,sdk默认支持subtitle_on、subtitle_off和widget_pic事件。通过代理,
  // 可以修改默认事件,业务侧也可实现各种其他事件。
  proxyWidget: {
    "widget_slideshow": (data: any) => {
      console.log("widget_slideshow", data);
    },
    "widget_video": (data: any) => {
      console.log("widget_video", data);
    },
  },
  onNetworkInfo(networkInfo) {
    console.log('networkInfo:', networkInfo)
  },
  onMessage(message) {
    console.log('SDK message:', message);
  },
  onStateChange(state: string) {
    console.log('SDK State Change:', state);
  },
  onStatusChange(status) {
    console.log('SDK Status Change:', status);
  },
  onStateRenderChange(state: string, duration: number) {
    console.log('SDK State Change Render:', state, duration);
  },
  onVoiceStateChange(status:string) {
      console.log("sdk voice status", status);
  },
  enableLogger: false, // 不展示sdk log,默认为false
})


appId、appSecret 需要去应用中查看

img

1.5 详细参数说明

enum EErrorCode {
  // 容器不存在
  CONTAINER_NOT_FOUND = 10001,
  // socket连接错误
  CONNECT_SOCKET_ERROR = 10002,
  // 会话错误,start_session进入catch(/api/session的接口数据异常,均使用response.error_code)
  START_SESSION_ERROR = 10003,
  // 会话错误,stop_session进入catch
  STOP_SESSION_ERROR = 10004,

  VIDEO_FRAME_EXTRACT_ERROR = 20001, // 视频抽帧错误
  INIT_WORKER_ERROR = 20002, // 初始化视频抽帧WORKER错误
  PROCESS_VIDEO_STREAM_ERROR = 20003, // 抽帧视频流处理错误
  FACE_PROCESSING_ERROR = 20004, // 表情处理错误
  
  BACKGROUND_IMAGE_LOAD_ERROR = 30001, // 背景图片加载错误
  FACE_BIN_LOAD_ERROR = 30002, // 表情数据加载错误
  INVALID_BODY_NAME = 30003, // body数据无Name
  VIDEO_DOWNLOAD_ERROR = 30004, // 视频下载错误

  AUDIO_DECODE_ERROR = 40001, // 音频解码错误
  FACE_DECODE_ERROR = 40002, // 表情解码错误
  VIDEO_DECODE_ERROR = 40003, // 身体视频解码错误
  EVENT_DECODE_ERROR = 40004, // 事件解码错误
  INVALID_DATA_STRUCTURE = 40005, // ttsa返回数据类型错误,非audio、body、face、event等
  TTSA_ERROR = 40006, // ttsa下行发送异常信息

  NETWORK_DOWN = 50001, // 离线模式
  NETWORK_UP = 50002, // 在线模式
  NETWORK_RETRY = 50003, // 网络重试
  NETWORK_BREAK = 50004, // 网络断开
}

interface SDKMessage {
  code: EErrorCode
  message: string
  timestamp: number
  originalError?: string
}

interface SDKNetworkInfo {
  rtt: number // 延迟,毫秒
  downlink: number // 下载速率(MB/s)
}

enum SDKStatus {
  online = 0,
  offline = 1,
  network_on = 2,
  network_off = 3,
  close = 4,
}


1.6 初始化连接房间

初始化 SDK,加载必要资源。

async init({
  onDownloadProgress?: (progress: number) => void,
  onError: (error: SDKError) => void,
  // socket断开
  onClose: () => void,
}): Promise<void>


参数说明:

  • onDownloadProgress:资源下载进度回调,progress取值范围[0, 100],首次连接bin资源或首个视频资源加载失败时,进度均不会达到100,且内部会调用stopSession。防止用户无法重新连接。

1.7 驱动数字人说话

控制虚拟人说话

speak(ssml: string, is_start: boolean, is_end: boolean): void


参数说明:

  • ssml: 可以直接传入需要数字人说的内容,也可以传入SSML格式的标记语言用以指定数字人做出KA动作,详见进阶接入。 非流式调用
speak("欢迎使用魔珐星云", is_start = true, is_end  = true)


1.8 销毁实例

销毁SDK实例,断开连接。

destroy(): void


2. 进阶接入

2.1 状态切换

img

img

  • 语义 KA 指令
<speak>
  热烈
  <ue4event>
    <type>ka_intent</type>
    <data><ka_intent>Welcome</ka_intent></data>
  </ue4event>
  欢迎各位贵宾在百忙之中拨冗莅临指导!您的到来如春风拂面,为我们带来宝贵的经验与智慧,这份肯定与支持让我们备受鼓舞。期待在您的指点下,我们能收获更多成长与启发,共同书写更精彩的篇章!
</speak>


  • 技能 KA 指令
<speak>
<ue4event>
    <type>ka</type>
    <data><action_semantic>dance</action_semantic></data>
  </ue4event>
</speak>


  • Speak KA 指令
<speak>
  <ue4event>
    <type>ka</type>
    <data><action_semantic>Hello</action_semantic></data>
  </ue4event>
  欢迎来到星云具身 3D 数字人平台,这里有超多精彩内容等你发现~
</speak>


2.2 声音控制

setVolume(volume: number): void


2.3 调试信息

# 显示调试信息
showDebugInfo(): void
# 隐藏调试信息
hideDebugInfo(): void


3. 快速使用SDK接入3D数字人

3.1 先看效果

img

进入页面,输入自己创建的应用的 AppID 和 App Secret 点击连接后就可以连接到你在平台上创建的应用了。 可以选择“打招呼”、“聊天气”、“自我介绍”、“祝福语”等一些列的内置内容。 还可以选择状态:待机、聆听。 最下面还可以选择是否展示调试工具。

3.2 完整代码

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>魔珐星云 - 3D数字人Demo</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Microsoft YaHei', sans-serif;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }

        h1 {
            color: #fff;
            margin-bottom: 20px;
            text-shadow: 0 0 10px rgba(100, 200, 255, 0.5);
        }

        .container {
            display: flex;
            gap: 20px;
            flex-wrap: wrap;
            justify-content: center;
            max-width: 1200px;
            width: 100%;
        }

        /* 数字人容器 */
        .avatar-container {
            width: 500px;
            height: 600px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 16px;
            overflow: hidden;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            position: relative;
        }

        #avatar-wrapper {
            width: 100%;
            height: 100%;
        }

        .loading-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.7);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: #fff;
            z-index: 10;
        }

        .loading-overlay.hidden {
            display: none;
        }

        .progress-bar {
            width: 80%;
            height: 8px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 4px;
            margin-top: 15px;
            overflow: hidden;
        }

        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #00d4ff, #7b2cbf);
            border-radius: 4px;
            transition: width 0.3s ease;
            width: 0%;
        }

        /* 控制面板 */
        .control-panel {
            width: 400px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 16px;
            padding: 24px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        }

        .section {
            margin-bottom: 20px;
        }

        .section-title {
            color: #00d4ff;
            font-size: 14px;
            margin-bottom: 12px;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .input-group {
            margin-bottom: 12px;
        }

        .input-group label {
            display: block;
            color: #aaa;
            font-size: 12px;
            margin-bottom: 6px;
        }

        .input-group input {
            width: 100%;
            padding: 10px 14px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 8px;
            background: rgba(255, 255, 255, 0.1);
            color: #fff;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        .input-group input:focus {
            outline: none;
            border-color: #00d4ff;
        }

        textarea {
            width: 100%;
            height: 100px;
            padding: 12px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 8px;
            background: rgba(255, 255, 255, 0.1);
            color: #fff;
            font-size: 14px;
            resize: vertical;
            font-family: inherit;
        }

        textarea:focus {
            outline: none;
            border-color: #00d4ff;
        }

        .btn-group {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }

        .btn {
            flex: 1;
            min-width: 80px;
            padding: 12px 16px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .btn-primary {
            background: linear-gradient(135deg, #00d4ff, #7b2cbf);
            color: #fff;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 15px rgba(0, 212, 255, 0.4);
        }

        .btn-secondary {
            background: rgba(255, 255, 255, 0.2);
            color: #fff;
        }

        .btn-secondary:hover {
            background: rgba(255, 255, 255, 0.3);
        }

        .btn-danger {
            background: #e74c3c;
            color: #fff;
        }

        .btn-danger:hover {
            background: #c0392b;
        }

        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none !important;
        }

        /* 状态指示 */
        .status-bar {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 10px 14px;
            background: rgba(0, 0, 0, 0.3);
            border-radius: 8px;
            margin-bottom: 16px;
        }

        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: #e74c3c;
        }

        .status-dot.connected {
            background: #2ecc71;
            animation: pulse 2s infinite;
        }

        @keyframes pulse {

            0%,
            100% {
                opacity: 1;
            }

            50% {
                opacity: 0.5;
            }
        }

        .status-text {
            color: #fff;
            font-size: 13px;
        }

        /* 快捷短语 */
        .quick-phrases {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-top: 12px;
        }

        .phrase-btn {
            padding: 8px 14px;
            background: rgba(255, 255, 255, 0.15);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 20px;
            color: #fff;
            font-size: 12px;
            cursor: pointer;
            transition: all 0.3s;
        }

        .phrase-btn:hover {
            background: rgba(0, 212, 255, 0.3);
            border-color: #00d4ff;
        }

        /* 音量控制 */
        .volume-control {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .volume-control input[type="range"] {
            flex: 1;
            -webkit-appearance: none;
            height: 6px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 3px;
            outline: none;
        }

        .volume-control input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 16px;
            height: 16px;
            background: #00d4ff;
            border-radius: 50%;
            cursor: pointer;
        }

        .volume-value {
            color: #fff;
            font-size: 12px;
            min-width: 40px;
        }

        /* 日志面板 */
        .log-panel {
            margin-top: 20px;
            max-width: 920px;
            width: 100%;
        }

        .log-content {
            height: 150px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 8px;
            padding: 12px;
            overflow-y: auto;
            font-family: 'Consolas', monospace;
            font-size: 12px;
            color: #0f0;
        }

        .log-entry {
            margin-bottom: 4px;
            padding: 2px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .log-entry.error {
            color: #e74c3c;
        }

        .log-entry.warning {
            color: #f39c12;
        }

        .log-entry.info {
            color: #3498db;
        }
    </style>
</head>

<body>
    <h1>🌟 魔珐星云 3D数字人 Demo</h1>

    <div class="container">
        <!-- 数字人显示区域 -->
        <div class="avatar-container">
            <div id="avatar-wrapper"></div>
            <div class="loading-overlay" id="loading-overlay">
                <div>正在加载数字人资源...</div>
                <div class="progress-bar">
                    <div class="progress-fill" id="progress-fill"></div>
                </div>
                <div id="progress-text" style="margin-top: 10px; font-size: 14px;">0%</div>
            </div>
        </div>

        <!-- 控制面板 -->
        <div class="control-panel">
            <!-- 状态栏 -->
            <div class="status-bar">
                <div class="status-dot" id="status-dot"></div>
                <span class="status-text" id="status-text">未连接</span>
            </div>

            <!-- 配置区域 -->
            <div class="section">
                <div class="section-title">📝 应用配置</div>
                <div class="input-group">
                    <label>App ID</label>
                    <input type="text" id="appId" placeholder="请输入您的 App ID">
                </div>
                <div class="input-group">
                    <label>App Secret</label>
                    <input type="password" id="appSecret" placeholder="请输入您的 App Secret">
                </div>
                <div class="btn-group">
                    <button class="btn btn-primary" id="btn-connect">连接</button>
                    <button class="btn btn-danger" id="btn-disconnect" disabled>断开</button>
                </div>
            </div>

            <!-- 说话控制 -->
            <div class="section">
                <div class="section-title">💬 让数字人说话</div>
                <textarea id="speak-text" placeholder="输入要让数字人说的话...">欢迎使用魔珐星云3D数字人平台!这里有超多精彩内容等你发现。</textarea>
                <div class="btn-group" style="margin-top: 12px;">
                    <button class="btn btn-primary" id="btn-speak" disabled>开始说话</button>
                    <button class="btn btn-secondary" id="btn-stop-speak" disabled>停止</button>
                </div>

                <!-- 快捷短语 -->
                <div class="quick-phrases">
                    <span class="phrase-btn" data-text="你好,很高兴认识你!">👋 打招呼</span>
                    <span class="phrase-btn" data-text="今天天气真不错,适合出去走走。">🌤️ 聊天气</span>
                    <span class="phrase-btn" data-text="我是魔珐星云的3D数字人,有什么可以帮助你的吗?">🤖 自我介绍</span>
                    <span class="phrase-btn" data-text="祝你工作顺利,生活愉快!">🎉 祝福语</span>
                </div>
            </div>

            <!-- 状态切换 -->
            <div class="section">
                <div class="section-title">🎭 状态控制</div>
                <div class="btn-group">
                    <button class="btn btn-secondary" id="btn-idle" disabled>待机</button>
                    <button class="btn btn-secondary" id="btn-listen" disabled>聆听</button>
                </div>
            </div>

            <!-- 音量控制 -->
            <div class="section">
                <div class="section-title">🔊 音量控制</div>
                <div class="volume-control">
                    <span style="color: #aaa;">🔈</span>
                    <input type="range" id="volume-slider" min="0" max="100" value="80">
                    <span class="volume-value" id="volume-value">80%</span>
                </div>
            </div>

            <!-- 调试 -->
            <div class="section">
                <div class="section-title">🔧 调试工具</div>
                <div class="btn-group">
                    <button class="btn btn-secondary" id="btn-show-debug" disabled>显示调试</button>
                    <button class="btn btn-secondary" id="btn-hide-debug" disabled>隐藏调试</button>
                </div>
            </div>
        </div>
    </div>

    <!-- 日志面板 -->
    <div class="log-panel">
        <div class="section-title" style="color: #00d4ff; margin-bottom: 8px;">📋 运行日志</div>
        <div class="log-content" id="log-content"></div>
    </div>

    <!-- 引入魔珐星云SDK -->
    <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>

    <script>
        // ==================== 日志系统 ====================
        const logContent = document.getElementById('log-content');

        function log(message, type = 'info') {
            const time = new Date().toLocaleTimeString();
            const entry = document.createElement('div');
            entry.className = `log-entry ${type}`;
            entry.textContent = `[${time}] ${message}`;
            logContent.appendChild(entry);
            logContent.scrollTop = logContent.scrollHeight;
        }

        // ==================== DOM元素 ====================
        const elements = {
            appId: document.getElementById('appId'),
            appSecret: document.getElementById('appSecret'),
            btnConnect: document.getElementById('btn-connect'),
            btnDisconnect: document.getElementById('btn-disconnect'),
            btnSpeak: document.getElementById('btn-speak'),
            btnStopSpeak: document.getElementById('btn-stop-speak'),
            btnIdle: document.getElementById('btn-idle'),
            btnListen: document.getElementById('btn-listen'),
            btnShowDebug: document.getElementById('btn-show-debug'),
            btnHideDebug: document.getElementById('btn-hide-debug'),
            speakText: document.getElementById('speak-text'),
            volumeSlider: document.getElementById('volume-slider'),
            volumeValue: document.getElementById('volume-value'),
            statusDot: document.getElementById('status-dot'),
            statusText: document.getElementById('status-text'),
            loadingOverlay: document.getElementById('loading-overlay'),
            progressFill: document.getElementById('progress-fill'),
            progressText: document.getElementById('progress-text'),
        };

        // ==================== SDK实例 ====================
        let avatarSDK = null;
        let isConnected = false;

        // ==================== 更新UI状态 ====================
        function updateUIState(connected) {
            isConnected = connected;

            elements.btnConnect.disabled = connected;
            elements.btnDisconnect.disabled = !connected;
            elements.btnSpeak.disabled = !connected;
            elements.btnStopSpeak.disabled = !connected;
            elements.btnIdle.disabled = !connected;
            elements.btnListen.disabled = !connected;
            elements.btnShowDebug.disabled = !connected;
            elements.btnHideDebug.disabled = !connected;

            elements.statusDot.classList.toggle('connected', connected);
            elements.statusText.textContent = connected ? '已连接' : '未连接';
        }

        // ==================== 连接SDK ====================
        async function connectSDK() {
            const appId = elements.appId.value.trim();
            const appSecret = elements.appSecret.value.trim();

            if (!appId || !appSecret) {
                log('请输入 App ID 和 App Secret', 'error');
                alert('请输入 App ID 和 App Secret');
                return;
            }

            log('正在创建SDK实例...', 'info');
            elements.loadingOverlay.classList.remove('hidden');

            try {
                // 创建SDK实例
                avatarSDK = new XmovAvatar({
                    containerId: '#avatar-wrapper',
                    appId: appId,
                    appSecret: appSecret,
                    gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',

                    // Widget事件回调
                    onWidgetEvent(data) {
                        log(`Widget事件: ${JSON.stringify(data)}`, 'info');
                    },

                    // 错误回调
                    onError(error) {
                        log(`错误: ${JSON.stringify(error)}`, 'error');
                    },

                    // 语音状态变化回调
                    onVoiceStateChange(state) {
                        log(`语音状态: ${state}`, 'info');
                        if (state === 'voice_start') {
                            elements.statusText.textContent = '正在说话...';
                        } else if (state === 'voice_end') {
                            elements.statusText.textContent = '已连接';
                        }
                    },

                    // 连接状态回调
                    onConnectionStateChange(state) {
                        log(`连接状态: ${state}`, 'info');
                    }
                });

                log('正在初始化SDK...', 'info');

                // 初始化SDK
                await avatarSDK.init({
                    onDownloadProgress: (progress) => {
                        elements.progressFill.style.width = `${progress}%`;
                        elements.progressText.textContent = `${Math.round(progress)}%`;
                        log(`资源下载进度: ${Math.round(progress)}%`, 'info');
                    }
                });

                elements.loadingOverlay.classList.add('hidden');
                updateUIState(true);
                log('SDK初始化成功!数字人已就绪', 'info');

                // 设置初始音量
                const volume = elements.volumeSlider.value / 100;
                avatarSDK.setVolume(volume);

            } catch (error) {
                elements.loadingOverlay.classList.add('hidden');
                log(`连接失败: ${error.message || error}`, 'error');
                alert('连接失败,请检查 App ID 和 App Secret 是否正确');
            }
        }

        // ==================== 断开连接 ====================
        function disconnectSDK() {
            if (avatarSDK) {
                avatarSDK.destroy();
                avatarSDK = null;
                log('已断开连接', 'warning');
            }
            updateUIState(false);
        }

        // ==================== 让数字人说话 ====================
        function speak() {
            if (!avatarSDK || !isConnected) return;

            const text = elements.speakText.value.trim();
            if (!text) {
                log('请输入要说的内容', 'warning');
                return;
            }

            log(`数字人开始说话: ${text}`, 'info');
            avatarSDK.speak(text, true, true);
        }

        // ==================== 停止说话 ====================
        function stopSpeak() {
            if (!avatarSDK || !isConnected) return;

            // 切换到待机状态来停止说话
            avatarSDK.interactive_idle();
            log('已停止说话', 'info');
        }

        // ==================== 状态切换 ====================
        function setIdle() {
            if (!avatarSDK || !isConnected) return;
            avatarSDK.interactive_idle();
            log('切换到待机状态', 'info');
        }

        function setListen() {
            if (!avatarSDK || !isConnected) return;
            avatarSDK.listen();
            log('切换到聆听状态', 'info');
        }

        // ==================== 音量控制 ====================
        function updateVolume() {
            const volume = elements.volumeSlider.value / 100;
            elements.volumeValue.textContent = `${elements.volumeSlider.value}%`;

            if (avatarSDK && isConnected) {
                avatarSDK.setVolume(volume);
                log(`音量设置为: ${elements.volumeSlider.value}%`, 'info');
            }
        }

        // ==================== 调试工具 ====================
        function showDebug() {
            if (avatarSDK && isConnected) {
                avatarSDK.showDebugInfo();
                log('已显示调试信息', 'info');
            }
        }

        function hideDebug() {
            if (avatarSDK && isConnected) {
                avatarSDK.hideDebugInfo();
                log('已隐藏调试信息', 'info');
            }
        }

        // ==================== 快捷短语 ====================
        function handleQuickPhrase(e) {
            if (e.target.classList.contains('phrase-btn')) {
                const text = e.target.dataset.text;
                elements.speakText.value = text;
                if (isConnected) {
                    speak();
                }
            }
        }

        // ==================== 事件绑定 ====================
        elements.btnConnect.addEventListener('click', connectSDK);
        elements.btnDisconnect.addEventListener('click', disconnectSDK);
        elements.btnSpeak.addEventListener('click', speak);
        elements.btnStopSpeak.addEventListener('click', stopSpeak);
        elements.btnIdle.addEventListener('click', setIdle);
        elements.btnListen.addEventListener('click', setListen);
        elements.btnShowDebug.addEventListener('click', showDebug);
        elements.btnHideDebug.addEventListener('click', hideDebug);
        elements.volumeSlider.addEventListener('input', updateVolume);
        document.querySelector('.quick-phrases').addEventListener('click', handleQuickPhrase);

        // 页面卸载时清理资源
        window.addEventListener('beforeunload', () => {
            if (avatarSDK) {
                avatarSDK.destroy();
            }
        });

        // 初始化日志
        log('页面加载完成,请输入 App ID 和 App Secret 后点击连接', 'info');
        log('提示:请在 https://xingyun3d.com/ 创建应用获取凭证', 'info');
    </script>
</body>

</html>


可以在git仓库中找到哦! github地址:github.com/Worldfickle…

五、常见问题及解决方案

  1. 确保容器元素具有明确的宽高,否则可能影响渲染效果
  2. 初始化时必须提供所有必填参数
  3. 使用destroy()方法时会清理所有资源并断开连接
  4. 建议在开发环境使用showDebugInfo()辅助调试

六、总结

体验下来,魔珐星云的具身智能确实不错,提供了一个 3D 数字人开发的平台,更提供了 SDK 来方便开发者快速开发应用。我们不能可以在星云平台上快速构建具身智能应用,从虚拟陪伴到机器人交互,从桌面小助手到车载交互界面,都可以快速的实现。 如果你也感兴趣的话,那就快来体验一下吧!

可以通过下方专属链接进行体验喔 专属链接:xingyun3d.com/?utm_campai…