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