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