292 lines
8.0 KiB
JavaScript
292 lines
8.0 KiB
JavaScript
"use strict";
|
|
const common_vendor = require("../../common/vendor.js");
|
|
const MIN_HOLD_MS = 280;
|
|
function isBenignSttError(msg, retcode) {
|
|
const m = String(msg || "").toLowerCase();
|
|
if (/internal voice data failed/i.test(m))
|
|
return true;
|
|
if (/voice data failed/i.test(m))
|
|
return true;
|
|
if (/recordfailed|record failed|record manager/i.test(m))
|
|
return true;
|
|
if (/please stop after start/i.test(m))
|
|
return true;
|
|
if (/no recognition|not start|未开始|无识别/i.test(m))
|
|
return true;
|
|
const code = Number(retcode);
|
|
if (code === -30002 || code === -30003 || code === -30012)
|
|
return true;
|
|
return false;
|
|
}
|
|
function friendlySttError(msg) {
|
|
const m = String(msg || "").trim();
|
|
if (!m || isBenignSttError(m))
|
|
return "";
|
|
if (/not allowed|auth|permission|权限|麦克风/i.test(m))
|
|
return "请允许使用麦克风";
|
|
if (/network|网络|timeout/i.test(m))
|
|
return "网络异常,请稍后再试";
|
|
return "语音识别失败,请重试";
|
|
}
|
|
let sharedManager = null;
|
|
let sharedManagerInited = false;
|
|
let sharedRecording = false;
|
|
let activeCtl = null;
|
|
function getSharedRecordManager() {
|
|
if (sharedManagerInited)
|
|
return sharedManager;
|
|
sharedManagerInited = true;
|
|
try {
|
|
const plugin = requirePlugin("WechatSI");
|
|
sharedManager = plugin.getRecordRecognitionManager();
|
|
} catch (e) {
|
|
common_vendor.index.__f__("warn", "at tongji/composables/useSpeechToText.js:44", "WechatSI record recognition not available", e);
|
|
sharedManager = null;
|
|
return null;
|
|
}
|
|
if (!sharedManager)
|
|
return null;
|
|
sharedManager.onStart = () => {
|
|
sharedRecording = true;
|
|
if (activeCtl)
|
|
activeCtl.handleStart();
|
|
};
|
|
sharedManager.onRecognize = (res) => {
|
|
if (activeCtl)
|
|
activeCtl.handleRecognize(res);
|
|
};
|
|
sharedManager.onStop = (res) => {
|
|
sharedRecording = false;
|
|
if (activeCtl)
|
|
activeCtl.handleStop(res);
|
|
};
|
|
sharedManager.onError = (res) => {
|
|
sharedRecording = false;
|
|
if (activeCtl)
|
|
activeCtl.handleError(res);
|
|
};
|
|
return sharedManager;
|
|
}
|
|
function useSpeechToText({ onResult, onSettle, onPartial, onError, showToast } = {}) {
|
|
const sttListening = common_vendor.ref(false);
|
|
const sttHolding = common_vendor.ref(false);
|
|
const sttSupported = common_vendor.ref(false);
|
|
let wxRecordManager = null;
|
|
let holdStartTs = 0;
|
|
let holdSessionId = 0;
|
|
let startRequested = false;
|
|
let recordAuthorized = null;
|
|
let pendingStartSession = 0;
|
|
function notify(msg) {
|
|
if (!msg)
|
|
return;
|
|
if (typeof showToast === "function") {
|
|
showToast(msg);
|
|
return;
|
|
}
|
|
common_vendor.index.showToast({ title: msg, icon: "none", duration: 2e3 });
|
|
}
|
|
function emitResult(text) {
|
|
const t = String(text || "").trim();
|
|
const heldMs = Date.now() - holdStartTs;
|
|
if (typeof onSettle === "function") {
|
|
const handled = onSettle(t, { heldMs });
|
|
if (handled === true)
|
|
return;
|
|
}
|
|
if (!t) {
|
|
if (heldMs < MIN_HOLD_MS)
|
|
return;
|
|
notify("没听清,请再试一次");
|
|
return;
|
|
}
|
|
if (typeof onResult === "function") {
|
|
onResult(t);
|
|
}
|
|
}
|
|
function stopListening() {
|
|
const ownsRecording = sharedRecording && activeCtl === controller;
|
|
if (wxRecordManager && (sttListening.value || ownsRecording)) {
|
|
try {
|
|
wxRecordManager.stop();
|
|
} catch (e) {
|
|
}
|
|
}
|
|
startRequested = false;
|
|
sttHolding.value = false;
|
|
sttListening.value = false;
|
|
}
|
|
function ensureRecordAuth(onGranted) {
|
|
if (recordAuthorized === true) {
|
|
onGranted();
|
|
return;
|
|
}
|
|
common_vendor.index.authorize({
|
|
scope: "scope.record",
|
|
success() {
|
|
recordAuthorized = true;
|
|
onGranted();
|
|
},
|
|
fail() {
|
|
recordAuthorized = false;
|
|
sttHolding.value = false;
|
|
common_vendor.index.showModal({
|
|
title: "需要麦克风权限",
|
|
content: "请在设置中允许使用麦克风,以便长按说话",
|
|
confirmText: "去设置",
|
|
success(res) {
|
|
if (res.confirm) {
|
|
common_vendor.index.openSetting({});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function actuallyStartWechat(session) {
|
|
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
|
|
return;
|
|
}
|
|
try {
|
|
startRequested = true;
|
|
wxRecordManager.start({
|
|
duration: 6e4,
|
|
lang: "zh_CN"
|
|
});
|
|
} catch (e) {
|
|
startRequested = false;
|
|
sharedRecording = false;
|
|
sttHolding.value = false;
|
|
notify("无法开始录音");
|
|
}
|
|
}
|
|
function startWechatRecord(session) {
|
|
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
|
|
return;
|
|
}
|
|
if (sharedRecording || startRequested) {
|
|
pendingStartSession = session;
|
|
try {
|
|
wxRecordManager.stop();
|
|
} catch (e) {
|
|
}
|
|
return;
|
|
}
|
|
actuallyStartWechat(session);
|
|
}
|
|
function beginHoldSpeech() {
|
|
if (!sttSupported.value) {
|
|
notify("当前环境不支持语音输入");
|
|
return;
|
|
}
|
|
if (sttHolding.value)
|
|
return;
|
|
const session = ++holdSessionId;
|
|
holdStartTs = Date.now();
|
|
sttHolding.value = true;
|
|
activeCtl = controller;
|
|
ensureRecordAuth(() => startWechatRecord(session));
|
|
}
|
|
function endHoldSpeech(force = false) {
|
|
let recording = false;
|
|
recording = sharedRecording && activeCtl === controller;
|
|
if (!force && !sttHolding.value && !sttListening.value && !startRequested && !recording) {
|
|
return;
|
|
}
|
|
sttHolding.value = false;
|
|
pendingStartSession = 0;
|
|
if (sttListening.value || recording) {
|
|
stopListening();
|
|
return;
|
|
}
|
|
if (startRequested) {
|
|
startRequested = false;
|
|
holdSessionId += 1;
|
|
return;
|
|
}
|
|
holdSessionId += 1;
|
|
}
|
|
const controller = {
|
|
handleStart() {
|
|
if (!sttHolding.value) {
|
|
startRequested = false;
|
|
sttListening.value = false;
|
|
return;
|
|
}
|
|
startRequested = false;
|
|
sttListening.value = true;
|
|
},
|
|
handleRecognize(res) {
|
|
if (typeof onPartial !== "function")
|
|
return;
|
|
const t = String((res == null ? void 0 : res.result) || "").trim();
|
|
if (t)
|
|
onPartial(t);
|
|
},
|
|
handleStop(res) {
|
|
sttListening.value = false;
|
|
startRequested = false;
|
|
if (pendingStartSession && pendingStartSession === holdSessionId && sttHolding.value) {
|
|
const s = pendingStartSession;
|
|
pendingStartSession = 0;
|
|
actuallyStartWechat(s);
|
|
return;
|
|
}
|
|
pendingStartSession = 0;
|
|
emitResult(res == null ? void 0 : res.result);
|
|
},
|
|
handleError(res) {
|
|
sttListening.value = false;
|
|
startRequested = false;
|
|
const msg = res && res.msg || "";
|
|
const retcode = res && (res.retcode ?? res.errCode ?? res.code);
|
|
if (/please stop after start/i.test(msg)) {
|
|
try {
|
|
wxRecordManager.stop();
|
|
} catch (e) {
|
|
}
|
|
pendingStartSession = sttHolding.value ? holdSessionId : 0;
|
|
return;
|
|
}
|
|
pendingStartSession = 0;
|
|
sttHolding.value = false;
|
|
const heldMs = Date.now() - holdStartTs;
|
|
if (isBenignSttError(msg, retcode) || heldMs < MIN_HOLD_MS) {
|
|
if (typeof onError === "function")
|
|
onError(msg || "");
|
|
return;
|
|
}
|
|
if (typeof onError === "function") {
|
|
const handled = onError(msg || "");
|
|
if (handled === true)
|
|
return;
|
|
}
|
|
const tip = friendlySttError(msg);
|
|
if (tip)
|
|
notify(tip);
|
|
}
|
|
};
|
|
wxRecordManager = getSharedRecordManager();
|
|
sttSupported.value = !!wxRecordManager;
|
|
common_vendor.onUnmounted(() => {
|
|
sttHolding.value = false;
|
|
holdSessionId += 1;
|
|
pendingStartSession = 0;
|
|
stopListening();
|
|
if (activeCtl === controller) {
|
|
activeCtl = null;
|
|
}
|
|
});
|
|
return {
|
|
sttListening,
|
|
sttHolding,
|
|
sttSupported,
|
|
beginHoldSpeech,
|
|
endHoldSpeech,
|
|
stopSpeechToText: endHoldSpeech
|
|
};
|
|
}
|
|
exports.isBenignSttError = isBenignSttError;
|
|
exports.useSpeechToText = useSpeechToText;
|
|
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useSpeechToText.js.map
|