首次上传
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
|
||||
Promise.prototype.finally = function(callback) {
|
||||
const promise = this.constructor;
|
||||
return this.then(
|
||||
(value) => promise.resolve(callback()).then(() => value),
|
||||
(reason) => promise.resolve(callback()).then(() => {
|
||||
throw reason;
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
;
|
||||
if (typeof uni !== "undefined" && uni && uni.requireGlobal) {
|
||||
const global = uni.requireGlobal();
|
||||
ArrayBuffer = global.ArrayBuffer;
|
||||
Int8Array = global.Int8Array;
|
||||
Uint8Array = global.Uint8Array;
|
||||
Uint8ClampedArray = global.Uint8ClampedArray;
|
||||
Int16Array = global.Int16Array;
|
||||
Uint16Array = global.Uint16Array;
|
||||
Int32Array = global.Int32Array;
|
||||
Uint32Array = global.Uint32Array;
|
||||
Float32Array = global.Float32Array;
|
||||
Float64Array = global.Float64Array;
|
||||
BigInt64Array = global.BigInt64Array;
|
||||
BigUint64Array = global.BigUint64Array;
|
||||
}
|
||||
;
|
||||
if (uni.restoreGlobal) {
|
||||
uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval);
|
||||
}
|
||||
(function(vue) {
|
||||
"use strict";
|
||||
function formatAppLog(type, filename, ...args) {
|
||||
if (uni.__log__) {
|
||||
uni.__log__(type, filename, ...args);
|
||||
} else {
|
||||
console[type].apply(console, [...args, filename]);
|
||||
}
|
||||
}
|
||||
const _export_sfc = (sfc, props) => {
|
||||
const target = sfc.__vccOpts || sfc;
|
||||
for (const [key, val] of props) {
|
||||
target[key] = val;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
const _sfc_main$1 = {
|
||||
data() {
|
||||
return {
|
||||
inputAreaHeight: 80,
|
||||
status: "未录音",
|
||||
recorder: null,
|
||||
// 录音实例
|
||||
recordTimer: null
|
||||
// 定时读取定时器
|
||||
};
|
||||
},
|
||||
onUnload() {
|
||||
this.stopRecord();
|
||||
},
|
||||
methods: {
|
||||
// 申请录音权限
|
||||
async applyRecordPermission() {
|
||||
return true;
|
||||
},
|
||||
// 开始录音
|
||||
async startRecord() {
|
||||
const AudioRecord = plus.android.importClass("android.media.AudioRecord");
|
||||
const AudioFormat = plus.android.importClass("android.media.AudioFormat");
|
||||
const MediaRecorder = plus.android.importClass("android.media.MediaRecorder");
|
||||
plus.android.importClass("android.content.Context");
|
||||
plus.android.runtimeMainActivity();
|
||||
const SAMPLE_RATE = 44100;
|
||||
const CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
|
||||
const AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
||||
const BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
|
||||
const audioRecord = new AudioRecord(
|
||||
MediaRecorder.AudioSource.MIC,
|
||||
// 麦克风源
|
||||
SAMPLE_RATE,
|
||||
CHANNEL_CONFIG,
|
||||
AUDIO_FORMAT,
|
||||
BUFFER_SIZE * 2
|
||||
// 缓冲区放大2倍,避免数据溢出
|
||||
);
|
||||
const pcmDataList = [];
|
||||
const SLICE_DURATION = 200;
|
||||
const SAMPLES_PER_SLICE = SAMPLE_RATE * (SLICE_DURATION / 1e3);
|
||||
function createShortArray(length) {
|
||||
try {
|
||||
const ArrayClass = plus.android.importClass("java.lang.reflect.Array");
|
||||
const ShortClass = plus.android.importClass("java.lang.Short").TYPE;
|
||||
return ArrayClass.newInstance(ShortClass, length);
|
||||
} catch (e) {
|
||||
const ShortArray = plus.android.createInstance("short[]", [length]);
|
||||
return ShortArray;
|
||||
}
|
||||
}
|
||||
audioRecord.startRecording();
|
||||
formatAppLog("log", "at pages/index/index.vue:127", "Android原生录音启动,开始实时PCM捕获");
|
||||
const readPCMData = () => {
|
||||
formatAppLog("log", "at pages/index/index.vue:131", "持续读取PCM数据到内存(修复newArray报错)", audioRecord.getRecordingState(), AudioRecord.RECORDSTATE_RECORDING);
|
||||
if (!audioRecord || audioRecord.getRecordingState() !== AudioRecord.RECORDSTATE_RECORDING)
|
||||
return;
|
||||
const shortArray = createShortArray(BUFFER_SIZE);
|
||||
const readSize = audioRecord.read(shortArray, 0, BUFFER_SIZE);
|
||||
formatAppLog("log", "at pages/index/index.vue:139", "读取麦克风的PCM数据", readSize);
|
||||
if (readSize > 0) {
|
||||
const pcmChunk = [];
|
||||
for (let i = 0; i < readSize; i++) {
|
||||
try {
|
||||
pcmChunk.push(plus.android.getArrayElement(shortArray, i));
|
||||
} catch (e) {
|
||||
pcmChunk.push(plus.android.getAttribute(shortArray, i));
|
||||
}
|
||||
}
|
||||
pcmDataList.push(...pcmChunk);
|
||||
}
|
||||
setTimeout(readPCMData, 0);
|
||||
};
|
||||
setTimeout(() => {
|
||||
readPCMData();
|
||||
}, 1e3);
|
||||
setInterval(() => {
|
||||
if (pcmDataList.length >= SAMPLES_PER_SLICE) {
|
||||
const slicePCM = pcmDataList.splice(0, SAMPLES_PER_SLICE);
|
||||
handlePCMSlice(slicePCM, SAMPLE_RATE);
|
||||
formatAppLog("log", "at pages/index/index.vue:171", `截取200ms PCM切片,采样点数:${slicePCM.length}`);
|
||||
}
|
||||
}, SLICE_DURATION);
|
||||
},
|
||||
// 停止录音
|
||||
stopRecord() {
|
||||
if (this.recorder) {
|
||||
this.recorder.stop();
|
||||
this.recorder = null;
|
||||
}
|
||||
if (this.recordTimer) {
|
||||
clearInterval(this.recordTimer);
|
||||
this.recordTimer = null;
|
||||
}
|
||||
this.status = "录音已停止";
|
||||
}
|
||||
}
|
||||
};
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return vue.openBlock(), vue.createElementBlock("view", { class: "ai-chat-container" }, [
|
||||
vue.createElementVNode(
|
||||
"scroll-view",
|
||||
{
|
||||
class: "chat-list",
|
||||
"scroll-y": "",
|
||||
"scroll-bottom": "{{scrollBottom}}",
|
||||
style: vue.normalizeStyle({ height: `calc(100vh - ${$data.inputAreaHeight}px)` })
|
||||
},
|
||||
[
|
||||
vue.createElementVNode("view", { class: "chat-item system" }, [
|
||||
vue.createElementVNode("view", { class: "chat-avatar system-avatar" }, [
|
||||
vue.createElementVNode("text", { class: "iconfont icon-robot" })
|
||||
]),
|
||||
vue.createElementVNode("view", { class: "chat-content" }, [
|
||||
vue.createElementVNode("view", { class: "content-text" }, "你好,我是智能助手,有什么可以帮你的?")
|
||||
])
|
||||
]),
|
||||
vue.createElementVNode("view", { class: "chat-item user" }, [
|
||||
vue.createElementVNode("view", { class: "chat-content" }, [
|
||||
vue.createElementVNode("view", { class: "content-text" }, "请问uniapp怎么实现语音流式传输?")
|
||||
]),
|
||||
vue.createElementVNode("view", { class: "chat-avatar user-avatar" }, [
|
||||
vue.createElementVNode("text", { class: "iconfont icon-user" })
|
||||
])
|
||||
]),
|
||||
vue.createElementVNode("view", { class: "chat-item ai" }, [
|
||||
vue.createElementVNode("view", { class: "chat-avatar ai-avatar" }, [
|
||||
vue.createElementVNode("text", { class: "iconfont icon-ai" })
|
||||
]),
|
||||
vue.createElementVNode("view", { class: "chat-content" }, [
|
||||
vue.createElementVNode("view", { class: "content-text" }, " UniApp实现语音流式传输可通过5+ API采集音频帧,结合WebSocket分块发送,核心步骤包括: 1. 初始化录音实例(16k采样率、单声道PCM); 2. 建立WebSocket连接; 3. 分块发送音频数据; 4. 接收实时ASR结果。 ")
|
||||
])
|
||||
])
|
||||
],
|
||||
4
|
||||
/* STYLE */
|
||||
),
|
||||
vue.createElementVNode(
|
||||
"view",
|
||||
{
|
||||
class: "input-area",
|
||||
style: vue.normalizeStyle({ height: $data.inputAreaHeight + "px" })
|
||||
},
|
||||
[
|
||||
vue.createElementVNode("view", { class: "send-btn-area" }, [
|
||||
vue.createElementVNode(
|
||||
"button",
|
||||
{
|
||||
class: "hold-send-btn",
|
||||
onTouchstart: _cache[0] || (_cache[0] = (...args) => $options.startRecord && $options.startRecord(...args))
|
||||
},
|
||||
[
|
||||
vue.createElementVNode(
|
||||
"text",
|
||||
{ class: "btn-text" },
|
||||
vue.toDisplayString($data.status ? "松开发送" : "按住 说话/发送"),
|
||||
1
|
||||
/* TEXT */
|
||||
)
|
||||
],
|
||||
32
|
||||
/* NEED_HYDRATION */
|
||||
)
|
||||
])
|
||||
],
|
||||
4
|
||||
/* STYLE */
|
||||
)
|
||||
]);
|
||||
}
|
||||
const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__scopeId", "data-v-1cf27b2a"], ["__file", "C:/Users/Administrator/Desktop/新建文件夹 (8)/测试流式传输uniapp/pages/index/index.vue"]]);
|
||||
__definePage("pages/index/index", PagesIndexIndex);
|
||||
const _sfc_main = {
|
||||
onLaunch: function() {
|
||||
formatAppLog("log", "at App.vue:4", "App Launch");
|
||||
},
|
||||
onShow: function() {
|
||||
formatAppLog("log", "at App.vue:7", "App Show");
|
||||
},
|
||||
onHide: function() {
|
||||
formatAppLog("log", "at App.vue:10", "App Hide");
|
||||
}
|
||||
};
|
||||
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "C:/Users/Administrator/Desktop/新建文件夹 (8)/测试流式传输uniapp/App.vue"]]);
|
||||
function createApp() {
|
||||
const app = vue.createVueApp(App);
|
||||
return {
|
||||
app
|
||||
};
|
||||
}
|
||||
const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp();
|
||||
uni.Vuex = __Vuex__;
|
||||
uni.Pinia = __Pinia__;
|
||||
__app__.provide("__globalStyles", __uniConfig.styles);
|
||||
__app__._component.mpType = "app";
|
||||
__app__._component.render = () => {
|
||||
};
|
||||
__app__.mount("#app");
|
||||
})(Vue);
|
||||
Reference in New Issue
Block a user