ruiyun-human 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -3
- package/dist/ruiyun-human.cjs.js +264 -143
- package/dist/ruiyun-human.es.js +265 -144
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/ruiyun-human.cjs.js
CHANGED
|
@@ -1,93 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
const vue = require("vue");
|
|
4
|
-
const _export_sfc = (sfc, props) => {
|
|
5
|
-
const target = sfc.__vccOpts || sfc;
|
|
6
|
-
for (const [key, val] of props) {
|
|
7
|
-
target[key] = val;
|
|
8
|
-
}
|
|
9
|
-
return target;
|
|
10
|
-
};
|
|
11
|
-
const _sfc_main$2 = {
|
|
12
|
-
__name: "index",
|
|
13
|
-
props: {
|
|
14
|
-
type: {
|
|
15
|
-
type: String,
|
|
16
|
-
default: "primary"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
emits: ["click"],
|
|
20
|
-
setup(__props) {
|
|
21
|
-
return (_ctx, _cache) => {
|
|
22
|
-
return vue.openBlock(), vue.createElementBlock("button", {
|
|
23
|
-
class: vue.normalizeClass(["demo-btn", __props.type]),
|
|
24
|
-
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click"))
|
|
25
|
-
}, [
|
|
26
|
-
vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
|
|
27
|
-
], 2);
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
const DemoButton = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-9a48516a"]]);
|
|
32
|
-
DemoButton.install = (app) => app.component("DemoButton", DemoButton);
|
|
33
|
-
const _hoisted_1 = { class: "demo-input-wrap" };
|
|
34
|
-
const _hoisted_2 = {
|
|
35
|
-
key: 0,
|
|
36
|
-
class: "prefix"
|
|
37
|
-
};
|
|
38
|
-
const _hoisted_3 = ["type", "placeholder", "value", "disabled"];
|
|
39
|
-
const _hoisted_4 = {
|
|
40
|
-
key: 1,
|
|
41
|
-
class: "suffix"
|
|
42
|
-
};
|
|
43
|
-
const _sfc_main$1 = {
|
|
44
|
-
__name: "index",
|
|
45
|
-
props: {
|
|
46
|
-
modelValue: {
|
|
47
|
-
type: [String, Number],
|
|
48
|
-
default: ""
|
|
49
|
-
},
|
|
50
|
-
placeholder: {
|
|
51
|
-
type: String,
|
|
52
|
-
default: "请输入内容"
|
|
53
|
-
},
|
|
54
|
-
inputType: {
|
|
55
|
-
type: String,
|
|
56
|
-
default: "text"
|
|
57
|
-
},
|
|
58
|
-
disabled: {
|
|
59
|
-
type: Boolean,
|
|
60
|
-
default: false
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
emits: ["update:modelValue", "focus", "blur"],
|
|
64
|
-
setup(__props, { emit: __emit }) {
|
|
65
|
-
const emit = __emit;
|
|
66
|
-
const handleInput = (e) => emit("update:modelValue", e.target.value);
|
|
67
|
-
return (_ctx, _cache) => {
|
|
68
|
-
return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
|
|
69
|
-
_ctx.$slots.prefix ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_2, [
|
|
70
|
-
vue.renderSlot(_ctx.$slots, "prefix", {}, void 0, true)
|
|
71
|
-
])) : vue.createCommentVNode("", true),
|
|
72
|
-
vue.createElementVNode("input", {
|
|
73
|
-
class: "demo-input",
|
|
74
|
-
type: __props.inputType,
|
|
75
|
-
placeholder: __props.placeholder,
|
|
76
|
-
value: __props.modelValue,
|
|
77
|
-
disabled: __props.disabled,
|
|
78
|
-
onInput: handleInput,
|
|
79
|
-
onFocus: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("focus")),
|
|
80
|
-
onBlur: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("blur"))
|
|
81
|
-
}, null, 40, _hoisted_3),
|
|
82
|
-
_ctx.$slots.suffix ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_4, [
|
|
83
|
-
vue.renderSlot(_ctx.$slots, "suffix", {}, void 0, true)
|
|
84
|
-
])) : vue.createCommentVNode("", true)
|
|
85
|
-
]);
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
const DemoInput = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-28a3493c"]]);
|
|
90
|
-
DemoInput.install = (app) => app.component("DemoInput", DemoInput);
|
|
91
4
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
92
5
|
function getDefaultExportFromCjs(x) {
|
|
93
6
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
@@ -385,6 +298,13 @@ var recorder = { exports: {} };
|
|
|
385
298
|
var recorderExports = recorder.exports;
|
|
386
299
|
var jsAudioRecorder = recorderExports;
|
|
387
300
|
const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
|
|
301
|
+
const _export_sfc = (sfc, props) => {
|
|
302
|
+
const target = sfc.__vccOpts || sfc;
|
|
303
|
+
for (const [key, val] of props) {
|
|
304
|
+
target[key] = val;
|
|
305
|
+
}
|
|
306
|
+
return target;
|
|
307
|
+
};
|
|
388
308
|
const VOICE_CLIENT_ID = "e5cd2b";
|
|
389
309
|
const VOICE_TENANT_ID = "000000";
|
|
390
310
|
const TTS_USER = "web-test-user";
|
|
@@ -399,15 +319,28 @@ const _sfc_main = {
|
|
|
399
319
|
},
|
|
400
320
|
szraddress: {
|
|
401
321
|
type: String,
|
|
322
|
+
// http://192.168.3.136:6039
|
|
402
323
|
default: "https://sqsyky.xzspj.suqian.gov.cn:8443"
|
|
403
324
|
}
|
|
404
325
|
},
|
|
405
|
-
emits: [
|
|
326
|
+
emits: [
|
|
327
|
+
"loaded",
|
|
328
|
+
// 画面就绪:待机帧加载完并完成首次绘制
|
|
329
|
+
"frames-loaded",
|
|
330
|
+
// 全部帧(待机 + 说话)加载完成
|
|
331
|
+
"load-error",
|
|
332
|
+
// 待机帧全部不可用,画面出不来
|
|
333
|
+
"changed",
|
|
334
|
+
"paused",
|
|
335
|
+
"resumed",
|
|
336
|
+
"audio-ready"
|
|
337
|
+
],
|
|
406
338
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
407
339
|
const props = __props;
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
340
|
+
const normalizeBase = (url) => String(url || "").replace(/\/+$/, "");
|
|
341
|
+
const TTS_URL = vue.computed(() => normalizeBase(props.szraddress));
|
|
342
|
+
const ASR_URL = vue.computed(() => normalizeBase(props.szraddress));
|
|
343
|
+
const szrUrl = vue.computed(() => `${normalizeBase(props.szraddress)}/szrimage`);
|
|
411
344
|
const buildPresets = (rootUrl) => ({
|
|
412
345
|
1: {
|
|
413
346
|
humanId: 1,
|
|
@@ -486,10 +419,19 @@ const _sfc_main = {
|
|
|
486
419
|
const canvasRef = vue.ref(null);
|
|
487
420
|
const ctx = vue.ref(null);
|
|
488
421
|
const currentHuman = vue.ref(null);
|
|
422
|
+
const isMutedNow = vue.computed(() => {
|
|
423
|
+
var _a;
|
|
424
|
+
const raw = props.human;
|
|
425
|
+
if (raw && typeof raw === "object" && typeof raw.isMuted === "boolean") return raw.isMuted;
|
|
426
|
+
return ((_a = currentHuman.value) == null ? void 0 : _a.isMuted) ?? false;
|
|
427
|
+
});
|
|
489
428
|
const animationId = vue.ref(null);
|
|
490
429
|
const frameIndex = vue.ref(1);
|
|
491
430
|
const timestamp = vue.ref(null);
|
|
492
431
|
const frames = vue.ref({ standby: [], speakStart: [], speakLoop: [], speakEnd: [] });
|
|
432
|
+
const loadToken = vue.ref(0);
|
|
433
|
+
const isReadyState = vue.ref(false);
|
|
434
|
+
const isAllFramesLoaded = vue.ref(false);
|
|
493
435
|
const animationState = vue.ref("standby");
|
|
494
436
|
const loopDirection = vue.ref(1);
|
|
495
437
|
const isPaused = vue.ref(false);
|
|
@@ -501,16 +443,25 @@ const _sfc_main = {
|
|
|
501
443
|
standbyNum: currentHuman.value.standby.standbyNum,
|
|
502
444
|
speakNum: currentHuman.value.speak.speakNum
|
|
503
445
|
};
|
|
446
|
+
console.log("[DigitalHuman] 开始加载帧:", {
|
|
447
|
+
folder,
|
|
448
|
+
standbyNum,
|
|
449
|
+
speakNum,
|
|
450
|
+
humanId: currentHuman.value.humanId,
|
|
451
|
+
title: currentHuman.value.title
|
|
452
|
+
});
|
|
504
453
|
const makeImage = (url) => {
|
|
505
454
|
const image = new Image();
|
|
506
|
-
image.src = encodeURI(url);
|
|
507
455
|
image.onload = () => {
|
|
508
456
|
image.__broken = false;
|
|
457
|
+
image.__loaded = true;
|
|
509
458
|
};
|
|
510
459
|
image.onerror = () => {
|
|
511
460
|
image.__broken = true;
|
|
512
|
-
|
|
461
|
+
image.__loaded = true;
|
|
462
|
+
console.warn("[DigitalHuman] ❌ 帧加载失败:", url);
|
|
513
463
|
};
|
|
464
|
+
image.src = encodeURI(url);
|
|
514
465
|
return image;
|
|
515
466
|
};
|
|
516
467
|
for (let i = 1; i <= standbyNum; i++) {
|
|
@@ -536,16 +487,29 @@ const _sfc_main = {
|
|
|
536
487
|
}
|
|
537
488
|
};
|
|
538
489
|
const draw = (image) => {
|
|
539
|
-
if (!ctx.value || !image
|
|
490
|
+
if (!ctx.value || !isDrawable(image)) return;
|
|
540
491
|
const canvas = canvasRef.value;
|
|
541
492
|
ctx.value.clearRect(0, 0, canvas.width, canvas.height);
|
|
542
|
-
const scale = Math.min(canvas.width / image.
|
|
543
|
-
const width = image.
|
|
544
|
-
const height = image.
|
|
493
|
+
const scale = Math.min(canvas.width / image.naturalWidth, canvas.height / image.naturalHeight);
|
|
494
|
+
const width = image.naturalWidth * scale;
|
|
495
|
+
const height = image.naturalHeight * scale;
|
|
545
496
|
const x = (canvas.width - width) / 2;
|
|
546
497
|
const y = canvas.height - height;
|
|
547
498
|
ctx.value.drawImage(image, x, y, width, height);
|
|
548
499
|
};
|
|
500
|
+
const isDrawable = (image) => !!image && image.complete && image.naturalWidth > 0;
|
|
501
|
+
const pickDrawable = (list, index2) => {
|
|
502
|
+
if (!list || list.length === 0) return null;
|
|
503
|
+
const i = Math.min(Math.max(index2, 1), list.length);
|
|
504
|
+
if (isDrawable(list[i - 1])) return list[i - 1];
|
|
505
|
+
for (let k = i + 1; k <= list.length; k++) {
|
|
506
|
+
if (isDrawable(list[k - 1])) return list[k - 1];
|
|
507
|
+
}
|
|
508
|
+
for (let k = i - 1; k >= 1; k--) {
|
|
509
|
+
if (isDrawable(list[k - 1])) return list[k - 1];
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
};
|
|
549
513
|
const stop = (keepAudio = false) => {
|
|
550
514
|
var _a;
|
|
551
515
|
if (animationId.value) {
|
|
@@ -632,7 +596,9 @@ const _sfc_main = {
|
|
|
632
596
|
loopDirection.value = 1;
|
|
633
597
|
}
|
|
634
598
|
}
|
|
635
|
-
draw(
|
|
599
|
+
draw(
|
|
600
|
+
pickDrawable(currentFrames, frameIndex.value) || pickDrawable(frames.value.standby, frameIndex.value)
|
|
601
|
+
);
|
|
636
602
|
const shouldStop = updateFrame();
|
|
637
603
|
if (shouldStop) return;
|
|
638
604
|
timestamp.value = time;
|
|
@@ -677,21 +643,32 @@ const _sfc_main = {
|
|
|
677
643
|
}
|
|
678
644
|
emit("resumed");
|
|
679
645
|
};
|
|
680
|
-
const waitFramesLoaded = (callback) => {
|
|
646
|
+
const waitFramesLoaded = (list, callback, token, timeout = 1e4) => {
|
|
647
|
+
const targets = Array.from(new Set(list || []));
|
|
648
|
+
if (!targets.length) {
|
|
649
|
+
callback({ total: 0, loaded: 0, broken: 0, timedOut: false });
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
681
652
|
let loadedCount = 0;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
653
|
+
let finished = false;
|
|
654
|
+
const finish = (timedOut = false) => {
|
|
655
|
+
if (finished) return;
|
|
656
|
+
finished = true;
|
|
657
|
+
clearTimeout(timer);
|
|
658
|
+
if (token !== loadToken.value) return;
|
|
659
|
+
callback({
|
|
660
|
+
total: targets.length,
|
|
661
|
+
loaded: loadedCount,
|
|
662
|
+
broken: targets.filter((img) => img.__broken).length,
|
|
663
|
+
timedOut
|
|
664
|
+
});
|
|
665
|
+
};
|
|
688
666
|
const checkLoaded = () => {
|
|
689
667
|
loadedCount++;
|
|
690
|
-
if (loadedCount >=
|
|
691
|
-
callback();
|
|
692
|
-
}
|
|
668
|
+
if (loadedCount >= targets.length) finish(false);
|
|
693
669
|
};
|
|
694
|
-
|
|
670
|
+
const timer = setTimeout(() => finish(true), timeout);
|
|
671
|
+
targets.forEach((img) => {
|
|
695
672
|
if (img.complete) {
|
|
696
673
|
if (!img.naturalWidth) img.__broken = true;
|
|
697
674
|
checkLoaded();
|
|
@@ -707,6 +684,71 @@ const _sfc_main = {
|
|
|
707
684
|
}
|
|
708
685
|
});
|
|
709
686
|
};
|
|
687
|
+
const syncCanvasSize = () => {
|
|
688
|
+
const canvas = canvasRef.value;
|
|
689
|
+
const container = containerRef.value;
|
|
690
|
+
if (!canvas || !container) return false;
|
|
691
|
+
const dpr = window.devicePixelRatio || 1;
|
|
692
|
+
const width = container.clientWidth;
|
|
693
|
+
const height = container.clientHeight;
|
|
694
|
+
if (!width || !height) {
|
|
695
|
+
if (canvas.width !== Math.round(300 * dpr)) {
|
|
696
|
+
console.warn('[DigitalHuman] 容器宽高为 0!组件根元素是 width:100%;height:100%,需要父容器提供宽高(如 style="width:500px;height:500px")。已用 300×300 兜底');
|
|
697
|
+
canvas.style.width = "300px";
|
|
698
|
+
canvas.style.height = "300px";
|
|
699
|
+
canvas.width = 300 * dpr;
|
|
700
|
+
canvas.height = 300 * dpr;
|
|
701
|
+
ctx.value = canvas.getContext("2d");
|
|
702
|
+
}
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
const w = Math.round(width * dpr);
|
|
706
|
+
const h = Math.round(height * dpr);
|
|
707
|
+
if (canvas.width === w && canvas.height === h) return false;
|
|
708
|
+
canvas.width = w;
|
|
709
|
+
canvas.height = h;
|
|
710
|
+
canvas.style.width = width + "px";
|
|
711
|
+
canvas.style.height = height + "px";
|
|
712
|
+
ctx.value = canvas.getContext("2d");
|
|
713
|
+
return true;
|
|
714
|
+
};
|
|
715
|
+
const redrawCurrent = () => {
|
|
716
|
+
draw(pickDrawable(frames.value.standby, frameIndex.value));
|
|
717
|
+
};
|
|
718
|
+
const trackAllFrames = (token, timeout = 3e4) => {
|
|
719
|
+
const all = Array.from(/* @__PURE__ */ new Set([
|
|
720
|
+
...frames.value.standby,
|
|
721
|
+
...frames.value.speakStart,
|
|
722
|
+
...frames.value.speakLoop,
|
|
723
|
+
...frames.value.speakEnd
|
|
724
|
+
]));
|
|
725
|
+
if (!all.length) return;
|
|
726
|
+
isAllFramesLoaded.value = false;
|
|
727
|
+
waitFramesLoaded(all, (info) => {
|
|
728
|
+
var _a, _b, _c;
|
|
729
|
+
isAllFramesLoaded.value = !info.timedOut && info.broken === 0;
|
|
730
|
+
emit("frames-loaded", {
|
|
731
|
+
humanId: (_a = currentHuman.value) == null ? void 0 : _a.humanId,
|
|
732
|
+
title: (_b = currentHuman.value) == null ? void 0 : _b.title,
|
|
733
|
+
folder: (_c = currentHuman.value) == null ? void 0 : _c.folder,
|
|
734
|
+
total: info.total,
|
|
735
|
+
broken: info.broken,
|
|
736
|
+
timedOut: info.timedOut,
|
|
737
|
+
allReady: isAllFramesLoaded.value
|
|
738
|
+
});
|
|
739
|
+
}, token, timeout);
|
|
740
|
+
};
|
|
741
|
+
const buildLoadInfo = (info) => {
|
|
742
|
+
var _a, _b, _c;
|
|
743
|
+
return {
|
|
744
|
+
humanId: (_a = currentHuman.value) == null ? void 0 : _a.humanId,
|
|
745
|
+
title: (_b = currentHuman.value) == null ? void 0 : _b.title,
|
|
746
|
+
folder: (_c = currentHuman.value) == null ? void 0 : _c.folder,
|
|
747
|
+
total: (info == null ? void 0 : info.total) ?? 0,
|
|
748
|
+
broken: (info == null ? void 0 : info.broken) ?? 0,
|
|
749
|
+
timedOut: !!(info == null ? void 0 : info.timedOut)
|
|
750
|
+
};
|
|
751
|
+
};
|
|
710
752
|
const init = () => {
|
|
711
753
|
vue.nextTick(() => {
|
|
712
754
|
var _a, _b;
|
|
@@ -718,36 +760,33 @@ const _sfc_main = {
|
|
|
718
760
|
const prevType = (_a = currentHuman.value) == null ? void 0 : _a.humanType;
|
|
719
761
|
if (((_b = currentHuman.value) == null ? void 0 : _b.humanId) === human.humanId && prevType === human.humanType) {
|
|
720
762
|
Object.assign(currentHuman.value, human);
|
|
763
|
+
if (syncCanvasSize()) redrawCurrent();
|
|
721
764
|
return;
|
|
722
765
|
}
|
|
723
766
|
abortTTS();
|
|
724
767
|
stop();
|
|
725
768
|
timestamp.value = null;
|
|
726
769
|
frameIndex.value = 1;
|
|
770
|
+
isReadyState.value = false;
|
|
727
771
|
currentHuman.value = human;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
const width = containerRef.value.clientWidth;
|
|
732
|
-
const height = containerRef.value.clientHeight;
|
|
733
|
-
if (!width || !height) {
|
|
734
|
-
console.warn('[DigitalHuman] 容器宽高为 0!组件根元素是 width:100%;height:100%,需要父容器提供宽高(如 style="width:500px;height:500px" 或 class="w-full h-screen")。已自动设置最小尺寸 300×300 作为兜底');
|
|
735
|
-
canvas.style.width = "300px";
|
|
736
|
-
canvas.style.height = "300px";
|
|
737
|
-
canvas.width = 300 * dpr;
|
|
738
|
-
canvas.height = 300 * dpr;
|
|
739
|
-
} else {
|
|
740
|
-
canvas.width = width * dpr;
|
|
741
|
-
canvas.height = height * dpr;
|
|
742
|
-
canvas.style.width = width + "px";
|
|
743
|
-
canvas.style.height = height + "px";
|
|
744
|
-
}
|
|
745
|
-
ctx.value = canvas.getContext("2d");
|
|
772
|
+
syncCanvasSize();
|
|
773
|
+
loadToken.value++;
|
|
774
|
+
const token = loadToken.value;
|
|
746
775
|
loadFrames();
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
776
|
+
trackAllFrames(token);
|
|
777
|
+
waitFramesLoaded(frames.value.standby, (info) => {
|
|
778
|
+
const first = pickDrawable(frames.value.standby, 1);
|
|
779
|
+
if (!first) {
|
|
780
|
+
isReadyState.value = false;
|
|
781
|
+
console.error("[DigitalHuman] 待机帧全部不可用,请检查图片地址/跨域配置:", currentHuman.value.folder);
|
|
782
|
+
emit("load-error", buildLoadInfo(info));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
isReadyState.value = true;
|
|
786
|
+
draw(first);
|
|
787
|
+
standby();
|
|
788
|
+
emit("loaded", currentHuman.value, buildLoadInfo(info));
|
|
789
|
+
}, token);
|
|
751
790
|
});
|
|
752
791
|
};
|
|
753
792
|
const currentAudio = vue.ref(null);
|
|
@@ -865,6 +904,7 @@ const _sfc_main = {
|
|
|
865
904
|
return;
|
|
866
905
|
}
|
|
867
906
|
const audio = new Audio(objectUrl);
|
|
907
|
+
audio.muted = isMutedNow.value;
|
|
868
908
|
currentAudio.value = audio;
|
|
869
909
|
const cleanup = () => {
|
|
870
910
|
URL.revokeObjectURL(objectUrl);
|
|
@@ -896,8 +936,12 @@ const _sfc_main = {
|
|
|
896
936
|
if (!text) return;
|
|
897
937
|
const latest = props.human ? resolveHuman(props.human) : null;
|
|
898
938
|
if (latest) {
|
|
899
|
-
if (
|
|
900
|
-
|
|
939
|
+
if (currentHuman.value) Object.assign(currentHuman.value, latest);
|
|
940
|
+
else currentHuman.value = latest;
|
|
941
|
+
}
|
|
942
|
+
if (isMutedNow.value) {
|
|
943
|
+
console.log("[TTS] 当前为静音状态,跳过播放");
|
|
944
|
+
return;
|
|
901
945
|
}
|
|
902
946
|
abortTTS();
|
|
903
947
|
ttsAbortController = new AbortController();
|
|
@@ -910,6 +954,10 @@ const _sfc_main = {
|
|
|
910
954
|
console.log("[TTS] 提交合成请求:", { human: currentHuman.value, text: cleanText, length: cleanText.length });
|
|
911
955
|
const audioBlob = await requestTTSAudio(cleanText, signal);
|
|
912
956
|
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
957
|
+
if (isMutedNow.value) {
|
|
958
|
+
console.log("[TTS] 合成期间已静音,丢弃音频");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
913
961
|
if (!audioBlob || audioBlob.size === 0) {
|
|
914
962
|
throw new Error("TTS 返回空音频");
|
|
915
963
|
}
|
|
@@ -987,13 +1035,25 @@ const _sfc_main = {
|
|
|
987
1035
|
});
|
|
988
1036
|
}
|
|
989
1037
|
};
|
|
1038
|
+
const applyMuted = () => {
|
|
1039
|
+
if (isMutedNow.value) {
|
|
1040
|
+
abortTTS();
|
|
1041
|
+
stopSpeaking();
|
|
1042
|
+
} else if (currentAudio.value) {
|
|
1043
|
+
currentAudio.value.muted = false;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
990
1046
|
vue.watch(
|
|
991
1047
|
() => props.human,
|
|
992
1048
|
() => {
|
|
993
1049
|
init();
|
|
1050
|
+
applyMuted();
|
|
994
1051
|
},
|
|
995
1052
|
{ deep: true }
|
|
996
1053
|
);
|
|
1054
|
+
vue.watch(isMutedNow, () => {
|
|
1055
|
+
applyMuted();
|
|
1056
|
+
});
|
|
997
1057
|
const switchHuman = (humanId) => {
|
|
998
1058
|
const next = resolveHuman(humanId);
|
|
999
1059
|
if (!next) return;
|
|
@@ -1001,13 +1061,63 @@ const _sfc_main = {
|
|
|
1001
1061
|
stop();
|
|
1002
1062
|
timestamp.value = null;
|
|
1003
1063
|
frameIndex.value = 1;
|
|
1064
|
+
isReadyState.value = false;
|
|
1004
1065
|
currentHuman.value = next;
|
|
1066
|
+
syncCanvasSize();
|
|
1067
|
+
loadToken.value++;
|
|
1068
|
+
const token = loadToken.value;
|
|
1005
1069
|
loadFrames();
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1070
|
+
trackAllFrames(token);
|
|
1071
|
+
waitFramesLoaded(frames.value.standby, (info) => {
|
|
1072
|
+
const first = pickDrawable(frames.value.standby, 1);
|
|
1073
|
+
if (!first) {
|
|
1074
|
+
isReadyState.value = false;
|
|
1075
|
+
console.error("[DigitalHuman] 待机帧全部不可用,请检查图片地址/跨域配置:", currentHuman.value.folder);
|
|
1076
|
+
emit("load-error", buildLoadInfo(info));
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
isReadyState.value = true;
|
|
1080
|
+
draw(first);
|
|
1081
|
+
standby();
|
|
1082
|
+
emit("changed", currentHuman.value, buildLoadInfo(info));
|
|
1083
|
+
}, token);
|
|
1010
1084
|
};
|
|
1085
|
+
const isReady = () => isReadyState.value;
|
|
1086
|
+
const isFullyLoaded = () => isAllFramesLoaded.value;
|
|
1087
|
+
const whenReady = (timeout = 15e3) => new Promise((resolve) => {
|
|
1088
|
+
if (isReadyState.value) {
|
|
1089
|
+
resolve(true);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let timer = null;
|
|
1093
|
+
const stopWatch = vue.watch(isReadyState, (val) => {
|
|
1094
|
+
if (!val) return;
|
|
1095
|
+
clearTimeout(timer);
|
|
1096
|
+
stopWatch();
|
|
1097
|
+
resolve(true);
|
|
1098
|
+
});
|
|
1099
|
+
timer = setTimeout(() => {
|
|
1100
|
+
stopWatch();
|
|
1101
|
+
resolve(false);
|
|
1102
|
+
}, timeout);
|
|
1103
|
+
});
|
|
1104
|
+
const whenFullyLoaded = (timeout = 3e4) => new Promise((resolve) => {
|
|
1105
|
+
if (isAllFramesLoaded.value) {
|
|
1106
|
+
resolve(true);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
let timer = null;
|
|
1110
|
+
const stopWatch = vue.watch(isAllFramesLoaded, (val) => {
|
|
1111
|
+
if (!val) return;
|
|
1112
|
+
clearTimeout(timer);
|
|
1113
|
+
stopWatch();
|
|
1114
|
+
resolve(true);
|
|
1115
|
+
});
|
|
1116
|
+
timer = setTimeout(() => {
|
|
1117
|
+
stopWatch();
|
|
1118
|
+
resolve(false);
|
|
1119
|
+
}, timeout);
|
|
1120
|
+
});
|
|
1011
1121
|
__expose({
|
|
1012
1122
|
standby,
|
|
1013
1123
|
speak,
|
|
@@ -1017,16 +1127,29 @@ const _sfc_main = {
|
|
|
1017
1127
|
playTTS,
|
|
1018
1128
|
startRecord,
|
|
1019
1129
|
stopRecord,
|
|
1020
|
-
switchHuman
|
|
1130
|
+
switchHuman,
|
|
1131
|
+
// 加载状态查询
|
|
1132
|
+
isReady,
|
|
1133
|
+
isFullyLoaded,
|
|
1134
|
+
whenReady,
|
|
1135
|
+
whenFullyLoaded
|
|
1021
1136
|
});
|
|
1137
|
+
let resizeObserver = null;
|
|
1022
1138
|
vue.onMounted(() => {
|
|
1023
|
-
console.log("onMounted", props.human);
|
|
1024
1139
|
initRecorder();
|
|
1025
1140
|
init();
|
|
1141
|
+
if (typeof ResizeObserver !== "undefined" && containerRef.value) {
|
|
1142
|
+
resizeObserver = new ResizeObserver(() => {
|
|
1143
|
+
if (syncCanvasSize()) redrawCurrent();
|
|
1144
|
+
});
|
|
1145
|
+
resizeObserver.observe(containerRef.value);
|
|
1146
|
+
}
|
|
1026
1147
|
});
|
|
1027
1148
|
vue.onUnmounted(() => {
|
|
1028
1149
|
abortTTS();
|
|
1029
1150
|
stop();
|
|
1151
|
+
resizeObserver == null ? void 0 : resizeObserver.disconnect();
|
|
1152
|
+
resizeObserver = null;
|
|
1030
1153
|
if (recorder2.value) {
|
|
1031
1154
|
recorder2.value.stop();
|
|
1032
1155
|
}
|
|
@@ -1045,14 +1168,12 @@ const _sfc_main = {
|
|
|
1045
1168
|
};
|
|
1046
1169
|
}
|
|
1047
1170
|
};
|
|
1048
|
-
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
1171
|
+
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-26d22b04"]]);
|
|
1049
1172
|
DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
|
|
1050
|
-
const components = [
|
|
1173
|
+
const components = [DigitalHuman];
|
|
1051
1174
|
const install = (app) => {
|
|
1052
1175
|
components.forEach((comp) => app.use(comp));
|
|
1053
1176
|
};
|
|
1054
1177
|
const index = { install };
|
|
1055
|
-
exports.DemoButton = DemoButton;
|
|
1056
|
-
exports.DemoInput = DemoInput;
|
|
1057
1178
|
exports.DigitalHuman = DigitalHuman;
|
|
1058
1179
|
exports.default = index;
|