ruiyun-human 1.0.4 → 1.0.6

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.
@@ -452,20 +452,24 @@ const _sfc_main = {
452
452
  const y = canvas.height - height;
453
453
  ctx.value.drawImage(image, x, y, width, height);
454
454
  };
455
- const stop = () => {
455
+ const stop = (keepAudio = false) => {
456
+ var _a;
456
457
  if (animationId.value) {
457
458
  cancelAnimationFrame(animationId.value);
458
459
  animationId.value = null;
459
460
  }
460
- try {
461
- currentAudio.value.pause();
462
- } catch (error) {
461
+ if (!keepAudio) {
462
+ try {
463
+ (_a = currentAudio.value) == null ? void 0 : _a.pause();
464
+ } catch (error) {
465
+ }
463
466
  }
464
467
  frameIndex.value = 1;
465
468
  animationState.value = "standby";
466
469
  };
467
470
  const runAnimation = (state) => {
468
- stop();
471
+ const keepAudio = state === "speak" || state === "speakLoop" || state === "speakEnd";
472
+ stop(keepAudio);
469
473
  animationState.value = state;
470
474
  loopDirection.value = 1;
471
475
  let currentFrames;
@@ -550,6 +554,7 @@ const _sfc_main = {
550
554
  runAnimation("speak");
551
555
  };
552
556
  const stopSpeaking = () => {
557
+ abortTTS();
553
558
  if (animationState.value === "speak" || animationState.value === "speakLoop") {
554
559
  runAnimation("speakEnd");
555
560
  } else {
@@ -625,13 +630,44 @@ const _sfc_main = {
625
630
  };
626
631
  const currentAudio = vue.ref(null);
627
632
  const currentTtsRequestId = vue.ref(null);
628
- const playTTS = async (text) => {
629
- var _a, _b, _c, _d, _e, _f, _g, _h;
630
- if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
633
+ let ttsAbortController = null;
634
+ function abortTTS() {
635
+ currentTtsRequestId.value = null;
636
+ if (ttsAbortController) {
637
+ try {
638
+ ttsAbortController.abort();
639
+ } catch {
640
+ }
641
+ ttsAbortController = null;
642
+ }
631
643
  if (currentAudio.value) {
632
- currentAudio.value.pause();
644
+ try {
645
+ currentAudio.value.pause();
646
+ } catch {
647
+ }
633
648
  currentAudio.value = null;
634
649
  }
650
+ }
651
+ function waitWithSignal(ms, signal) {
652
+ return new Promise((resolve, reject) => {
653
+ if (signal == null ? void 0 : signal.aborted) {
654
+ reject(new DOMException("Aborted", "AbortError"));
655
+ return;
656
+ }
657
+ const timer = setTimeout(resolve, ms);
658
+ const onAbort = () => {
659
+ clearTimeout(timer);
660
+ reject(new DOMException("Aborted", "AbortError"));
661
+ };
662
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
663
+ });
664
+ }
665
+ const playTTS = async (text) => {
666
+ var _a, _b, _c, _d, _e, _f, _g, _h;
667
+ if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
668
+ abortTTS();
669
+ ttsAbortController = new AbortController();
670
+ const { signal } = ttsAbortController;
635
671
  currentTtsRequestId.value = Date.now();
636
672
  const requestId = currentTtsRequestId.value;
637
673
  try {
@@ -647,7 +683,8 @@ const _sfc_main = {
647
683
  const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
648
684
  method: "POST",
649
685
  headers: { "Content-Type": "application/json" },
650
- body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 })
686
+ body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
687
+ signal
651
688
  });
652
689
  const joinData = await joinRes.json();
653
690
  console.log("[OmniVoice] Step1 响应:", joinData);
@@ -656,10 +693,13 @@ const _sfc_main = {
656
693
  let maxPolls = 60;
657
694
  let pollInterval = 1e3;
658
695
  while (!audioUrl && maxPolls > 0) {
659
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
696
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
697
+ await waitWithSignal(pollInterval, signal);
660
698
  maxPolls--;
661
699
  try {
662
- const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`);
700
+ const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
701
+ signal
702
+ });
663
703
  const pollText = await pollRes.text();
664
704
  const lines = pollText.split("\n");
665
705
  for (const line of lines) {
@@ -686,7 +726,7 @@ const _sfc_main = {
686
726
  }
687
727
  const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
688
728
  const audioObjectUrl = URL.createObjectURL(wavBlob);
689
- if (currentTtsRequestId.value !== requestId) {
729
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
690
730
  URL.revokeObjectURL(audioObjectUrl);
691
731
  return;
692
732
  }
@@ -722,16 +762,21 @@ const _sfc_main = {
722
762
  }
723
763
  }
724
764
  } catch (e) {
765
+ if ((e == null ? void 0 : e.name) === "AbortError") throw e;
725
766
  console.warn("[OmniVoice] 轮询失败:", e.message);
726
767
  }
727
768
  }
728
769
  if (audioUrl) {
729
770
  if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
730
771
  console.log("[OmniVoice] 下载音频:", audioUrl);
731
- if (currentTtsRequestId.value !== requestId) return;
732
- const audioRes = await fetch(audioUrl);
772
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
773
+ const audioRes = await fetch(audioUrl, { signal });
733
774
  const audioBlob = await audioRes.blob();
734
775
  const audioObjectUrl = URL.createObjectURL(audioBlob);
776
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
777
+ URL.revokeObjectURL(audioObjectUrl);
778
+ return;
779
+ }
735
780
  currentAudio.value = new Audio(audioObjectUrl);
736
781
  currentAudio.value.onplaying = () => {
737
782
  console.log("[OmniVoice] 音频开始播放");
@@ -755,6 +800,10 @@ const _sfc_main = {
755
800
  standby();
756
801
  }
757
802
  } catch (e) {
803
+ if ((e == null ? void 0 : e.name) === "AbortError") {
804
+ console.log("[OmniVoice] TTS已取消");
805
+ return;
806
+ }
758
807
  console.error("[OmniVoice] TTS失败:", e);
759
808
  standby();
760
809
  }
@@ -866,6 +915,7 @@ const _sfc_main = {
866
915
  init();
867
916
  });
868
917
  vue.onUnmounted(() => {
918
+ abortTTS();
869
919
  stop();
870
920
  if (recorder2.value) {
871
921
  recorder2.value.stop();
@@ -885,7 +935,7 @@ const _sfc_main = {
885
935
  };
886
936
  }
887
937
  };
888
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-dd386539"]]);
938
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-90fb7a29"]]);
889
939
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
890
940
  const components = [DemoButton, DemoInput, DigitalHuman];
891
941
  const install = (app) => {
@@ -450,20 +450,24 @@ const _sfc_main = {
450
450
  const y = canvas.height - height;
451
451
  ctx.value.drawImage(image, x, y, width, height);
452
452
  };
453
- const stop = () => {
453
+ const stop = (keepAudio = false) => {
454
+ var _a;
454
455
  if (animationId.value) {
455
456
  cancelAnimationFrame(animationId.value);
456
457
  animationId.value = null;
457
458
  }
458
- try {
459
- currentAudio.value.pause();
460
- } catch (error) {
459
+ if (!keepAudio) {
460
+ try {
461
+ (_a = currentAudio.value) == null ? void 0 : _a.pause();
462
+ } catch (error) {
463
+ }
461
464
  }
462
465
  frameIndex.value = 1;
463
466
  animationState.value = "standby";
464
467
  };
465
468
  const runAnimation = (state) => {
466
- stop();
469
+ const keepAudio = state === "speak" || state === "speakLoop" || state === "speakEnd";
470
+ stop(keepAudio);
467
471
  animationState.value = state;
468
472
  loopDirection.value = 1;
469
473
  let currentFrames;
@@ -548,6 +552,7 @@ const _sfc_main = {
548
552
  runAnimation("speak");
549
553
  };
550
554
  const stopSpeaking = () => {
555
+ abortTTS();
551
556
  if (animationState.value === "speak" || animationState.value === "speakLoop") {
552
557
  runAnimation("speakEnd");
553
558
  } else {
@@ -623,13 +628,44 @@ const _sfc_main = {
623
628
  };
624
629
  const currentAudio = ref(null);
625
630
  const currentTtsRequestId = ref(null);
626
- const playTTS = async (text) => {
627
- var _a, _b, _c, _d, _e, _f, _g, _h;
628
- if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
631
+ let ttsAbortController = null;
632
+ function abortTTS() {
633
+ currentTtsRequestId.value = null;
634
+ if (ttsAbortController) {
635
+ try {
636
+ ttsAbortController.abort();
637
+ } catch {
638
+ }
639
+ ttsAbortController = null;
640
+ }
629
641
  if (currentAudio.value) {
630
- currentAudio.value.pause();
642
+ try {
643
+ currentAudio.value.pause();
644
+ } catch {
645
+ }
631
646
  currentAudio.value = null;
632
647
  }
648
+ }
649
+ function waitWithSignal(ms, signal) {
650
+ return new Promise((resolve, reject) => {
651
+ if (signal == null ? void 0 : signal.aborted) {
652
+ reject(new DOMException("Aborted", "AbortError"));
653
+ return;
654
+ }
655
+ const timer = setTimeout(resolve, ms);
656
+ const onAbort = () => {
657
+ clearTimeout(timer);
658
+ reject(new DOMException("Aborted", "AbortError"));
659
+ };
660
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
661
+ });
662
+ }
663
+ const playTTS = async (text) => {
664
+ var _a, _b, _c, _d, _e, _f, _g, _h;
665
+ if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
666
+ abortTTS();
667
+ ttsAbortController = new AbortController();
668
+ const { signal } = ttsAbortController;
633
669
  currentTtsRequestId.value = Date.now();
634
670
  const requestId = currentTtsRequestId.value;
635
671
  try {
@@ -645,7 +681,8 @@ const _sfc_main = {
645
681
  const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
646
682
  method: "POST",
647
683
  headers: { "Content-Type": "application/json" },
648
- body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 })
684
+ body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
685
+ signal
649
686
  });
650
687
  const joinData = await joinRes.json();
651
688
  console.log("[OmniVoice] Step1 响应:", joinData);
@@ -654,10 +691,13 @@ const _sfc_main = {
654
691
  let maxPolls = 60;
655
692
  let pollInterval = 1e3;
656
693
  while (!audioUrl && maxPolls > 0) {
657
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
694
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
695
+ await waitWithSignal(pollInterval, signal);
658
696
  maxPolls--;
659
697
  try {
660
- const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`);
698
+ const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
699
+ signal
700
+ });
661
701
  const pollText = await pollRes.text();
662
702
  const lines = pollText.split("\n");
663
703
  for (const line of lines) {
@@ -684,7 +724,7 @@ const _sfc_main = {
684
724
  }
685
725
  const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
686
726
  const audioObjectUrl = URL.createObjectURL(wavBlob);
687
- if (currentTtsRequestId.value !== requestId) {
727
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
688
728
  URL.revokeObjectURL(audioObjectUrl);
689
729
  return;
690
730
  }
@@ -720,16 +760,21 @@ const _sfc_main = {
720
760
  }
721
761
  }
722
762
  } catch (e) {
763
+ if ((e == null ? void 0 : e.name) === "AbortError") throw e;
723
764
  console.warn("[OmniVoice] 轮询失败:", e.message);
724
765
  }
725
766
  }
726
767
  if (audioUrl) {
727
768
  if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
728
769
  console.log("[OmniVoice] 下载音频:", audioUrl);
729
- if (currentTtsRequestId.value !== requestId) return;
730
- const audioRes = await fetch(audioUrl);
770
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
771
+ const audioRes = await fetch(audioUrl, { signal });
731
772
  const audioBlob = await audioRes.blob();
732
773
  const audioObjectUrl = URL.createObjectURL(audioBlob);
774
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
775
+ URL.revokeObjectURL(audioObjectUrl);
776
+ return;
777
+ }
733
778
  currentAudio.value = new Audio(audioObjectUrl);
734
779
  currentAudio.value.onplaying = () => {
735
780
  console.log("[OmniVoice] 音频开始播放");
@@ -753,6 +798,10 @@ const _sfc_main = {
753
798
  standby();
754
799
  }
755
800
  } catch (e) {
801
+ if ((e == null ? void 0 : e.name) === "AbortError") {
802
+ console.log("[OmniVoice] TTS已取消");
803
+ return;
804
+ }
756
805
  console.error("[OmniVoice] TTS失败:", e);
757
806
  standby();
758
807
  }
@@ -864,6 +913,7 @@ const _sfc_main = {
864
913
  init();
865
914
  });
866
915
  onUnmounted(() => {
916
+ abortTTS();
867
917
  stop();
868
918
  if (recorder2.value) {
869
919
  recorder2.value.stop();
@@ -883,7 +933,7 @@ const _sfc_main = {
883
933
  };
884
934
  }
885
935
  };
886
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-dd386539"]]);
936
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-90fb7a29"]]);
887
937
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
888
938
  const components = [DemoButton, DemoInput, DigitalHuman];
889
939
  const install = (app) => {
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-dd386539]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-dd386539]{max-width:100%;max-height:100%}
1
+ .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-90fb7a29]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-90fb7a29]{max-width:100%;max-height:100%}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruiyun-human",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "数字人初步实现",
6
6
  "main": "./dist/ruiyun-human.cjs.js",