魔珐星云数字人SDK接入教程
·
1、魔珐星云官网
https://www.xingyun3d.com/
进入官网后点击登陆注册
2、输入邀请码
XDKAT457YU
输入邀请码会赠送1000积分使用
3、创建应用

4、任意选择一个,然后保存

5、保存APP信息
点击接入SDK
保存这两个信息
6、代码中接入SDK
下面这个代码可以集成在自己网页中需要的位置,也可以单独运行,是一个简单的Demo。
在代码的258行和259行“配置区:换成你的应用凭证”,填入上一步保存的凭证即可。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>魔珐星云 · 数字人播报控制台</title>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--ink:#0b1417; --panel:#111e23; --panel2:#0e191d;
--line:rgba(110,175,185,.16); --line-strong:rgba(110,175,185,.32);
--amber:#ffb454; --cyan:#4dd6c1; --green:#5ad68d; --red:#ff6b6b;
--text:#d9e7ea; --dim:#7e959d;
--display:'Chakra Petch',sans-serif;
--body:'Noto Sans SC',system-ui,sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
body{
font-family:var(--body); color:var(--text); min-height:100vh;
background:
radial-gradient(900px 600px at 8% -10%, rgba(255,180,84,.07), transparent 60%),
radial-gradient(1000px 700px at 105% 110%, rgba(77,214,193,.08), transparent 60%),
linear-gradient(rgba(110,175,185,.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(110,175,185,.045) 1px, transparent 1px),
var(--ink);
background-size:auto,auto,44px 44px,44px 44px,auto;
}
/* ── 顶栏 ─────────────────────────── */
header{
display:flex;align-items:center;justify-content:space-between;
padding:14px 28px;border-bottom:1px solid var(--line);
background:rgba(14,25,29,.7);backdrop-filter:blur(6px);
position:sticky;top:0;z-index:10;
}
.brand{display:flex;align-items:center;gap:12px}
.brand .dot{width:10px;height:10px;background:var(--green);border-radius:50%;
box-shadow:0 0 10px var(--green);animation:pulse 2s infinite}
.brand h1{font-family:var(--display);font-size:17px;letter-spacing:.12em;font-weight:600}
.brand h1 em{font-style:normal;color:var(--amber)}
.brand small{display:block;font-size:11px;color:var(--dim);letter-spacing:.28em;font-weight:400}
.top-right{display:flex;align-items:center;gap:18px;font-family:var(--display)}
#clock{font-size:15px;color:var(--cyan);letter-spacing:.1em}
.led{display:flex;align-items:center;gap:7px;font-size:11px;letter-spacing:.2em;color:var(--dim)}
.led i{width:8px;height:8px;border-radius:50%;background:#3a4d52;transition:.3s}
.led.on i{background:var(--green);box-shadow:0 0 8px var(--green)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
/* ── 主区布局 ─────────────────────── */
main{display:grid;grid-template-columns:minmax(320px,440px) minmax(0,1fr);
gap:26px;padding:26px 28px;max-width:1280px;margin:0 auto;align-items:start}
@media(max-width:900px){main{grid-template-columns:1fr}}
/* ── 舞台(数字人监视器) ──────────── */
.stage-wrap{position:sticky;top:80px}
@media(max-width:900px){.stage-wrap{position:static}}
.stage{
position:relative;aspect-ratio:9/16;max-height:78vh;width:100%;
background:var(--panel2);border:1px solid var(--line-strong);
box-shadow:0 24px 60px rgba(0,0,0,.5), inset 0 0 80px rgba(0,0,0,.5);
overflow:hidden;
}
.stage .corner{position:absolute;width:22px;height:22px;border:2px solid var(--amber);z-index:5;transition:.3s}
.corner.tl{top:8px;left:8px;border-right:0;border-bottom:0}
.corner.tr{top:8px;right:8px;border-left:0;border-bottom:0}
.corner.bl{bottom:8px;left:8px;border-right:0;border-top:0}
.corner.br{bottom:8px;right:8px;border-left:0;border-top:0}
.stage:hover .corner{width:30px;height:30px}
.scanlines{position:absolute;inset:0;z-index:4;pointer-events:none;opacity:.5;
background:repeating-linear-gradient(0deg,rgba(255,255,255,.022) 0 1px,transparent 1px 3px)}
#sdk{width:100%;height:100%}
.stage-overlay{
position:absolute;inset:0;z-index:6;display:flex;flex-direction:column;
align-items:center;justify-content:center;gap:16px;text-align:center;
background:rgba(10,18,21,.86);transition:opacity .6s;padding:24px;
}
.stage-overlay.hide{opacity:0;pointer-events:none}
.stage-overlay .big{font-family:var(--display);font-size:22px;letter-spacing:.3em;color:var(--cyan)}
.stage-overlay .sub{font-size:12px;color:var(--dim);letter-spacing:.15em}
.pbar{width:70%;height:6px;background:#1a2a30;border:1px solid var(--line);position:relative;overflow:hidden}
.pbar i{position:absolute;inset:0;width:0%;background:linear-gradient(90deg,var(--cyan),var(--amber));transition:width .3s}
.pbar::after{content:'';position:absolute;inset:0;
background:repeating-linear-gradient(45deg,rgba(255,255,255,.12) 0 6px,transparent 6px 12px);
animation:slide 1s linear infinite}
@keyframes slide{to{transform:translateX(17px)}}
#pct{font-family:var(--display);font-size:34px;font-weight:700;color:var(--amber)}
.rec{position:absolute;top:14px;right:40px;z-index:5;display:none;align-items:center;gap:6px;
font-family:var(--display);font-size:11px;letter-spacing:.25em;color:var(--red)}
.rec i{width:9px;height:9px;border-radius:50%;background:var(--red);animation:pulse 1s infinite}
.rec.on{display:flex}
.live-tag{position:absolute;bottom:14px;left:40px;z-index:5;font-family:var(--display);
font-size:10px;letter-spacing:.3em;color:var(--dim)}
.eq{position:absolute;bottom:14px;right:40px;z-index:5;display:none;align-items:flex-end;gap:3px;height:18px}
.eq.on{display:flex}
.eq i{width:4px;background:var(--cyan);animation:eq .7s ease-in-out infinite}
.eq i:nth-child(1){height:40%;animation-delay:0s}
.eq i:nth-child(2){height:90%;animation-delay:.15s}
.eq i:nth-child(3){height:60%;animation-delay:.3s}
.eq i:nth-child(4){height:100%;animation-delay:.1s}
.eq i:nth-child(5){height:50%;animation-delay:.25s}
@keyframes eq{0%,100%{transform:scaleY(.4)}50%{transform:scaleY(1)}}
/* ── 控制台面板 ───────────────────── */
.panel{background:var(--panel);border:1px solid var(--line);padding:20px 22px;margin-bottom:20px;
transition:border-color .3s}
.panel:hover{border-color:var(--line-strong)}
.panel h2{font-family:var(--display);font-size:12px;letter-spacing:.3em;color:var(--dim);
font-weight:600;margin-bottom:16px;display:flex;align-items:center;gap:10px}
.panel h2 b{color:var(--amber);font-weight:700}
.panel h2::after{content:'';flex:1;height:1px;background:var(--line)}
textarea{
width:100%;height:96px;resize:vertical;background:var(--panel2);color:var(--text);
border:1px solid var(--line);padding:12px 14px;font-family:var(--body);font-size:15px;
line-height:1.7;transition:border-color .25s, box-shadow .25s;
}
textarea:focus{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(77,214,193,.12)}
.ta-foot{display:flex;justify-content:space-between;align-items:center;margin-top:8px}
#counter{font-family:var(--display);font-size:11px;color:var(--dim);letter-spacing:.1em}
.hint{font-size:11px;color:var(--dim)}
.hint kbd{background:#1a2a30;border:1px solid var(--line);padding:1px 6px;font-family:var(--display);font-size:10px}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 18px}
.chip{background:transparent;border:1px solid var(--line-strong);color:var(--dim);
padding:6px 14px;font-size:12px;cursor:pointer;font-family:var(--body);
transition:.2s;letter-spacing:.05em}
.chip:hover{border-color:var(--amber);color:var(--amber);transform:translateY(-2px);
box-shadow:0 4px 12px rgba(255,180,84,.15)}
.btn-row{display:flex;gap:12px;flex-wrap:wrap}
button.act{
font-family:var(--display);letter-spacing:.2em;font-size:13px;font-weight:600;
padding:12px 28px;cursor:pointer;border:1px solid;transition:.2s;background:transparent;
}
.btn-speak{background:var(--amber);border-color:var(--amber);color:#1a1206}
.btn-speak:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 20px rgba(255,180,84,.35)}
.btn-speak:disabled{opacity:.35;cursor:not-allowed}
.btn-destroy{border-color:rgba(255,107,107,.5);color:var(--red)}
.btn-destroy:hover{background:rgba(255,107,107,.1);transform:translateY(-2px)}
.btn-reload{border-color:rgba(77,214,193,.5);color:var(--cyan);display:none}
.btn-reload:hover{background:rgba(77,214,193,.1);transform:translateY(-2px)}
.btn-auto{border-color:rgba(90,214,141,.5);color:var(--green)}
.btn-auto:hover:not(:disabled){background:rgba(90,214,141,.1);transform:translateY(-2px)}
.btn-auto:disabled{opacity:.35;cursor:not-allowed}
.btn-auto.running{background:var(--green);border-color:var(--green);color:#061a0c;animation:pulse 1.4s infinite}
.auto-prog{margin-top:12px;font-family:var(--display);font-size:12px;color:var(--dim);letter-spacing:.1em}
.auto-prog b{color:var(--green)}
button.act:active{transform:translateY(0) scale(.98)}
.shake{animation:shake .4s}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}
.gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px}
.gauge{background:var(--panel2);border:1px solid var(--line);padding:14px 16px;transition:.25s}
.gauge:hover{border-color:var(--line-strong);transform:translateY(-2px)}
.gauge label{display:block;font-size:10px;letter-spacing:.25em;color:var(--dim);margin-bottom:8px}
.gauge .val{font-family:var(--display);font-size:24px;font-weight:700;color:var(--text);line-height:1}
.gauge .val small{font-size:11px;color:var(--dim);font-weight:400;margin-left:4px}
.gauge .val.ok{color:var(--green)} .gauge .val.warn{color:var(--amber)} .gauge .val.bad{color:var(--red)}
.gauge.speaking .val{color:var(--cyan)}
#log{
height:200px;overflow-y:auto;background:var(--panel2);border:1px solid var(--line);
padding:12px 14px;font-family:ui-monospace,'Courier New',monospace;font-size:12px;line-height:1.9;
}
#log::-webkit-scrollbar{width:6px}
#log::-webkit-scrollbar-thumb{background:#22363d}
.log-line{animation:login .3s ease-out}
@keyframes login{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}
.log-line time{color:#4a616a;margin-right:10px}
.log-info{color:var(--cyan)} .log-ok{color:var(--green)}
.log-warn{color:var(--amber)} .log-err{color:var(--red)} .log-sys{color:var(--dim)}
footer{padding:14px 28px 30px;text-align:center;font-size:11px;color:#4a616a;letter-spacing:.1em}
footer b{color:var(--amber)}
</style>
</head>
<body>
<header>
<div class="brand">
<span class="dot"></span>
<h1>XMOV <em>NEBULA</em><small>魔珐星云 · 数字人播报控制台</small></h1>
</div>
<div class="top-right">
<div class="led" id="connLed"><i></i><span>SESSION</span></div>
<div id="clock">--:--:--</div>
</div>
</header>
<main>
<!-- 左:数字人舞台 -->
<div class="stage-wrap">
<div class="stage">
<span class="corner tl"></span><span class="corner tr"></span>
<span class="corner bl"></span><span class="corner br"></span>
<div id="sdk"></div>
<div class="scanlines"></div>
<div class="rec" id="rec"><i></i>SPEAKING</div>
<div class="live-tag">LIVE · XINGYUN3D</div>
<div class="eq" id="eq"><i></i><i></i><i></i><i></i><i></i></div>
<div class="stage-overlay" id="overlay">
<div class="big" id="ovTitle">INITIALIZING</div>
<div class="sub" id="ovSub">正在连接魔珐星云并下载数字人资源…</div>
<div id="pct">0%</div>
<div class="pbar"><i id="pfill"></i></div>
</div>
</div>
</div>
<!-- 右:控制台 -->
<div>
<section class="panel">
<h2><b>01</b> 播报控制 / SPEAK</h2>
<textarea id="txt" maxlength="200" placeholder="输入要让数字人说的话…">欢迎使用魔珐星云,我是你的数字人助手。</textarea>
<div class="ta-foot">
<span class="hint"><kbd>Ctrl</kbd> + <kbd>Enter</kbd> 快速播报</span>
<span id="counter">0 / 200</span>
</div>
<div class="chips">
<button class="chip">欢迎使用魔珐星云</button>
<button class="chip">你好,很高兴为您服务</button>
<button class="chip">今天天气不错,适合出去走走</button>
<button class="chip">感谢观看,我们下次再见</button>
</div>
<div class="btn-row">
<button class="act btn-speak" id="btnSpeak" disabled>▶ 立即播报</button>
<button class="act btn-auto" id="btnAuto" disabled>↻ 自动播报</button>
<button class="act btn-destroy" id="btnDestroy">■ 断开连接</button>
<button class="act btn-reload" id="btnReload">↻ 重新连接</button>
</div>
<div class="auto-prog" id="autoProg">自动播报:未开始(共100条)</div>
</section>
<section class="panel">
<h2><b>02</b> 运行状态 / STATUS</h2>
<div class="gauges">
<div class="gauge"><label>会话状态</label><div class="val" id="gStatus" style="font-size:17px">连接中…</div></div>
<div class="gauge" id="gVoiceBox"><label>语音状态</label><div class="val" id="gVoice" style="font-size:17px">待机</div></div>
<div class="gauge"><label>网络延迟</label><div class="val" id="gRtt">--<small>ms</small></div></div>
<div class="gauge"><label>下载带宽</label><div class="val" id="gDl">--<small>MB/s</small></div></div>
</div>
</section>
<section class="panel">
<h2><b>03</b> 系统日志 / LOG</h2>
<div id="log"></div>
</section>
</div>
</main>
<footer>⚠ 安全提示:App Secret 已暴露在源码中,仅适合本地测试 · 正式环境请通过后端代理调用并<b>重新生成密钥</b></footer>
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
<script>
/* ══════════ 配置区(换密钥改这里) ══════════ */
const CONFIG = {
appId: 'e345b90e891844f69cea27a6938328ca',
appSecret: '2d2722746b184e51ba883f66980a9593',
gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
};
/* ══════════ 工具 ══════════ */
const $ = id => document.getElementById(id);
const STATUS_MAP = {0:'在线',1:'离线',2:'网络恢复',3:'网络断开',4:'已关闭',5:'隐身',6:'可见',7:'已停止'};
const ERR_MAP = {10001:'容器不存在',10002:'Socket连接错误',10003:'会话启动失败',10004:'会话停止失败',
20001:'视频抽帧错误',20002:'Worker初始化错误',20003:'视频流处理错误',20004:'表情处理错误',
30001:'背景图加载错误',30002:'表情数据加载错误',30003:'body数据异常',30004:'视频下载错误',
40001:'音频解码错误',40002:'表情解码错误',40003:'视频解码错误',40004:'事件解码错误',
40005:'数据结构错误',40006:'TTSA下行异常',50001:'离线',50002:'在线',50003:'网络重试',50004:'网络断开'};
function log(msg, type='sys'){
const box = $('log'), line = document.createElement('div');
line.className = 'log-line log-' + type;
line.innerHTML = `<time>${new Date().toTimeString().slice(0,8)}</time>${msg}`;
box.appendChild(line);
box.scrollTop = box.scrollHeight;
}
let ready = false;
let speaking = false;
/* ══════════ 时钟 ══════════ */
setInterval(() => $('clock').textContent = new Date().toTimeString().slice(0,8), 1000);
/* ══════════ 创建 SDK 实例 ══════════ */
if (typeof XmovAvatar === 'undefined') {
$('ovTitle').textContent = 'SDK LOAD FAILED';
$('ovSub').textContent = '无法加载SDK脚本,请检查网络后刷新';
log('SDK脚本加载失败(可能是网络问题)', 'err');
} else {
const sdk = new XmovAvatar({
containerId: '#sdk',
appId: CONFIG.appId,
appSecret: CONFIG.appSecret,
gatewayServer: CONFIG.gatewayServer,
hardwareAcceleration: 'prefer-hardware',
onMessage(m){
const label = ERR_MAP[m.code] ? `(${ERR_MAP[m.code]})` : '';
const type = m.code >= 40000 ? 'err' : (m.code >= 10000 ? 'warn' : 'info');
log(`SDK消息 #${m.code}${label}:${m.message}`, type);
},
onStateChange(s){ log(`数字人状态 → ${s}`, 'info'); },
onStatusChange(s){
const name = STATUS_MAP[s] ?? ('未知(' + s + ')');
$('gStatus').textContent = name;
log(`会话状态 → ${name}`, s === 0 || s === 6 ? 'ok' : 'warn');
$('connLed').classList.toggle('on', s === 0 || s === 2 || s === 6);
},
onVoiceStateChange(v){
speaking = v === 'start';
$('gVoice').textContent = speaking ? '播报中' : '待机';
$('gVoiceBox').classList.toggle('speaking', speaking);
$('rec').classList.toggle('on', speaking);
$('eq').classList.toggle('on', speaking);
log(speaking ? '🔊 开始播报' : '播报结束', speaking ? 'ok' : 'sys');
},
onNetworkInfo(n){
const rtt = $('gRtt');
rtt.innerHTML = n.rtt + '<small>ms</small>';
rtt.className = 'val ' + (n.rtt < 150 ? 'ok' : n.rtt < 300 ? 'warn' : 'bad');
$('gDl').innerHTML = n.downlink + '<small>MB/s</small>';
},
onStateRenderChange(s, d){ log(`状态 ${s} 首帧渲染耗时 ${d}ms`, 'sys'); },
onStartSessionWarning(w){ log('配置警告:' + JSON.stringify(w), 'warn'); },
onWidgetEvent(d){ log('Widget事件:' + JSON.stringify(d), 'sys'); },
enableLogger: false,
});
/* ══════════ 初始化连接 ══════════ */
log('正在创建会话…', 'info');
sdk.init({
initModel: 'normal',
onDownloadProgress(p){
$('pfill').style.width = p + '%';
$('pct').textContent = Math.floor(p) + '%';
if (p >= 100 && !ready){
ready = true;
$('overlay').classList.add('hide');
$('btnSpeak').disabled = false;
$('btnAuto').disabled = false;
log('✅ 资源加载完成,数字人就绪!', 'ok');
}
}
});
/* ══════════ 交互 ══════════ */
const txt = $('txt');
const updateCounter = () => $('counter').textContent = `${txt.value.length} / 200`;
txt.addEventListener('input', updateCounter); updateCounter();
function doSpeak(){
const text = txt.value.trim();
if (!ready){
log('数字人还未就绪,请稍候…', 'warn');
txt.classList.add('shake'); setTimeout(() => txt.classList.remove('shake'), 400);
return;
}
if (!text){ log('播报内容不能为空', 'warn'); return; }
sdk.speak(text, true, true);
log(`发送播报:「${text}」`, 'info');
}
$('btnSpeak').onclick = doSpeak;
txt.addEventListener('keydown', e => { if (e.ctrlKey && e.key === 'Enter') doSpeak(); });
document.querySelectorAll('.chip').forEach(c => c.onclick = () => {
txt.value = c.textContent; updateCounter(); doSpeak();
});
/* ══════════ 自动循环播报(100条不重复) ══════════ */
const AUTO_TEXTS = (() => {
const list = [
'欢迎使用魔珐星云数字人播报系统','今天也是充满希望的一天','愿你被这世界温柔以待',
'读书是最好的独处方式','保持微笑,好运自然会来','每天进步一点点,就是最大的成功',
'健康是人生最大的财富','心怀感恩,所遇皆温柔','生活不止眼前的苟且,还有诗和远方',
'早睡早起,精神百倍','多喝水少熬夜,身体才会好','愿你眼里有光,心中有梦',
'种一棵树最好的时间是十年前,其次是现在','善良是最高级的智慧','今天的努力,是明天的底气',
'与其抱怨,不如改变','岁月不居,时节如流','千里之行,始于足下','一日之计在于晨',
'海纳百川,有容乃大','静以修身,俭以养德','宝剑锋从磨砺出,梅花香自苦寒来',
'业精于勤,荒于嬉','少壮不努力,老大徒伤悲','欲穷千里目,更上一层楼',
'会当凌绝顶,一览众山小','长风破浪会有时,直挂云帆济沧海','山重水复疑无路,柳暗花明又一村',
'不识庐山真面目,只缘身在此山中','春风又绿江南岸,明月何时照我还','科技改变生活,创新引领未来',
'人工智能正在让世界变得更智能','数字人技术让虚拟与现实更近一步','每一次发声,都是科技的温度',
'云端渲染让高清数字人触手可及','愿你的每一天都充满阳光','生活明朗,万物可爱',
'人间值得,未来可期','愿你三冬暖,愿你春不寒','愿你心想事成,万事如意','身体健康,万事顺遂',
'工作顺利,家庭和睦','笑口常开,好运自然来','心若向阳,无谓悲伤','愿所有的美好都如期而至',
'人生没有白走的路,每一步都算数','你只管努力,剩下的交给时间','星光不问赶路人,时光不负有心人',
'越努力,越幸运','做最好的自己,遇见最好的别人',
'春有百花秋有月,夏有凉风冬有雪','落霞与孤鹜齐飞,秋水共长天一色','接天莲叶无穷碧,映日荷花别样红',
'两个黄鹂鸣翠柳,一行白鹭上青天','停车坐爱枫林晚,霜叶红于二月花','柴门闻犬吠,风雪夜归人',
'独在异乡为异客,每逢佳节倍思亲','举头望明月,低头思故乡','海内存知己,天涯若比邻',
'劝君更尽一杯酒,西出阳关无故人','桃花潭水深千尺,不及汪伦送我情','莫愁前路无知己,天下谁人不识君',
'天生我材必有用,千金散尽还复来','抽刀断水水更流,举杯消愁愁更愁','两岸猿声啼不住,轻舟已过万重山',
'飞流直下三千尺,疑是银河落九天','窗含西岭千秋雪,门泊东吴万里船','黄沙百战穿金甲,不破楼兰终不还',
'醉卧沙场君莫笑,古来征战几人回','羌笛何须怨杨柳,春风不度玉门关','日出江花红胜火,春来江水绿如蓝',
'野火烧不尽,春风吹又生','同是天涯沦落人,相逢何必曾相识','在天愿作比翼鸟,在地愿为连理枝',
'天长地久有时尽,此恨绵绵无绝期','曾经沧海难为水,除却巫山不是云','身无彩凤双飞翼,心有灵犀一点通',
'春蚕到死丝方尽,蜡炬成灰泪始干','采菊东篱下,悠然见南山','问君何能尔,心远地自偏',
'此中有真意,欲辨已忘言','盛年不重来,一日难再晨','及时当勉励,岁月不待人','羁鸟恋旧林,池鱼思故渊',
'久在樊笼里,复得返自然','晨兴理荒秽,带月荷锄归','人生无根蒂,飘如陌上尘','落地为兄弟,何必骨肉亲',
'得欢当作乐,斗酒聚比邻','奇文共欣赏,疑义相与析','结庐在人境,而无车马喧',
'问渠那得清如许,为有源头活水来','等闲识得东风面,万紫千红总是春','纸上得来终觉浅,绝知此事要躬行',
'山外青山楼外楼,西湖歌舞几时休','暖风熏得游人醉,直把杭州作汴州','粉骨碎身浑不怕,要留清白在人间',
'千锤万凿出深山,烈火焚烧若等闲','咬定青山不放松,立根原在破岩中','竹外桃花三两枝,春江水暖鸭先知',
];
const uniq = [...new Set(list)];
while (uniq.length < 100) uniq.push(`这是第 ${uniq.length+1} 条播报内容,保持节奏,继续前行`);
return uniq.slice(0, 100);
})();
let autoRunning = false, autoIdx = 0;
const btnAuto = $('btnAuto'), autoProg = $('autoProg');
const wait = ms => new Promise(r => setTimeout(r, ms));
function waitUntil(fn, timeout=60000){
return new Promise(res => {
const start = Date.now();
const t = setInterval(() => {
if (fn()){ clearInterval(t); res(true); }
else if (Date.now() - start > timeout){ clearInterval(t); res(false); }
}, 100);
});
}
async function startAuto(){
if (!ready || autoRunning) return;
autoRunning = true; autoIdx = 0;
btnAuto.classList.add('running');
btnAuto.textContent = '■ 停止自动';
btnSpeak.disabled = true;
log('开始自动循环播报(共100条)', 'info');
while (autoRunning && autoIdx < AUTO_TEXTS.length){
const text = AUTO_TEXTS[autoIdx];
await waitUntil(() => !speaking, 30000); // 等空闲
if (!autoRunning) break;
try { sdk.speak(text, true, true); }
catch(e){ log('speak 异常:' + e.message, 'err'); break; }
autoProg.innerHTML = `自动播报:<b>${autoIdx+1} / 100</b>`;
log(`自动 ${autoIdx+1}/100:${text}`, 'info');
autoIdx++;
await waitUntil(() => speaking, 15000); // 等开始说话
await waitUntil(() => !speaking, 60000); // 等说完
await wait(500); // 句间间隔
}
stopAuto(autoIdx >= AUTO_TEXTS.length);
}
function stopAuto(done){
autoRunning = false;
btnAuto.classList.remove('running');
btnAuto.textContent = '↻ 自动播报';
if (ready) btnSpeak.disabled = false;
autoProg.innerHTML = done
? `✅ 已播报完 100 条`
: `已停止(共播报 ${autoIdx} 条)`;
log(done ? '✅ 100 条全部播报完毕' : '已停止自动播报', done ? 'ok' : 'warn');
}
btnAuto.onclick = () => autoRunning ? stopAuto(false) : startAuto();
$('btnDestroy').onclick = () => {
if (autoRunning) stopAuto(false);
sdk.destroy();
ready = false;
$('btnSpeak').disabled = true;
$('btnAuto').disabled = true;
$('connLed').classList.remove('on');
$('gStatus').textContent = '已停止';
$('ovTitle').textContent = 'SESSION CLOSED';
$('ovSub').textContent = '会话已断开,可重新连接';
$('pct').textContent = '--';
$('overlay').classList.remove('hide');
$('btnReload').style.display = 'inline-block';
log('已销毁实例,断开连接', 'warn');
};
$('btnReload').onclick = () => location.reload();
}
</script>
</body>
</html>
总结
上述是完整的Demo使用步骤,需要进阶的更多玩法,可以看一下官方手册。
https://www.xingyun3d.com/developers/52-183
更多推荐

所有评论(0)