ruiyun-human 1.0.20 → 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 +256 -143
- package/dist/ruiyun-human.es.js +257 -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);
|
|
@@ -510,15 +452,16 @@ const _sfc_main = {
|
|
|
510
452
|
});
|
|
511
453
|
const makeImage = (url) => {
|
|
512
454
|
const image = new Image();
|
|
513
|
-
image.src = encodeURI(url);
|
|
514
455
|
image.onload = () => {
|
|
515
456
|
image.__broken = false;
|
|
516
|
-
|
|
457
|
+
image.__loaded = true;
|
|
517
458
|
};
|
|
518
459
|
image.onerror = () => {
|
|
519
460
|
image.__broken = true;
|
|
461
|
+
image.__loaded = true;
|
|
520
462
|
console.warn("[DigitalHuman] ❌ 帧加载失败:", url);
|
|
521
463
|
};
|
|
464
|
+
image.src = encodeURI(url);
|
|
522
465
|
return image;
|
|
523
466
|
};
|
|
524
467
|
for (let i = 1; i <= standbyNum; i++) {
|
|
@@ -544,16 +487,29 @@ const _sfc_main = {
|
|
|
544
487
|
}
|
|
545
488
|
};
|
|
546
489
|
const draw = (image) => {
|
|
547
|
-
if (!ctx.value || !image
|
|
490
|
+
if (!ctx.value || !isDrawable(image)) return;
|
|
548
491
|
const canvas = canvasRef.value;
|
|
549
492
|
ctx.value.clearRect(0, 0, canvas.width, canvas.height);
|
|
550
|
-
const scale = Math.min(canvas.width / image.
|
|
551
|
-
const width = image.
|
|
552
|
-
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;
|
|
553
496
|
const x = (canvas.width - width) / 2;
|
|
554
497
|
const y = canvas.height - height;
|
|
555
498
|
ctx.value.drawImage(image, x, y, width, height);
|
|
556
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
|
+
};
|
|
557
513
|
const stop = (keepAudio = false) => {
|
|
558
514
|
var _a;
|
|
559
515
|
if (animationId.value) {
|
|
@@ -640,7 +596,9 @@ const _sfc_main = {
|
|
|
640
596
|
loopDirection.value = 1;
|
|
641
597
|
}
|
|
642
598
|
}
|
|
643
|
-
draw(
|
|
599
|
+
draw(
|
|
600
|
+
pickDrawable(currentFrames, frameIndex.value) || pickDrawable(frames.value.standby, frameIndex.value)
|
|
601
|
+
);
|
|
644
602
|
const shouldStop = updateFrame();
|
|
645
603
|
if (shouldStop) return;
|
|
646
604
|
timestamp.value = time;
|
|
@@ -685,21 +643,32 @@ const _sfc_main = {
|
|
|
685
643
|
}
|
|
686
644
|
emit("resumed");
|
|
687
645
|
};
|
|
688
|
-
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
|
+
}
|
|
689
652
|
let loadedCount = 0;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
};
|
|
696
666
|
const checkLoaded = () => {
|
|
697
667
|
loadedCount++;
|
|
698
|
-
if (loadedCount >=
|
|
699
|
-
callback();
|
|
700
|
-
}
|
|
668
|
+
if (loadedCount >= targets.length) finish(false);
|
|
701
669
|
};
|
|
702
|
-
|
|
670
|
+
const timer = setTimeout(() => finish(true), timeout);
|
|
671
|
+
targets.forEach((img) => {
|
|
703
672
|
if (img.complete) {
|
|
704
673
|
if (!img.naturalWidth) img.__broken = true;
|
|
705
674
|
checkLoaded();
|
|
@@ -715,6 +684,71 @@ const _sfc_main = {
|
|
|
715
684
|
}
|
|
716
685
|
});
|
|
717
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
|
+
};
|
|
718
752
|
const init = () => {
|
|
719
753
|
vue.nextTick(() => {
|
|
720
754
|
var _a, _b;
|
|
@@ -726,36 +760,33 @@ const _sfc_main = {
|
|
|
726
760
|
const prevType = (_a = currentHuman.value) == null ? void 0 : _a.humanType;
|
|
727
761
|
if (((_b = currentHuman.value) == null ? void 0 : _b.humanId) === human.humanId && prevType === human.humanType) {
|
|
728
762
|
Object.assign(currentHuman.value, human);
|
|
763
|
+
if (syncCanvasSize()) redrawCurrent();
|
|
729
764
|
return;
|
|
730
765
|
}
|
|
731
766
|
abortTTS();
|
|
732
767
|
stop();
|
|
733
768
|
timestamp.value = null;
|
|
734
769
|
frameIndex.value = 1;
|
|
770
|
+
isReadyState.value = false;
|
|
735
771
|
currentHuman.value = human;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
const width = containerRef.value.clientWidth;
|
|
740
|
-
const height = containerRef.value.clientHeight;
|
|
741
|
-
if (!width || !height) {
|
|
742
|
-
console.warn('[DigitalHuman] 容器宽高为 0!组件根元素是 width:100%;height:100%,需要父容器提供宽高(如 style="width:500px;height:500px" 或 class="w-full h-screen")。已自动设置最小尺寸 300×300 作为兜底');
|
|
743
|
-
canvas.style.width = "300px";
|
|
744
|
-
canvas.style.height = "300px";
|
|
745
|
-
canvas.width = 300 * dpr;
|
|
746
|
-
canvas.height = 300 * dpr;
|
|
747
|
-
} else {
|
|
748
|
-
canvas.width = width * dpr;
|
|
749
|
-
canvas.height = height * dpr;
|
|
750
|
-
canvas.style.width = width + "px";
|
|
751
|
-
canvas.style.height = height + "px";
|
|
752
|
-
}
|
|
753
|
-
ctx.value = canvas.getContext("2d");
|
|
772
|
+
syncCanvasSize();
|
|
773
|
+
loadToken.value++;
|
|
774
|
+
const token = loadToken.value;
|
|
754
775
|
loadFrames();
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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);
|
|
759
790
|
});
|
|
760
791
|
};
|
|
761
792
|
const currentAudio = vue.ref(null);
|
|
@@ -873,6 +904,7 @@ const _sfc_main = {
|
|
|
873
904
|
return;
|
|
874
905
|
}
|
|
875
906
|
const audio = new Audio(objectUrl);
|
|
907
|
+
audio.muted = isMutedNow.value;
|
|
876
908
|
currentAudio.value = audio;
|
|
877
909
|
const cleanup = () => {
|
|
878
910
|
URL.revokeObjectURL(objectUrl);
|
|
@@ -904,8 +936,12 @@ const _sfc_main = {
|
|
|
904
936
|
if (!text) return;
|
|
905
937
|
const latest = props.human ? resolveHuman(props.human) : null;
|
|
906
938
|
if (latest) {
|
|
907
|
-
if (
|
|
908
|
-
|
|
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;
|
|
909
945
|
}
|
|
910
946
|
abortTTS();
|
|
911
947
|
ttsAbortController = new AbortController();
|
|
@@ -918,6 +954,10 @@ const _sfc_main = {
|
|
|
918
954
|
console.log("[TTS] 提交合成请求:", { human: currentHuman.value, text: cleanText, length: cleanText.length });
|
|
919
955
|
const audioBlob = await requestTTSAudio(cleanText, signal);
|
|
920
956
|
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
957
|
+
if (isMutedNow.value) {
|
|
958
|
+
console.log("[TTS] 合成期间已静音,丢弃音频");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
921
961
|
if (!audioBlob || audioBlob.size === 0) {
|
|
922
962
|
throw new Error("TTS 返回空音频");
|
|
923
963
|
}
|
|
@@ -995,13 +1035,25 @@ const _sfc_main = {
|
|
|
995
1035
|
});
|
|
996
1036
|
}
|
|
997
1037
|
};
|
|
1038
|
+
const applyMuted = () => {
|
|
1039
|
+
if (isMutedNow.value) {
|
|
1040
|
+
abortTTS();
|
|
1041
|
+
stopSpeaking();
|
|
1042
|
+
} else if (currentAudio.value) {
|
|
1043
|
+
currentAudio.value.muted = false;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
998
1046
|
vue.watch(
|
|
999
1047
|
() => props.human,
|
|
1000
1048
|
() => {
|
|
1001
1049
|
init();
|
|
1050
|
+
applyMuted();
|
|
1002
1051
|
},
|
|
1003
1052
|
{ deep: true }
|
|
1004
1053
|
);
|
|
1054
|
+
vue.watch(isMutedNow, () => {
|
|
1055
|
+
applyMuted();
|
|
1056
|
+
});
|
|
1005
1057
|
const switchHuman = (humanId) => {
|
|
1006
1058
|
const next = resolveHuman(humanId);
|
|
1007
1059
|
if (!next) return;
|
|
@@ -1009,13 +1061,63 @@ const _sfc_main = {
|
|
|
1009
1061
|
stop();
|
|
1010
1062
|
timestamp.value = null;
|
|
1011
1063
|
frameIndex.value = 1;
|
|
1064
|
+
isReadyState.value = false;
|
|
1012
1065
|
currentHuman.value = next;
|
|
1066
|
+
syncCanvasSize();
|
|
1067
|
+
loadToken.value++;
|
|
1068
|
+
const token = loadToken.value;
|
|
1013
1069
|
loadFrames();
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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);
|
|
1018
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
|
+
});
|
|
1019
1121
|
__expose({
|
|
1020
1122
|
standby,
|
|
1021
1123
|
speak,
|
|
@@ -1025,16 +1127,29 @@ const _sfc_main = {
|
|
|
1025
1127
|
playTTS,
|
|
1026
1128
|
startRecord,
|
|
1027
1129
|
stopRecord,
|
|
1028
|
-
switchHuman
|
|
1130
|
+
switchHuman,
|
|
1131
|
+
// 加载状态查询
|
|
1132
|
+
isReady,
|
|
1133
|
+
isFullyLoaded,
|
|
1134
|
+
whenReady,
|
|
1135
|
+
whenFullyLoaded
|
|
1029
1136
|
});
|
|
1137
|
+
let resizeObserver = null;
|
|
1030
1138
|
vue.onMounted(() => {
|
|
1031
|
-
console.log("onMounted", props.human);
|
|
1032
1139
|
initRecorder();
|
|
1033
1140
|
init();
|
|
1141
|
+
if (typeof ResizeObserver !== "undefined" && containerRef.value) {
|
|
1142
|
+
resizeObserver = new ResizeObserver(() => {
|
|
1143
|
+
if (syncCanvasSize()) redrawCurrent();
|
|
1144
|
+
});
|
|
1145
|
+
resizeObserver.observe(containerRef.value);
|
|
1146
|
+
}
|
|
1034
1147
|
});
|
|
1035
1148
|
vue.onUnmounted(() => {
|
|
1036
1149
|
abortTTS();
|
|
1037
1150
|
stop();
|
|
1151
|
+
resizeObserver == null ? void 0 : resizeObserver.disconnect();
|
|
1152
|
+
resizeObserver = null;
|
|
1038
1153
|
if (recorder2.value) {
|
|
1039
1154
|
recorder2.value.stop();
|
|
1040
1155
|
}
|
|
@@ -1053,14 +1168,12 @@ const _sfc_main = {
|
|
|
1053
1168
|
};
|
|
1054
1169
|
}
|
|
1055
1170
|
};
|
|
1056
|
-
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
1171
|
+
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-26d22b04"]]);
|
|
1057
1172
|
DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
|
|
1058
|
-
const components = [
|
|
1173
|
+
const components = [DigitalHuman];
|
|
1059
1174
|
const install = (app) => {
|
|
1060
1175
|
components.forEach((comp) => app.use(comp));
|
|
1061
1176
|
};
|
|
1062
1177
|
const index = { install };
|
|
1063
|
-
exports.DemoButton = DemoButton;
|
|
1064
|
-
exports.DemoInput = DemoInput;
|
|
1065
1178
|
exports.DigitalHuman = DigitalHuman;
|
|
1066
1179
|
exports.default = index;
|