ruiyun-human 1.0.10 → 1.0.12
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 +7 -0
- package/dist/ruiyun-human.cjs.js +170 -176
- package/dist/ruiyun-human.es.js +170 -176
- package/dist/style.css +1 -1
- package/package.json +4 -4
- package/dist/favicon.svg +0 -4
package/README.md
ADDED
package/dist/ruiyun-human.cjs.js
CHANGED
|
@@ -385,6 +385,8 @@ var recorder = { exports: {} };
|
|
|
385
385
|
var recorderExports = recorder.exports;
|
|
386
386
|
var jsAudioRecorder = recorderExports;
|
|
387
387
|
const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
|
|
388
|
+
const baseTTSUrl = "http://192.168.3.182:8002";
|
|
389
|
+
const ASR_URL = "http://192.168.3.182:9988";
|
|
388
390
|
const _sfc_main = {
|
|
389
391
|
__name: "index",
|
|
390
392
|
props: {
|
|
@@ -647,22 +649,150 @@ const _sfc_main = {
|
|
|
647
649
|
currentAudio.value = null;
|
|
648
650
|
}
|
|
649
651
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
652
|
+
const voiceFileCache = /* @__PURE__ */ new Map();
|
|
653
|
+
const FILEDATA_META = { _type: "gradio.FileData" };
|
|
654
|
+
const uploadReferenceAudio = async (blob, filename, signal) => {
|
|
655
|
+
const form = new FormData();
|
|
656
|
+
form.append("files", blob, filename);
|
|
657
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/upload`, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
body: form,
|
|
660
|
+
signal
|
|
661
|
+
});
|
|
662
|
+
if (!res.ok) throw new Error(`参考音频上传失败 HTTP ${res.status}`);
|
|
663
|
+
const paths = await res.json();
|
|
664
|
+
const path = Array.isArray(paths) ? paths[0] : paths;
|
|
665
|
+
if (!path) throw new Error("参考音频上传失败:服务未返回文件路径");
|
|
666
|
+
return { path, orig_name: filename, meta: FILEDATA_META };
|
|
667
|
+
};
|
|
668
|
+
const resolveVoiceFileData = async (voiceSource, signal) => {
|
|
669
|
+
if (voiceSource && typeof voiceSource === "object" && !Array.isArray(voiceSource) && voiceSource.path) {
|
|
670
|
+
return {
|
|
671
|
+
path: voiceSource.path,
|
|
672
|
+
orig_name: voiceSource.orig_name || "reference.mp3",
|
|
673
|
+
meta: FILEDATA_META
|
|
660
674
|
};
|
|
661
|
-
|
|
675
|
+
}
|
|
676
|
+
if (voiceSource instanceof Blob) {
|
|
677
|
+
const name = voiceSource.name || `reference_${Date.now()}.mp3`;
|
|
678
|
+
return await uploadReferenceAudio(voiceSource, name, signal);
|
|
679
|
+
}
|
|
680
|
+
if (typeof voiceSource === "string" && voiceSource) {
|
|
681
|
+
if (voiceSource.startsWith(baseTTSUrl)) {
|
|
682
|
+
const match = voiceSource.match(/\/gradio_api\/file=(.+)$/);
|
|
683
|
+
if (match) {
|
|
684
|
+
const path = decodeURIComponent(match[1]);
|
|
685
|
+
const origName = path.split(/[\\/]/).pop() || "reference.mp3";
|
|
686
|
+
return { path, orig_name: origName, meta: FILEDATA_META };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const res = await fetch(voiceSource, { signal });
|
|
690
|
+
if (!res.ok) throw new Error(`参考音频下载失败 HTTP ${res.status}`);
|
|
691
|
+
const blob = await res.blob();
|
|
692
|
+
const fileName = decodeURIComponent(voiceSource.split("?")[0].split("/").pop()) || "reference.mp3";
|
|
693
|
+
return await uploadReferenceAudio(blob, fileName, signal);
|
|
694
|
+
}
|
|
695
|
+
throw new Error("不支持的参考音频来源:voiceUrl 应为音频 URL 字符串、File/Blob 或 { path } 对象");
|
|
696
|
+
};
|
|
697
|
+
const submitCloneJob = async (data, signal) => {
|
|
698
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn`, {
|
|
699
|
+
method: "POST",
|
|
700
|
+
headers: { "Content-Type": "application/json" },
|
|
701
|
+
body: JSON.stringify({ data }),
|
|
702
|
+
signal
|
|
662
703
|
});
|
|
663
|
-
|
|
704
|
+
if (!res.ok) {
|
|
705
|
+
const text = await res.text().catch(() => "");
|
|
706
|
+
throw new Error(`克隆任务提交失败 HTTP ${res.status} ${text}`);
|
|
707
|
+
}
|
|
708
|
+
const json = await res.json();
|
|
709
|
+
if (!(json == null ? void 0 : json.event_id)) throw new Error("克隆任务提交失败:未返回 event_id");
|
|
710
|
+
return json.event_id;
|
|
711
|
+
};
|
|
712
|
+
const waitCloneResult = async (eventId, signal) => {
|
|
713
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn/${eventId}`, {
|
|
714
|
+
signal,
|
|
715
|
+
headers: { Accept: "text/event-stream" }
|
|
716
|
+
});
|
|
717
|
+
if (!res.ok) throw new Error(`合成结果监听失败 HTTP ${res.status}`);
|
|
718
|
+
const reader = res.body.getReader();
|
|
719
|
+
const decoder = new TextDecoder("utf-8");
|
|
720
|
+
let buffer = "";
|
|
721
|
+
let eventName = "message";
|
|
722
|
+
while (true) {
|
|
723
|
+
const { done, value } = await reader.read();
|
|
724
|
+
if (done) break;
|
|
725
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
726
|
+
let sep;
|
|
727
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
728
|
+
const frame = buffer.slice(0, sep);
|
|
729
|
+
buffer = buffer.slice(sep + 2);
|
|
730
|
+
const dataLines = [];
|
|
731
|
+
for (const line of frame.split("\n")) {
|
|
732
|
+
if (line.startsWith("event:")) eventName = line.slice(6).trim();
|
|
733
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
734
|
+
}
|
|
735
|
+
if (dataLines.length === 0) continue;
|
|
736
|
+
const payload = dataLines.join("\n");
|
|
737
|
+
if (eventName === "complete") {
|
|
738
|
+
return JSON.parse(payload);
|
|
739
|
+
}
|
|
740
|
+
if (eventName === "error") {
|
|
741
|
+
let msg = payload;
|
|
742
|
+
try {
|
|
743
|
+
const parsed = JSON.parse(payload);
|
|
744
|
+
msg = typeof parsed === "string" ? parsed : (parsed == null ? void 0 : parsed.message) || payload;
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
throw new Error(msg || "OmniVoice 合成失败");
|
|
748
|
+
}
|
|
749
|
+
eventName = "message";
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
throw new Error("OmniVoice 合成结果流提前中断");
|
|
753
|
+
};
|
|
754
|
+
const playAudioFromUrl = async (audioUrl, requestId, signal) => {
|
|
755
|
+
const audioRes = await fetch(audioUrl, { signal });
|
|
756
|
+
if (!audioRes.ok) throw new Error(`合成音频下载失败 HTTP ${audioRes.status}`);
|
|
757
|
+
const audioBlob = await audioRes.blob();
|
|
758
|
+
const objectUrl = URL.createObjectURL(audioBlob);
|
|
759
|
+
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
760
|
+
URL.revokeObjectURL(objectUrl);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const audio = new Audio(objectUrl);
|
|
764
|
+
currentAudio.value = audio;
|
|
765
|
+
const cleanup = () => {
|
|
766
|
+
URL.revokeObjectURL(objectUrl);
|
|
767
|
+
if (currentAudio.value === audio) currentAudio.value = null;
|
|
768
|
+
};
|
|
769
|
+
audio.onplaying = () => {
|
|
770
|
+
console.log("[OmniVoice] 音频开始播放");
|
|
771
|
+
speak();
|
|
772
|
+
};
|
|
773
|
+
audio.onended = () => {
|
|
774
|
+
console.log("[OmniVoice] 音频播放结束");
|
|
775
|
+
cleanup();
|
|
776
|
+
stopSpeaking();
|
|
777
|
+
};
|
|
778
|
+
audio.onerror = () => {
|
|
779
|
+
console.error("[OmniVoice] 音频播放失败");
|
|
780
|
+
cleanup();
|
|
781
|
+
stopSpeaking();
|
|
782
|
+
};
|
|
783
|
+
await audio.play();
|
|
784
|
+
};
|
|
664
785
|
const playTTS = async (text) => {
|
|
665
|
-
var _a, _b, _c, _d
|
|
786
|
+
var _a, _b, _c, _d;
|
|
787
|
+
console.log(props.humans);
|
|
788
|
+
let voiceUrl = "";
|
|
789
|
+
let voiceUrl2 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\4eef6d9157de756de02bccca2f66389382e2ac4acb0c674981aa9fe13dcc294c\\李梓萌.mp3";
|
|
790
|
+
let voiceUrl1 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\ff2665f538a4b0e5efbcd79f9f699236f5f0e52cad7e17f0664ddf431169be30\\guozj.mp3";
|
|
791
|
+
if (props.humans[0].speakSet.sex == "Female / 女") {
|
|
792
|
+
voiceUrl = voiceUrl2;
|
|
793
|
+
} else {
|
|
794
|
+
voiceUrl = voiceUrl1;
|
|
795
|
+
}
|
|
666
796
|
if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
|
|
667
797
|
abortTTS();
|
|
668
798
|
ttsAbortController = new AbortController();
|
|
@@ -672,133 +802,33 @@ const _sfc_main = {
|
|
|
672
802
|
try {
|
|
673
803
|
const cleanText = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/[#*_~]/g, "").replace(/\n+/g, ",").replace(/\s+/g, " ").replace(/[,。!?]{2,}/g, ",").trim();
|
|
674
804
|
if (!cleanText) return;
|
|
675
|
-
const
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
let age = ((_e = (_d = currentHuman.value) == null ? void 0 : _d.speakSet) == null ? void 0 : _e.age) || "Young Adult / 青年";
|
|
680
|
-
let sex = ((_g = (_f = currentHuman.value) == null ? void 0 : _f.speakSet) == null ? void 0 : _g.sex) || "Female / 女";
|
|
681
|
-
const data = [cleanText, "Auto", 32, 2, true, speed, null, true, true, sex, age, "Auto", "Auto", "Auto", "Auto"];
|
|
682
|
-
console.log("[OmniVoice] 提交请求:", { text: cleanText, length: cleanText.length, fn_index: 1 });
|
|
683
|
-
const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
|
|
684
|
-
method: "POST",
|
|
685
|
-
headers: { "Content-Type": "application/json" },
|
|
686
|
-
body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
|
|
687
|
-
signal
|
|
688
|
-
});
|
|
689
|
-
const joinData = await joinRes.json();
|
|
690
|
-
console.log("[OmniVoice] Step1 响应:", joinData);
|
|
691
|
-
console.log("[OmniVoice] Step2: 轮询等待结果");
|
|
692
|
-
let audioUrl = "";
|
|
693
|
-
let maxPolls = 60;
|
|
694
|
-
let pollInterval = 1e3;
|
|
695
|
-
while (!audioUrl && maxPolls > 0) {
|
|
696
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
697
|
-
await waitWithSignal(pollInterval, signal);
|
|
698
|
-
maxPolls--;
|
|
699
|
-
try {
|
|
700
|
-
const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
|
|
701
|
-
signal
|
|
702
|
-
});
|
|
703
|
-
const pollText = await pollRes.text();
|
|
704
|
-
const lines = pollText.split("\n");
|
|
705
|
-
for (const line of lines) {
|
|
706
|
-
const trimLine = line.trim();
|
|
707
|
-
if (!trimLine.startsWith("data: ")) continue;
|
|
708
|
-
try {
|
|
709
|
-
const json = JSON.parse(trimLine.replace("data: ", ""));
|
|
710
|
-
console.log("[OmniVoice] 轮询消息:", json);
|
|
711
|
-
if (json.msg === "process_completed" && ((_h = json.output) == null ? void 0 : _h.data)) {
|
|
712
|
-
const fileData = json.output.data[0];
|
|
713
|
-
console.log("[OmniVoice] 获取到音频:", fileData);
|
|
714
|
-
if (fileData == null ? void 0 : fileData.url) audioUrl = fileData.url;
|
|
715
|
-
else if (fileData == null ? void 0 : fileData.path) audioUrl = `${baseUrl}/gradio_api/file=${fileData.path}`;
|
|
716
|
-
else if (typeof fileData === "string") audioUrl = fileData.startsWith("http") ? fileData : `${baseUrl}/gradio_api/file=${fileData}`;
|
|
717
|
-
else if (Array.isArray(fileData) && fileData.length >= 2) {
|
|
718
|
-
const samplingRate = fileData[0];
|
|
719
|
-
const waveform = fileData[1];
|
|
720
|
-
console.log("[OmniVoice] 直接获取到音频数组:", { samplingRate, waveformLength: waveform == null ? void 0 : waveform.length });
|
|
721
|
-
if (samplingRate && waveform && Array.isArray(waveform)) {
|
|
722
|
-
const audioBuffer = new ArrayBuffer(waveform.length * 2);
|
|
723
|
-
const view = new DataView(audioBuffer);
|
|
724
|
-
for (let i = 0; i < waveform.length; i++) {
|
|
725
|
-
view.setInt16(i * 2, waveform[i], true);
|
|
726
|
-
}
|
|
727
|
-
const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
|
|
728
|
-
const audioObjectUrl = URL.createObjectURL(wavBlob);
|
|
729
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
730
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
currentAudio.value = new Audio(audioObjectUrl);
|
|
734
|
-
currentAudio.value.onplaying = () => {
|
|
735
|
-
console.log("[OmniVoice] 音频开始播放");
|
|
736
|
-
speak();
|
|
737
|
-
};
|
|
738
|
-
currentAudio.value.onended = () => {
|
|
739
|
-
console.log("[OmniVoice] 音频播放结束");
|
|
740
|
-
stopSpeaking();
|
|
741
|
-
currentAudio.value = null;
|
|
742
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
743
|
-
};
|
|
744
|
-
currentAudio.value.onerror = () => {
|
|
745
|
-
console.error("[OmniVoice] 音频播放失败");
|
|
746
|
-
stopSpeaking();
|
|
747
|
-
currentAudio.value = null;
|
|
748
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
749
|
-
};
|
|
750
|
-
await currentAudio.value.play();
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
break;
|
|
755
|
-
} else if (json.msg === "process_error") {
|
|
756
|
-
console.error("[OmniVoice] 处理错误:", json);
|
|
757
|
-
standby();
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
} catch (e) {
|
|
761
|
-
console.error("[OmniVoice] 解析轮询消息失败:", e);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
} catch (e) {
|
|
765
|
-
if ((e == null ? void 0 : e.name) === "AbortError") throw e;
|
|
766
|
-
console.warn("[OmniVoice] 轮询失败:", e.message);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (audioUrl) {
|
|
770
|
-
if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
|
|
771
|
-
console.log("[OmniVoice] 下载音频:", audioUrl);
|
|
772
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
773
|
-
const audioRes = await fetch(audioUrl, { signal });
|
|
774
|
-
const audioBlob = await audioRes.blob();
|
|
775
|
-
const audioObjectUrl = URL.createObjectURL(audioBlob);
|
|
776
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
777
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
currentAudio.value = new Audio(audioObjectUrl);
|
|
781
|
-
currentAudio.value.onplaying = () => {
|
|
782
|
-
console.log("[OmniVoice] 音频开始播放");
|
|
783
|
-
speak();
|
|
784
|
-
};
|
|
785
|
-
currentAudio.value.onended = () => {
|
|
786
|
-
console.log("[OmniVoice] 音频播放结束");
|
|
787
|
-
stopSpeaking();
|
|
788
|
-
currentAudio.value = null;
|
|
789
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
790
|
-
};
|
|
791
|
-
currentAudio.value.onerror = () => {
|
|
792
|
-
console.error("[OmniVoice] 音频播放失败");
|
|
793
|
-
stopSpeaking();
|
|
794
|
-
currentAudio.value = null;
|
|
795
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
796
|
-
};
|
|
797
|
-
await currentAudio.value.play();
|
|
798
|
-
} else {
|
|
799
|
-
console.error("[OmniVoice] 未获取到音频");
|
|
805
|
+
const speed = Number((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
|
|
806
|
+
const voiceSource = voiceUrl || ((_d = currentHuman.value) == null ? void 0 : _d.voiceUrl);
|
|
807
|
+
if (!voiceSource) {
|
|
808
|
+
console.error("[OmniVoice] 未配置参考音频 voiceUrl,无法进行音色克隆");
|
|
800
809
|
standby();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
let refFile = voiceFileCache.get(voiceSource);
|
|
813
|
+
if (!refFile) {
|
|
814
|
+
console.log("[OmniVoice] 准备参考音频:", typeof voiceSource === "string" ? voiceSource : "Blob/File");
|
|
815
|
+
refFile = await resolveVoiceFileData(voiceSource, signal);
|
|
816
|
+
voiceFileCache.set(voiceSource, refFile);
|
|
801
817
|
}
|
|
818
|
+
console.log("[OmniVoice] 参考音频就绪:", refFile);
|
|
819
|
+
const data = [cleanText, "Auto", refFile, "", null, 32, 2, true, speed, null, true, true];
|
|
820
|
+
console.log("[OmniVoice] 提交克隆请求:", { text: cleanText, length: cleanText.length });
|
|
821
|
+
const eventId = await submitCloneJob(data, signal);
|
|
822
|
+
const outputs = await waitCloneResult(eventId, signal);
|
|
823
|
+
const audioFile = outputs == null ? void 0 : outputs[0];
|
|
824
|
+
const statusText = outputs == null ? void 0 : outputs[1];
|
|
825
|
+
console.log("[OmniVoice] 合成结果:", { audioFile, statusText });
|
|
826
|
+
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
827
|
+
if (!audioFile || !audioFile.path && !audioFile.url) {
|
|
828
|
+
throw new Error(statusText || "OmniVoice 未返回合成音频");
|
|
829
|
+
}
|
|
830
|
+
const audioUrl = audioFile.url && audioFile.url.startsWith("http") ? audioFile.url : `${baseTTSUrl}/gradio_api/file=${audioFile.path}`;
|
|
831
|
+
await playAudioFromUrl(audioUrl, requestId, signal);
|
|
802
832
|
} catch (e) {
|
|
803
833
|
if ((e == null ? void 0 : e.name) === "AbortError") {
|
|
804
834
|
console.log("[OmniVoice] TTS已取消");
|
|
@@ -835,7 +865,7 @@ const _sfc_main = {
|
|
|
835
865
|
const formData = new FormData();
|
|
836
866
|
formData.append("file", blob, "recorded_audio.wav");
|
|
837
867
|
try {
|
|
838
|
-
const res = await fetch(
|
|
868
|
+
const res = await fetch(ASR_URL + "/transcribe", {
|
|
839
869
|
method: "POST",
|
|
840
870
|
body: formData
|
|
841
871
|
});
|
|
@@ -848,42 +878,6 @@ const _sfc_main = {
|
|
|
848
878
|
return { status: e, text: "" };
|
|
849
879
|
}
|
|
850
880
|
};
|
|
851
|
-
const createWavBlob = (audioBuffer, sampleRate, channels, bitsPerSample) => {
|
|
852
|
-
const bytesPerSample = bitsPerSample / 8;
|
|
853
|
-
const blockAlign = channels * bytesPerSample;
|
|
854
|
-
const dataSize = audioBuffer.byteLength;
|
|
855
|
-
const buffer = new ArrayBuffer(44 + dataSize);
|
|
856
|
-
const view = new DataView(buffer);
|
|
857
|
-
view.setUint8(0, 82);
|
|
858
|
-
view.setUint8(1, 73);
|
|
859
|
-
view.setUint8(2, 70);
|
|
860
|
-
view.setUint8(3, 70);
|
|
861
|
-
view.setUint32(4, 36 + dataSize, true);
|
|
862
|
-
view.setUint8(8, 87);
|
|
863
|
-
view.setUint8(9, 65);
|
|
864
|
-
view.setUint8(10, 86);
|
|
865
|
-
view.setUint8(11, 69);
|
|
866
|
-
view.setUint8(12, 102);
|
|
867
|
-
view.setUint8(13, 109);
|
|
868
|
-
view.setUint8(14, 116);
|
|
869
|
-
view.setUint8(15, 32);
|
|
870
|
-
view.setUint32(16, 16, true);
|
|
871
|
-
view.setUint16(20, 1, true);
|
|
872
|
-
view.setUint16(22, channels, true);
|
|
873
|
-
view.setUint32(24, sampleRate, true);
|
|
874
|
-
view.setUint32(28, sampleRate * blockAlign, true);
|
|
875
|
-
view.setUint16(32, blockAlign, true);
|
|
876
|
-
view.setUint16(34, bitsPerSample, true);
|
|
877
|
-
view.setUint8(36, 100);
|
|
878
|
-
view.setUint8(37, 97);
|
|
879
|
-
view.setUint8(38, 116);
|
|
880
|
-
view.setUint8(39, 97);
|
|
881
|
-
view.setUint32(40, dataSize, true);
|
|
882
|
-
const audioData = new Uint8Array(audioBuffer);
|
|
883
|
-
const destData = new Uint8Array(buffer, 44);
|
|
884
|
-
destData.set(audioData);
|
|
885
|
-
return new Blob([buffer], { type: "audio/wav" });
|
|
886
|
-
};
|
|
887
881
|
let recorder2 = vue.ref(null);
|
|
888
882
|
const initRecorder = () => {
|
|
889
883
|
console.log("initRecorder");
|
|
@@ -935,7 +929,7 @@ const _sfc_main = {
|
|
|
935
929
|
};
|
|
936
930
|
}
|
|
937
931
|
};
|
|
938
|
-
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
932
|
+
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c25016c"]]);
|
|
939
933
|
DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
|
|
940
934
|
const components = [DemoButton, DemoInput, DigitalHuman];
|
|
941
935
|
const install = (app) => {
|
package/dist/ruiyun-human.es.js
CHANGED
|
@@ -383,6 +383,8 @@ var recorder = { exports: {} };
|
|
|
383
383
|
var recorderExports = recorder.exports;
|
|
384
384
|
var jsAudioRecorder = recorderExports;
|
|
385
385
|
const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
|
|
386
|
+
const baseTTSUrl = "http://192.168.3.182:8002";
|
|
387
|
+
const ASR_URL = "http://192.168.3.182:9988";
|
|
386
388
|
const _sfc_main = {
|
|
387
389
|
__name: "index",
|
|
388
390
|
props: {
|
|
@@ -645,22 +647,150 @@ const _sfc_main = {
|
|
|
645
647
|
currentAudio.value = null;
|
|
646
648
|
}
|
|
647
649
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
650
|
+
const voiceFileCache = /* @__PURE__ */ new Map();
|
|
651
|
+
const FILEDATA_META = { _type: "gradio.FileData" };
|
|
652
|
+
const uploadReferenceAudio = async (blob, filename, signal) => {
|
|
653
|
+
const form = new FormData();
|
|
654
|
+
form.append("files", blob, filename);
|
|
655
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/upload`, {
|
|
656
|
+
method: "POST",
|
|
657
|
+
body: form,
|
|
658
|
+
signal
|
|
659
|
+
});
|
|
660
|
+
if (!res.ok) throw new Error(`参考音频上传失败 HTTP ${res.status}`);
|
|
661
|
+
const paths = await res.json();
|
|
662
|
+
const path = Array.isArray(paths) ? paths[0] : paths;
|
|
663
|
+
if (!path) throw new Error("参考音频上传失败:服务未返回文件路径");
|
|
664
|
+
return { path, orig_name: filename, meta: FILEDATA_META };
|
|
665
|
+
};
|
|
666
|
+
const resolveVoiceFileData = async (voiceSource, signal) => {
|
|
667
|
+
if (voiceSource && typeof voiceSource === "object" && !Array.isArray(voiceSource) && voiceSource.path) {
|
|
668
|
+
return {
|
|
669
|
+
path: voiceSource.path,
|
|
670
|
+
orig_name: voiceSource.orig_name || "reference.mp3",
|
|
671
|
+
meta: FILEDATA_META
|
|
658
672
|
};
|
|
659
|
-
|
|
673
|
+
}
|
|
674
|
+
if (voiceSource instanceof Blob) {
|
|
675
|
+
const name = voiceSource.name || `reference_${Date.now()}.mp3`;
|
|
676
|
+
return await uploadReferenceAudio(voiceSource, name, signal);
|
|
677
|
+
}
|
|
678
|
+
if (typeof voiceSource === "string" && voiceSource) {
|
|
679
|
+
if (voiceSource.startsWith(baseTTSUrl)) {
|
|
680
|
+
const match = voiceSource.match(/\/gradio_api\/file=(.+)$/);
|
|
681
|
+
if (match) {
|
|
682
|
+
const path = decodeURIComponent(match[1]);
|
|
683
|
+
const origName = path.split(/[\\/]/).pop() || "reference.mp3";
|
|
684
|
+
return { path, orig_name: origName, meta: FILEDATA_META };
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const res = await fetch(voiceSource, { signal });
|
|
688
|
+
if (!res.ok) throw new Error(`参考音频下载失败 HTTP ${res.status}`);
|
|
689
|
+
const blob = await res.blob();
|
|
690
|
+
const fileName = decodeURIComponent(voiceSource.split("?")[0].split("/").pop()) || "reference.mp3";
|
|
691
|
+
return await uploadReferenceAudio(blob, fileName, signal);
|
|
692
|
+
}
|
|
693
|
+
throw new Error("不支持的参考音频来源:voiceUrl 应为音频 URL 字符串、File/Blob 或 { path } 对象");
|
|
694
|
+
};
|
|
695
|
+
const submitCloneJob = async (data, signal) => {
|
|
696
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn`, {
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: { "Content-Type": "application/json" },
|
|
699
|
+
body: JSON.stringify({ data }),
|
|
700
|
+
signal
|
|
660
701
|
});
|
|
661
|
-
|
|
702
|
+
if (!res.ok) {
|
|
703
|
+
const text = await res.text().catch(() => "");
|
|
704
|
+
throw new Error(`克隆任务提交失败 HTTP ${res.status} ${text}`);
|
|
705
|
+
}
|
|
706
|
+
const json = await res.json();
|
|
707
|
+
if (!(json == null ? void 0 : json.event_id)) throw new Error("克隆任务提交失败:未返回 event_id");
|
|
708
|
+
return json.event_id;
|
|
709
|
+
};
|
|
710
|
+
const waitCloneResult = async (eventId, signal) => {
|
|
711
|
+
const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn/${eventId}`, {
|
|
712
|
+
signal,
|
|
713
|
+
headers: { Accept: "text/event-stream" }
|
|
714
|
+
});
|
|
715
|
+
if (!res.ok) throw new Error(`合成结果监听失败 HTTP ${res.status}`);
|
|
716
|
+
const reader = res.body.getReader();
|
|
717
|
+
const decoder = new TextDecoder("utf-8");
|
|
718
|
+
let buffer = "";
|
|
719
|
+
let eventName = "message";
|
|
720
|
+
while (true) {
|
|
721
|
+
const { done, value } = await reader.read();
|
|
722
|
+
if (done) break;
|
|
723
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
724
|
+
let sep;
|
|
725
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
726
|
+
const frame = buffer.slice(0, sep);
|
|
727
|
+
buffer = buffer.slice(sep + 2);
|
|
728
|
+
const dataLines = [];
|
|
729
|
+
for (const line of frame.split("\n")) {
|
|
730
|
+
if (line.startsWith("event:")) eventName = line.slice(6).trim();
|
|
731
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
732
|
+
}
|
|
733
|
+
if (dataLines.length === 0) continue;
|
|
734
|
+
const payload = dataLines.join("\n");
|
|
735
|
+
if (eventName === "complete") {
|
|
736
|
+
return JSON.parse(payload);
|
|
737
|
+
}
|
|
738
|
+
if (eventName === "error") {
|
|
739
|
+
let msg = payload;
|
|
740
|
+
try {
|
|
741
|
+
const parsed = JSON.parse(payload);
|
|
742
|
+
msg = typeof parsed === "string" ? parsed : (parsed == null ? void 0 : parsed.message) || payload;
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
throw new Error(msg || "OmniVoice 合成失败");
|
|
746
|
+
}
|
|
747
|
+
eventName = "message";
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
throw new Error("OmniVoice 合成结果流提前中断");
|
|
751
|
+
};
|
|
752
|
+
const playAudioFromUrl = async (audioUrl, requestId, signal) => {
|
|
753
|
+
const audioRes = await fetch(audioUrl, { signal });
|
|
754
|
+
if (!audioRes.ok) throw new Error(`合成音频下载失败 HTTP ${audioRes.status}`);
|
|
755
|
+
const audioBlob = await audioRes.blob();
|
|
756
|
+
const objectUrl = URL.createObjectURL(audioBlob);
|
|
757
|
+
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
758
|
+
URL.revokeObjectURL(objectUrl);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const audio = new Audio(objectUrl);
|
|
762
|
+
currentAudio.value = audio;
|
|
763
|
+
const cleanup = () => {
|
|
764
|
+
URL.revokeObjectURL(objectUrl);
|
|
765
|
+
if (currentAudio.value === audio) currentAudio.value = null;
|
|
766
|
+
};
|
|
767
|
+
audio.onplaying = () => {
|
|
768
|
+
console.log("[OmniVoice] 音频开始播放");
|
|
769
|
+
speak();
|
|
770
|
+
};
|
|
771
|
+
audio.onended = () => {
|
|
772
|
+
console.log("[OmniVoice] 音频播放结束");
|
|
773
|
+
cleanup();
|
|
774
|
+
stopSpeaking();
|
|
775
|
+
};
|
|
776
|
+
audio.onerror = () => {
|
|
777
|
+
console.error("[OmniVoice] 音频播放失败");
|
|
778
|
+
cleanup();
|
|
779
|
+
stopSpeaking();
|
|
780
|
+
};
|
|
781
|
+
await audio.play();
|
|
782
|
+
};
|
|
662
783
|
const playTTS = async (text) => {
|
|
663
|
-
var _a, _b, _c, _d
|
|
784
|
+
var _a, _b, _c, _d;
|
|
785
|
+
console.log(props.humans);
|
|
786
|
+
let voiceUrl = "";
|
|
787
|
+
let voiceUrl2 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\4eef6d9157de756de02bccca2f66389382e2ac4acb0c674981aa9fe13dcc294c\\李梓萌.mp3";
|
|
788
|
+
let voiceUrl1 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\ff2665f538a4b0e5efbcd79f9f699236f5f0e52cad7e17f0664ddf431169be30\\guozj.mp3";
|
|
789
|
+
if (props.humans[0].speakSet.sex == "Female / 女") {
|
|
790
|
+
voiceUrl = voiceUrl2;
|
|
791
|
+
} else {
|
|
792
|
+
voiceUrl = voiceUrl1;
|
|
793
|
+
}
|
|
664
794
|
if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
|
|
665
795
|
abortTTS();
|
|
666
796
|
ttsAbortController = new AbortController();
|
|
@@ -670,133 +800,33 @@ const _sfc_main = {
|
|
|
670
800
|
try {
|
|
671
801
|
const cleanText = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/[#*_~]/g, "").replace(/\n+/g, ",").replace(/\s+/g, " ").replace(/[,。!?]{2,}/g, ",").trim();
|
|
672
802
|
if (!cleanText) return;
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
let age = ((_e = (_d = currentHuman.value) == null ? void 0 : _d.speakSet) == null ? void 0 : _e.age) || "Young Adult / 青年";
|
|
678
|
-
let sex = ((_g = (_f = currentHuman.value) == null ? void 0 : _f.speakSet) == null ? void 0 : _g.sex) || "Female / 女";
|
|
679
|
-
const data = [cleanText, "Auto", 32, 2, true, speed, null, true, true, sex, age, "Auto", "Auto", "Auto", "Auto"];
|
|
680
|
-
console.log("[OmniVoice] 提交请求:", { text: cleanText, length: cleanText.length, fn_index: 1 });
|
|
681
|
-
const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
|
|
682
|
-
method: "POST",
|
|
683
|
-
headers: { "Content-Type": "application/json" },
|
|
684
|
-
body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
|
|
685
|
-
signal
|
|
686
|
-
});
|
|
687
|
-
const joinData = await joinRes.json();
|
|
688
|
-
console.log("[OmniVoice] Step1 响应:", joinData);
|
|
689
|
-
console.log("[OmniVoice] Step2: 轮询等待结果");
|
|
690
|
-
let audioUrl = "";
|
|
691
|
-
let maxPolls = 60;
|
|
692
|
-
let pollInterval = 1e3;
|
|
693
|
-
while (!audioUrl && maxPolls > 0) {
|
|
694
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
695
|
-
await waitWithSignal(pollInterval, signal);
|
|
696
|
-
maxPolls--;
|
|
697
|
-
try {
|
|
698
|
-
const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
|
|
699
|
-
signal
|
|
700
|
-
});
|
|
701
|
-
const pollText = await pollRes.text();
|
|
702
|
-
const lines = pollText.split("\n");
|
|
703
|
-
for (const line of lines) {
|
|
704
|
-
const trimLine = line.trim();
|
|
705
|
-
if (!trimLine.startsWith("data: ")) continue;
|
|
706
|
-
try {
|
|
707
|
-
const json = JSON.parse(trimLine.replace("data: ", ""));
|
|
708
|
-
console.log("[OmniVoice] 轮询消息:", json);
|
|
709
|
-
if (json.msg === "process_completed" && ((_h = json.output) == null ? void 0 : _h.data)) {
|
|
710
|
-
const fileData = json.output.data[0];
|
|
711
|
-
console.log("[OmniVoice] 获取到音频:", fileData);
|
|
712
|
-
if (fileData == null ? void 0 : fileData.url) audioUrl = fileData.url;
|
|
713
|
-
else if (fileData == null ? void 0 : fileData.path) audioUrl = `${baseUrl}/gradio_api/file=${fileData.path}`;
|
|
714
|
-
else if (typeof fileData === "string") audioUrl = fileData.startsWith("http") ? fileData : `${baseUrl}/gradio_api/file=${fileData}`;
|
|
715
|
-
else if (Array.isArray(fileData) && fileData.length >= 2) {
|
|
716
|
-
const samplingRate = fileData[0];
|
|
717
|
-
const waveform = fileData[1];
|
|
718
|
-
console.log("[OmniVoice] 直接获取到音频数组:", { samplingRate, waveformLength: waveform == null ? void 0 : waveform.length });
|
|
719
|
-
if (samplingRate && waveform && Array.isArray(waveform)) {
|
|
720
|
-
const audioBuffer = new ArrayBuffer(waveform.length * 2);
|
|
721
|
-
const view = new DataView(audioBuffer);
|
|
722
|
-
for (let i = 0; i < waveform.length; i++) {
|
|
723
|
-
view.setInt16(i * 2, waveform[i], true);
|
|
724
|
-
}
|
|
725
|
-
const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
|
|
726
|
-
const audioObjectUrl = URL.createObjectURL(wavBlob);
|
|
727
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
728
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
currentAudio.value = new Audio(audioObjectUrl);
|
|
732
|
-
currentAudio.value.onplaying = () => {
|
|
733
|
-
console.log("[OmniVoice] 音频开始播放");
|
|
734
|
-
speak();
|
|
735
|
-
};
|
|
736
|
-
currentAudio.value.onended = () => {
|
|
737
|
-
console.log("[OmniVoice] 音频播放结束");
|
|
738
|
-
stopSpeaking();
|
|
739
|
-
currentAudio.value = null;
|
|
740
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
741
|
-
};
|
|
742
|
-
currentAudio.value.onerror = () => {
|
|
743
|
-
console.error("[OmniVoice] 音频播放失败");
|
|
744
|
-
stopSpeaking();
|
|
745
|
-
currentAudio.value = null;
|
|
746
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
747
|
-
};
|
|
748
|
-
await currentAudio.value.play();
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
break;
|
|
753
|
-
} else if (json.msg === "process_error") {
|
|
754
|
-
console.error("[OmniVoice] 处理错误:", json);
|
|
755
|
-
standby();
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
} catch (e) {
|
|
759
|
-
console.error("[OmniVoice] 解析轮询消息失败:", e);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
} catch (e) {
|
|
763
|
-
if ((e == null ? void 0 : e.name) === "AbortError") throw e;
|
|
764
|
-
console.warn("[OmniVoice] 轮询失败:", e.message);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
if (audioUrl) {
|
|
768
|
-
if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
|
|
769
|
-
console.log("[OmniVoice] 下载音频:", audioUrl);
|
|
770
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
771
|
-
const audioRes = await fetch(audioUrl, { signal });
|
|
772
|
-
const audioBlob = await audioRes.blob();
|
|
773
|
-
const audioObjectUrl = URL.createObjectURL(audioBlob);
|
|
774
|
-
if (signal.aborted || currentTtsRequestId.value !== requestId) {
|
|
775
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
currentAudio.value = new Audio(audioObjectUrl);
|
|
779
|
-
currentAudio.value.onplaying = () => {
|
|
780
|
-
console.log("[OmniVoice] 音频开始播放");
|
|
781
|
-
speak();
|
|
782
|
-
};
|
|
783
|
-
currentAudio.value.onended = () => {
|
|
784
|
-
console.log("[OmniVoice] 音频播放结束");
|
|
785
|
-
stopSpeaking();
|
|
786
|
-
currentAudio.value = null;
|
|
787
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
788
|
-
};
|
|
789
|
-
currentAudio.value.onerror = () => {
|
|
790
|
-
console.error("[OmniVoice] 音频播放失败");
|
|
791
|
-
stopSpeaking();
|
|
792
|
-
currentAudio.value = null;
|
|
793
|
-
URL.revokeObjectURL(audioObjectUrl);
|
|
794
|
-
};
|
|
795
|
-
await currentAudio.value.play();
|
|
796
|
-
} else {
|
|
797
|
-
console.error("[OmniVoice] 未获取到音频");
|
|
803
|
+
const speed = Number((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
|
|
804
|
+
const voiceSource = voiceUrl || ((_d = currentHuman.value) == null ? void 0 : _d.voiceUrl);
|
|
805
|
+
if (!voiceSource) {
|
|
806
|
+
console.error("[OmniVoice] 未配置参考音频 voiceUrl,无法进行音色克隆");
|
|
798
807
|
standby();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
let refFile = voiceFileCache.get(voiceSource);
|
|
811
|
+
if (!refFile) {
|
|
812
|
+
console.log("[OmniVoice] 准备参考音频:", typeof voiceSource === "string" ? voiceSource : "Blob/File");
|
|
813
|
+
refFile = await resolveVoiceFileData(voiceSource, signal);
|
|
814
|
+
voiceFileCache.set(voiceSource, refFile);
|
|
799
815
|
}
|
|
816
|
+
console.log("[OmniVoice] 参考音频就绪:", refFile);
|
|
817
|
+
const data = [cleanText, "Auto", refFile, "", null, 32, 2, true, speed, null, true, true];
|
|
818
|
+
console.log("[OmniVoice] 提交克隆请求:", { text: cleanText, length: cleanText.length });
|
|
819
|
+
const eventId = await submitCloneJob(data, signal);
|
|
820
|
+
const outputs = await waitCloneResult(eventId, signal);
|
|
821
|
+
const audioFile = outputs == null ? void 0 : outputs[0];
|
|
822
|
+
const statusText = outputs == null ? void 0 : outputs[1];
|
|
823
|
+
console.log("[OmniVoice] 合成结果:", { audioFile, statusText });
|
|
824
|
+
if (signal.aborted || currentTtsRequestId.value !== requestId) return;
|
|
825
|
+
if (!audioFile || !audioFile.path && !audioFile.url) {
|
|
826
|
+
throw new Error(statusText || "OmniVoice 未返回合成音频");
|
|
827
|
+
}
|
|
828
|
+
const audioUrl = audioFile.url && audioFile.url.startsWith("http") ? audioFile.url : `${baseTTSUrl}/gradio_api/file=${audioFile.path}`;
|
|
829
|
+
await playAudioFromUrl(audioUrl, requestId, signal);
|
|
800
830
|
} catch (e) {
|
|
801
831
|
if ((e == null ? void 0 : e.name) === "AbortError") {
|
|
802
832
|
console.log("[OmniVoice] TTS已取消");
|
|
@@ -833,7 +863,7 @@ const _sfc_main = {
|
|
|
833
863
|
const formData = new FormData();
|
|
834
864
|
formData.append("file", blob, "recorded_audio.wav");
|
|
835
865
|
try {
|
|
836
|
-
const res = await fetch(
|
|
866
|
+
const res = await fetch(ASR_URL + "/transcribe", {
|
|
837
867
|
method: "POST",
|
|
838
868
|
body: formData
|
|
839
869
|
});
|
|
@@ -846,42 +876,6 @@ const _sfc_main = {
|
|
|
846
876
|
return { status: e, text: "" };
|
|
847
877
|
}
|
|
848
878
|
};
|
|
849
|
-
const createWavBlob = (audioBuffer, sampleRate, channels, bitsPerSample) => {
|
|
850
|
-
const bytesPerSample = bitsPerSample / 8;
|
|
851
|
-
const blockAlign = channels * bytesPerSample;
|
|
852
|
-
const dataSize = audioBuffer.byteLength;
|
|
853
|
-
const buffer = new ArrayBuffer(44 + dataSize);
|
|
854
|
-
const view = new DataView(buffer);
|
|
855
|
-
view.setUint8(0, 82);
|
|
856
|
-
view.setUint8(1, 73);
|
|
857
|
-
view.setUint8(2, 70);
|
|
858
|
-
view.setUint8(3, 70);
|
|
859
|
-
view.setUint32(4, 36 + dataSize, true);
|
|
860
|
-
view.setUint8(8, 87);
|
|
861
|
-
view.setUint8(9, 65);
|
|
862
|
-
view.setUint8(10, 86);
|
|
863
|
-
view.setUint8(11, 69);
|
|
864
|
-
view.setUint8(12, 102);
|
|
865
|
-
view.setUint8(13, 109);
|
|
866
|
-
view.setUint8(14, 116);
|
|
867
|
-
view.setUint8(15, 32);
|
|
868
|
-
view.setUint32(16, 16, true);
|
|
869
|
-
view.setUint16(20, 1, true);
|
|
870
|
-
view.setUint16(22, channels, true);
|
|
871
|
-
view.setUint32(24, sampleRate, true);
|
|
872
|
-
view.setUint32(28, sampleRate * blockAlign, true);
|
|
873
|
-
view.setUint16(32, blockAlign, true);
|
|
874
|
-
view.setUint16(34, bitsPerSample, true);
|
|
875
|
-
view.setUint8(36, 100);
|
|
876
|
-
view.setUint8(37, 97);
|
|
877
|
-
view.setUint8(38, 116);
|
|
878
|
-
view.setUint8(39, 97);
|
|
879
|
-
view.setUint32(40, dataSize, true);
|
|
880
|
-
const audioData = new Uint8Array(audioBuffer);
|
|
881
|
-
const destData = new Uint8Array(buffer, 44);
|
|
882
|
-
destData.set(audioData);
|
|
883
|
-
return new Blob([buffer], { type: "audio/wav" });
|
|
884
|
-
};
|
|
885
879
|
let recorder2 = ref(null);
|
|
886
880
|
const initRecorder = () => {
|
|
887
881
|
console.log("initRecorder");
|
|
@@ -933,7 +927,7 @@ const _sfc_main = {
|
|
|
933
927
|
};
|
|
934
928
|
}
|
|
935
929
|
};
|
|
936
|
-
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
930
|
+
const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c25016c"]]);
|
|
937
931
|
DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
|
|
938
932
|
const components = [DemoButton, DemoInput, DigitalHuman];
|
|
939
933
|
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-
|
|
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-9c25016c]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-9c25016c]{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.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "数字人初步实现",
|
|
6
6
|
"main": "./dist/ruiyun-human.cjs.js",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"postcss": "^8.5.20",
|
|
30
30
|
"tailwindcss": "^4.3.3",
|
|
31
31
|
"terser": "^5.49.0",
|
|
32
|
-
"vite": "^5.1.4"
|
|
33
|
-
"vue": "^3.4.21"
|
|
32
|
+
"vite": "^5.1.4"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
35
|
"js-audio-recorder": "^1.0.7",
|
|
37
|
-
"vue-router": "^4.6.4"
|
|
36
|
+
"vue-router": "^4.6.4",
|
|
37
|
+
"vue": "^3.4.21"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/dist/favicon.svg
DELETED