feat: 基本逻辑完成
This commit is contained in:
parent
f1aae7c270
commit
c67149f35e
|
|
@ -61,9 +61,9 @@
|
|||
let model = ['X', 'XR', 'XS', '11', '12', '13', '14', '15'];
|
||||
model.forEach(item => {
|
||||
//适配iphoneX以上的底部,给tabbar一定高度的padding-bottom
|
||||
if (res.model.indexOf(item) != -1 && res.model.indexOf('iPhone') != -1) {
|
||||
that.paddingBottomHeight = 40;
|
||||
}
|
||||
// if (res.model.indexOf(item) != -1 && res.model.indexOf('iPhone') != -1) {
|
||||
// that.paddingBottomHeight = 40;
|
||||
// }
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,808 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||||
<title>家庭管理语音AI页面</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 讯飞 -->
|
||||
<script src="./js/voice.js"></script>
|
||||
<script src="./js/crypto-js.min.js"></script>
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||||
<script src="js/w_md5.js"></script>
|
||||
|
||||
<!-- 构建界面 -->
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<!-- 加载遮罩 -->
|
||||
<div class="dialog" id="dialog" style="display: none;">
|
||||
<!-- <div class="overlay" id="overlay"></div> -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="ld"><em></em></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div class="video-wrap">
|
||||
<video id="myVideo" muted loop autoplay playsinline>
|
||||
<source src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<audio
|
||||
id="myAudio"
|
||||
autoplay
|
||||
controls
|
||||
style="width: 100% height: 10px">
|
||||
</audio>
|
||||
|
||||
<div id="AiButton">
|
||||
<!-- 录制 -->
|
||||
<img class="startRec" src="https://img.agrimedia.cn/bmsc/apps/start-tuya.png">
|
||||
<!-- 录制中 -->
|
||||
<img class="runRec" src="https://img.agrimedia.cn/bmsc/apps/runnig-tuya.png">
|
||||
<!-- 停止 -->
|
||||
<img class="endRec" src="https://img.agrimedia.cn/bmsc/apps/end-tuya.png">
|
||||
</div>
|
||||
|
||||
<!-- 讯飞测试 -->
|
||||
<div style="opacity: 1">
|
||||
<div class="voice-box">
|
||||
<input class="voice-input" type="search" name="voice" id="voice-txt" style="pointer-events: none"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂'];
|
||||
var Question = '';
|
||||
|
||||
function getURLParameter(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
// SEE事件解析
|
||||
function extractStopEvent(sseString) {
|
||||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||||
const events = sseString.split(/\r?\n\r?\n/);
|
||||
let stopEvent = null;
|
||||
|
||||
// 遍历每个事件
|
||||
events.forEach(event => {
|
||||
if (!event.trim()) {
|
||||
// 忽略空字符串
|
||||
return;
|
||||
}
|
||||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||||
let dataLines = [];
|
||||
let isDataLine = false;
|
||||
|
||||
// 分解当前事件为行
|
||||
const lines = event.split(/\r?\n/);
|
||||
|
||||
// 遍历每一行来找到 data 字段
|
||||
lines.forEach(line => {
|
||||
if (line.trim().startsWith('data:')) {
|
||||
isDataLine = true;
|
||||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||||
} else if (isDataLine && !line.trim()) {
|
||||
// 如果在 data 字段后遇到了空行,则停止收集
|
||||
isDataLine = false;
|
||||
} else if (isDataLine) {
|
||||
// 继续收集 data 字段的行(跨越多行的情况)
|
||||
dataLines.push(line.trim());
|
||||
}
|
||||
});
|
||||
|
||||
const dataJson = dataLines.join('');
|
||||
try {
|
||||
if (dataJson) {
|
||||
const eventData = JSON.parse(dataJson);
|
||||
if (eventData.output.finish_reason === "stop") {
|
||||
stopEvent = eventData;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||||
return stopEvent;
|
||||
}
|
||||
|
||||
// 筛选关键词
|
||||
function containsKeywordRegex(text) {
|
||||
var index = Items.findIndex(item => text.includes(item));
|
||||
|
||||
if (index !== -1) {
|
||||
return index
|
||||
} else {
|
||||
return 99999
|
||||
}
|
||||
}
|
||||
|
||||
// 语音所需时间
|
||||
function calculateSpeakingTime(text) {
|
||||
// 假设平均每分钟说230个单词
|
||||
const wordsPerMinute = 230;
|
||||
// 计算文字的长度
|
||||
const wordCount = text.trim().length;
|
||||
// 计算所需时间(以分钟为单位)
|
||||
const speakingTime = wordCount / wordsPerMinute;
|
||||
// 转换为秒数
|
||||
const speakingTimeInSeconds = speakingTime * 60;
|
||||
return speakingTimeInSeconds;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 讯飞语音识别 -->
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var demoData = {
|
||||
bloodGlucose: "血糖",
|
||||
SleepDatas: "睡眠",
|
||||
bloodOxygen: "血氧",
|
||||
bloodPressure: '血压',
|
||||
bloodLiquid: "血脂",
|
||||
meiTuo: '梅脱',
|
||||
pulseReat: '心率',
|
||||
bodyTemperature: '体温',
|
||||
ECGData: '心电图',
|
||||
bodyData: '身体成份',
|
||||
stepIndex: '运动'
|
||||
};
|
||||
|
||||
var videoElement = document.getElementById('myVideo');
|
||||
var startTime = 5; // 开始时间(以秒为单位)
|
||||
var endTime = 10; // 结束时间(以秒为单位)
|
||||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||||
|
||||
// 指定段落
|
||||
function playVideoSegment(startTime, endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
videoElement.play();
|
||||
|
||||
// 添加timeupdate事件监听器
|
||||
timeUpdateListener = function() {
|
||||
if (videoElement.currentTime >= endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
}
|
||||
};
|
||||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||||
}
|
||||
|
||||
// 重新播放
|
||||
function replayVideoSegment(startTime, endTime) {
|
||||
stopVideo();
|
||||
playVideoSegment(startTime, endTime);
|
||||
}
|
||||
|
||||
// 停止播放
|
||||
function stopVideo() {
|
||||
videoElement.pause();
|
||||
videoElement.currentTime = 0;
|
||||
|
||||
// 移除timeupdate事件监听器
|
||||
if (timeUpdateListener) {
|
||||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||||
timeUpdateListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前AI视频循环
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
// 获取 audio 元素的引用
|
||||
var audioElement = document.getElementById('myAudio');
|
||||
audioElement.muted = true; // 先静音
|
||||
|
||||
// 获取页面元素
|
||||
var element = document.getElementById("elementId");
|
||||
|
||||
// 获取遮罩和弹窗元素
|
||||
var dialog = document.getElementById('dialog');
|
||||
|
||||
// 点击事件
|
||||
var startRec = document.getElementsByClassName('startRec')[0];
|
||||
var runRec = document.getElementsByClassName('runRec')[0];
|
||||
var endRec = document.getElementsByClassName('endRec')[0];
|
||||
|
||||
var token = null;
|
||||
let times = null;
|
||||
|
||||
// 获取微软token
|
||||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||||
},
|
||||
}).then(async(response) => {
|
||||
token = await response.text();
|
||||
}).catch(e => {});
|
||||
|
||||
|
||||
// 文本输入框
|
||||
const voiceTxt = document.querySelector('#voice-txt');
|
||||
|
||||
// 防止多次请求
|
||||
var isCallbackExecuted = false;
|
||||
|
||||
|
||||
/*
|
||||
* 给予数据文字标识
|
||||
*/
|
||||
let exampleData = JSON.parse(getURLParameter('data'));
|
||||
for (let i = 0; i < exampleData.length; i++) {
|
||||
exampleData[i].name = demoData[exampleData[i].type]
|
||||
}
|
||||
|
||||
/*
|
||||
* 实例化迅飞语音听写(流式版)WebAPI
|
||||
*/
|
||||
const voice = new Voice({
|
||||
// 服务接口认证信息
|
||||
appId: '5f4ffdeb',
|
||||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||||
|
||||
onTextChange: function (text) {
|
||||
// 监听识别结果的变化
|
||||
console.log(text, '监听')
|
||||
voiceTxt.value = text;
|
||||
|
||||
// 3秒钟内没有说话,就自动关闭
|
||||
if (text) {
|
||||
clearTimeout(times);
|
||||
if (!isCallbackExecuted) {
|
||||
times = setTimeout(() => {
|
||||
this.stop();
|
||||
// voice.stop();
|
||||
|
||||
const params = { msg: text }
|
||||
|
||||
alert(params.msg)
|
||||
|
||||
/*
|
||||
* 拿到匹配的文字下标
|
||||
*/
|
||||
let QSindex = containsKeywordRegex(params.msg);
|
||||
console.log(QSindex)
|
||||
if (QSindex == 0) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodGlucose");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血糖为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 1) {
|
||||
const obj = exampleData.filter(item => item.type == "SleepDatas");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近睡眠时长为${obj[0].data_msg[0].sleepTotalTime}分钟`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 2) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodOxygen");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血氧为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 3) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodPressure");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血压为${obj[0].data_msg.bloodPressureLow}/${obj[0].data_msg.bloodPressureHigh}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 4 || QSindex == 11) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodLiquid");
|
||||
if (obj[0].data_msg.cholesterol) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血脂状况为,
|
||||
尿酸为${obj[0].data_msg.uricAcidVal/10},
|
||||
总胆固醇为${obj[0].data_msg.cholesterol/100},
|
||||
甘油三酯为${obj[0].data_msg.cholesterol/100},
|
||||
高密度脂蛋白为${obj[0].data_msg.cholesterol/100},
|
||||
低密度脂蛋白为${obj[0].data_msg.cholesterol/100}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 6) {
|
||||
const obj = exampleData.filter(item => item.type == "pulseReat");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的心率为${obj[0].data_msg[0]}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 7) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyTemperature");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的体温为${obj[0].data_msg}摄氏度`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 8) {
|
||||
const obj = exampleData.filter(item => item.type == "ECGData");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近心电图测量结果为${obj[0].data_msg.heartRate}次/分`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 9) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyData");
|
||||
if (obj[0].data_msg.BMI) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近身体成分结果为${obj[0].data_msg.BMI}`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 10) {
|
||||
const obj = exampleData.filter(item => item.type == "stepIndex");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的运动为${obj[0].data_msg.step}步数,
|
||||
${obj[0].data_msg.calorie/10}千卡,
|
||||
${obj[0].data_msg.distance/1000}公里`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 99999) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": params.msg},
|
||||
{"role": "user", "content": params.msg}
|
||||
]
|
||||
})
|
||||
|
||||
console.log(str.output.text, '返回的答案')
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
} else {
|
||||
/*
|
||||
* 调用接口 传递关键信息 文字转语音
|
||||
*/
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
|
||||
/*
|
||||
* 关键字转换
|
||||
*/
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": Question},
|
||||
{"role": "user", "content": `请问我${Items[QSindex]}正常吗`}
|
||||
]
|
||||
})
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 开始识别
|
||||
startRec.addEventListener("click", function() {
|
||||
/**开始识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.start();
|
||||
isCallbackExecuted = false;
|
||||
|
||||
// 先静音即可处理解决(提前做交互)
|
||||
audioElement.muted = false;
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
|
||||
startRec.style.display = 'none';
|
||||
runRec.style.display = 'block';
|
||||
// endRec.style.opacity = 0;
|
||||
showModal()
|
||||
});
|
||||
|
||||
|
||||
// 关闭识别
|
||||
endRec.addEventListener("click", function() {
|
||||
/**关闭识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.stop();
|
||||
// 音频
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
// 视频
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
isCallbackExecuted = false;
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
hideModal()
|
||||
});
|
||||
|
||||
|
||||
// 显示弹窗和遮罩
|
||||
function showModal() {
|
||||
// overlay.style.display = 'block';
|
||||
modal.style.display = 'block';
|
||||
dialog.style.display = 'block';
|
||||
}
|
||||
|
||||
// 隐藏弹窗和遮罩
|
||||
function hideModal() {
|
||||
// overlay.style.display = 'none';
|
||||
modal.style.display = 'none';
|
||||
dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/ai-persion-bg-tuya.jpeg');
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.video-wrap {
|
||||
width: 80%;
|
||||
top: 10%;
|
||||
height: auto;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/video-bg.png');
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myVideo {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myAudio {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#AiButton {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
#AiButton > img {
|
||||
width: 120px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* 遮罩样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||||
display: none; /* 初始隐藏 */
|
||||
z-index: 9999; /* 设置为最高层级 */
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ld {
|
||||
background: #000;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||||
|
||||
--dis: 20px; /*圆尺寸和偏移量*/
|
||||
--dur: 2s; /*动画时长*/
|
||||
}
|
||||
|
||||
.ld em {
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ld::before {
|
||||
background-color: #38597A;
|
||||
}
|
||||
|
||||
.ld::after {
|
||||
background-color: #3FEBFF;
|
||||
}
|
||||
|
||||
.ld em::before {
|
||||
background-color: #87B2DD;
|
||||
}
|
||||
|
||||
.ld em::after {
|
||||
background-color: #58D5FF;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld::after,
|
||||
.ld em::before,
|
||||
.ld em::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--dis);
|
||||
height: var(--dis);
|
||||
border-radius: var(--dis);
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation-duration: var(--dur);
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
--dir: 1;
|
||||
--tr: calc(var(--dir) * var(--dis));
|
||||
--ttr: calc(-1 * var(--tr));
|
||||
--tx: translateX(var(--tr));
|
||||
--ttx: translateX(var(--ttr));
|
||||
--ty: translateY(var(--tr));
|
||||
--tty: translateY(var(--ttr));
|
||||
}
|
||||
.ld::before,
|
||||
.ld::after {
|
||||
--dir: -1;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld em::before {
|
||||
transform: var(--ttx);
|
||||
animation-name: tx;
|
||||
}
|
||||
.ld::after,
|
||||
.ld em::after {
|
||||
transform: var(--tty);
|
||||
animation-name: ty;
|
||||
}
|
||||
|
||||
@keyframes tx {
|
||||
50% {
|
||||
transform: var(--tx);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ty {
|
||||
50% {
|
||||
transform: var(--ty);
|
||||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
width: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.rule::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.runRec {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.voice-input {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: none;
|
||||
line-height: 50px;
|
||||
border: 0;/*清除自带的2px的边框*/
|
||||
padding: 0;/*清除自带的padding间距*/
|
||||
outline: none;/*清除input点击之后的黑色边框*/
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,46 @@
|
|||
;
|
||||
(function() {
|
||||
let self = this
|
||||
self.onmessage = function(e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
; (function (window, voice) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(voice);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = voice();
|
||||
} else {
|
||||
window.Voice = voice();
|
||||
};
|
||||
}(typeof window !== "undefined" ? window : this, () => {
|
||||
"use strict";
|
||||
return class IatRecorder {
|
||||
constructor(opts = {}) {
|
||||
// 服务接口认证信息(语音听写(流式版)WebAPI)
|
||||
this.appId = opts.appId || '';
|
||||
this.apiKey = opts.apiKey || '';
|
||||
this.apiSecret = opts.apiSecret || '';
|
||||
// 识别监听方法
|
||||
this.onTextChange = opts.onTextChange || Function();
|
||||
this.onWillStatusChange = opts.onWillStatusChange || Function();
|
||||
// 方言/语种
|
||||
this.status = 'null'
|
||||
this.language = opts.language || 'zh_cn'
|
||||
this.accent = opts.accent || 'mandarin';
|
||||
// 流媒体
|
||||
this.streamRef = [];
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = '';
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = '';
|
||||
// 音频数据多线程
|
||||
this.init();
|
||||
};
|
||||
// WebSocket请求地址鉴权
|
||||
getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
try {
|
||||
const CryptoJS = require('crypto-js');
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
} catch (error) {
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
// 操作初始化
|
||||
init() {
|
||||
const self = this;
|
||||
try {
|
||||
if (!self.appId || !self.apiKey || !self.apiSecret) {
|
||||
alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!');
|
||||
} else {
|
||||
self.webWorker = new Worker('./js/transcode.worker.js');
|
||||
self.webWorker.onmessage = function (event) {
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
alert('对不起:请在服务器环境下运行!');
|
||||
console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error);
|
||||
};
|
||||
// console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;");
|
||||
};
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status);
|
||||
this.status = status;
|
||||
};
|
||||
// 设置识别结果内容
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '');
|
||||
resultText !== undefined && (this.resultText = resultText);
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp);
|
||||
};
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
};
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
};
|
||||
// 连接WebSocket
|
||||
connectWebSocket() {
|
||||
return this.getWebSocketUrl().then(url => {
|
||||
let iatWS;
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket!');
|
||||
return false;
|
||||
}
|
||||
this.webSocket = iatWS;
|
||||
this.setStatus('init');
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing');
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend();
|
||||
}, 500);
|
||||
};
|
||||
iatWS.onmessage = e => {
|
||||
this.webSocketRes(e.data);
|
||||
};
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
iatWS.onclose = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
})
|
||||
};
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.audioContext.resume();
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 获取浏览器录音权限成功时回调
|
||||
let getMediaSuccess = _ => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1);
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
if (this.status === 'ing') {
|
||||
// 多线程音频数据处理
|
||||
try {
|
||||
this.webWorker.postMessage(e.inputBuffer.getChannelData(0));
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef);
|
||||
this.mediaSource.connect(this.scriptProcessor);
|
||||
this.scriptProcessor.connect(this.audioContext.destination);
|
||||
this.connectWebSocket();
|
||||
};
|
||||
// 获取浏览器录音权限失败时回调
|
||||
let getMediaFail = (e) => {
|
||||
alert('对不起:录音权限获取失败!');
|
||||
this.audioContext && this.audioContext.close();
|
||||
this.audioContext = undefined;
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true
|
||||
}).then(stream => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}).catch(e => {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: true
|
||||
}, (stream) => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}, function (e) {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!');
|
||||
} else {
|
||||
alert('对不起:未识别到录音设备!');
|
||||
}
|
||||
this.audioContext && this.audioContext.close();
|
||||
return false;
|
||||
};
|
||||
};
|
||||
// 向webSocket发送数据(音频二进制数据经过Base64处理)
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) return false;
|
||||
// 音频数据
|
||||
const audioData = this.audioData.splice(0, 1280);
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData)
|
||||
}
|
||||
};
|
||||
// 发送数据
|
||||
this.webSocket.send(JSON.stringify(params));
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
return false;
|
||||
};
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: ''
|
||||
}
|
||||
})
|
||||
);
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(this.audioData.splice(0, 1280))
|
||||
}
|
||||
})
|
||||
);
|
||||
}, 40);
|
||||
};
|
||||
// 识别结束 webSocket返回数据
|
||||
webSocketRes(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(JSON.stringify(jsonData), 'websocket')
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result;
|
||||
let str = '';
|
||||
let ws = data.ws;
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w;
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp
|
||||
});
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str
|
||||
});
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
// 启动录音
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit();
|
||||
} else {
|
||||
this.audioContext.resume();
|
||||
this.connectWebSocket();
|
||||
}
|
||||
};
|
||||
// 停止录音
|
||||
recorderStop() {
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
this.audioContext && this.audioContext.suspend();
|
||||
}
|
||||
this.setStatus('end');
|
||||
try {
|
||||
// this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
|
||||
} catch (error) {
|
||||
console.error('暂停失败!');
|
||||
}
|
||||
};
|
||||
// 开始
|
||||
start() {
|
||||
this.recorderStart();
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' });
|
||||
};
|
||||
// 停止
|
||||
stop() {
|
||||
this.recorderStop();
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
let w_md5 = {}
|
||||
function hex_md5(string,bit) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
function md5_F(x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
}
|
||||
function md5_G(x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
}
|
||||
function md5_H(x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
function md5_I(x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
}
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
};
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
};
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
if(bit==32){
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
||||
return (md5_WordToHex(b) + md5_WordToHex(c)).toLowerCase();
|
||||
}
|
||||
//16位小写
|
||||
w_md5.hex_md5_16 = function (string) {
|
||||
return hex_md5(string,16);
|
||||
}
|
||||
//16位大写
|
||||
w_md5.hex_md5_16Upper = function (string) {
|
||||
return hex_md5(string,16).toUpperCase();
|
||||
}
|
||||
//32位小写
|
||||
w_md5.hex_md5_32 = function (string) {
|
||||
return hex_md5(string,32);
|
||||
}
|
||||
//32位大写
|
||||
w_md5.hex_md5_32Upper = function (string) {
|
||||
return hex_md5(string,32).toUpperCase();
|
||||
}
|
||||
|
|
@ -40,7 +40,8 @@
|
|||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"
|
||||
]
|
||||
},
|
||||
// "orientation": "portrait",
|
||||
|
|
@ -109,6 +110,11 @@
|
|||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "2",
|
||||
"permissions" : {
|
||||
"android.permission.RECORD_AUDIO" : {
|
||||
"desc" : "请授权使用录音功能"
|
||||
}
|
||||
},
|
||||
"h5" : {
|
||||
"async" : {
|
||||
"delay" : "10000" // 这个时间内页面js已经加载了,所以不展示默认的loading
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@
|
|||
<text class="icon">{{(i == 0?'时':'分')}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="bg">
|
||||
|
|
@ -318,13 +317,7 @@
|
|||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<video :enable-progress-gesture="false"
|
||||
object-fit="cover"
|
||||
style="width: 100%; height: 100%;"
|
||||
:controls="false"
|
||||
:show-center-play-btn="false"
|
||||
src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4">
|
||||
</video>
|
||||
<web-view :src="urlLink"></web-view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 200rpx"></view>
|
||||
|
|
@ -367,43 +360,53 @@
|
|||
bodyData:{},
|
||||
SleepDatas:{
|
||||
}
|
||||
}
|
||||
// dataListsNew:{
|
||||
// stepIndex:{
|
||||
// calorie:'--',
|
||||
// distance:'--',
|
||||
// step:'--',
|
||||
// },
|
||||
// pulseReat:[],
|
||||
// bloodOxygen:[],
|
||||
// bloodPressure:{
|
||||
// bloodPressureHigh:'',
|
||||
// bloodPressureLow:''
|
||||
// },
|
||||
// bloodGlucose:'',
|
||||
// meiTuo:[],
|
||||
// bodyTemperature:'',
|
||||
// bloodLiquid:{
|
||||
// cholesterol:0,
|
||||
// highDensity:0,
|
||||
// lowDensity:0,
|
||||
// triacylglycerol:0,
|
||||
// uricAcidVal:0,
|
||||
// }
|
||||
// }
|
||||
},
|
||||
|
||||
urlLink: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
/*
|
||||
* 首页数据
|
||||
*/
|
||||
this.onRefresh();
|
||||
// #ifdef APP-PLUS
|
||||
setTimeout(() => {
|
||||
var avaudiosession = plus.ios.import("AVAudioSession");
|
||||
var avaudio = avaudiosession.sharedInstance();
|
||||
avaudio.requestRecordPermission(()=>{
|
||||
console.log('申请麦克风权限');
|
||||
});
|
||||
}, 1000);
|
||||
// #endif
|
||||
|
||||
this.urlLink = `/hybrid/html/ai.html`;
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getActiceDevice: "api/getActiceDevice"
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
|
||||
onReady() {
|
||||
// #ifdef APP-PLUS
|
||||
let pages = getCurrentPages();
|
||||
let page = pages[pages.length - 1];
|
||||
let currentWebview = page.$getAppWebview();
|
||||
setTimeout(function() {
|
||||
const wv = currentWebview.children()[0]
|
||||
//setStyle设置webview的宽高的位置
|
||||
wv.setStyle({
|
||||
top: 0,
|
||||
right: 0,
|
||||
height: uni.getSystemInfoSync().windowHeight,
|
||||
width: uni.getSystemInfoSync().windowWidth - 660
|
||||
});
|
||||
}, 100);
|
||||
// #endif
|
||||
},
|
||||
|
||||
methods: {
|
||||
getArrMaxValue,
|
||||
onRefresh(pageNo, pageSize) {
|
||||
this.$store.dispatch('api/getIndexData', {
|
||||
|
|
@ -423,13 +426,14 @@
|
|||
],
|
||||
}).then(res => {
|
||||
for(let i = 0;i<res.length;i++){
|
||||
// console.log(this.dataListsNew[res[i].type])
|
||||
// console.log(res[i].data_msg)
|
||||
this.dataListsNew[res[i].type] = res[i]
|
||||
this.dataListsNew[res[i].name] = this.getNameByKey(res[i].type)
|
||||
}
|
||||
// this.dataListsNew = res.data_msg;
|
||||
// console.log(this.dataListsNew, '222222222')
|
||||
this.urlLink = `/hybrid/html/ai.html?data=${JSON.stringify(res)}`;
|
||||
this.$refs.paging.complete();
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
mkHourMin(min){
|
||||
if(min < 60){
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<title>
|
||||
<%= htmlWebpackPlugin.options.title %>
|
||||
</title>
|
||||
<link rel="icon" type="image/x-icon" href="https://img.agrimedia.cn/bmsc/static/ico/20240116-103757.ico">
|
||||
<script src="https://cstaticdun.126.net/load.min.js?t=201903281201"></script>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>Please enable JavaScript to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
https://app.liuyingyong.cn/build/download/617bf480-7592-11ef-a054-7dafa15c3dbd
|
||||
https://app.liuyingyong.cn/build/download/fcde2160-77e5-11ef-88bd-653596bd7016
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,808 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||||
<title>家庭管理语音AI页面</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 讯飞 -->
|
||||
<script src="./js/voice.js"></script>
|
||||
<script src="./js/crypto-js.min.js"></script>
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||||
<script src="js/w_md5.js"></script>
|
||||
|
||||
<!-- 构建界面 -->
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<!-- 加载遮罩 -->
|
||||
<div class="dialog" id="dialog" style="display: none;">
|
||||
<!-- <div class="overlay" id="overlay"></div> -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="ld"><em></em></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div class="video-wrap">
|
||||
<video id="myVideo" muted loop autoplay playsinline>
|
||||
<source src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<audio
|
||||
id="myAudio"
|
||||
autoplay
|
||||
controls
|
||||
style="width: 100% height: 10px">
|
||||
</audio>
|
||||
|
||||
<div id="AiButton">
|
||||
<!-- 录制 -->
|
||||
<img class="startRec" src="https://img.agrimedia.cn/bmsc/apps/start-tuya.png">
|
||||
<!-- 录制中 -->
|
||||
<img class="runRec" src="https://img.agrimedia.cn/bmsc/apps/runnig-tuya.png">
|
||||
<!-- 停止 -->
|
||||
<img class="endRec" src="https://img.agrimedia.cn/bmsc/apps/end-tuya.png">
|
||||
</div>
|
||||
|
||||
<!-- 讯飞测试 -->
|
||||
<div style="opacity: 1">
|
||||
<div class="voice-box">
|
||||
<input class="voice-input" type="search" name="voice" id="voice-txt" style="pointer-events: none"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂'];
|
||||
var Question = '';
|
||||
|
||||
function getURLParameter(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
// SEE事件解析
|
||||
function extractStopEvent(sseString) {
|
||||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||||
const events = sseString.split(/\r?\n\r?\n/);
|
||||
let stopEvent = null;
|
||||
|
||||
// 遍历每个事件
|
||||
events.forEach(event => {
|
||||
if (!event.trim()) {
|
||||
// 忽略空字符串
|
||||
return;
|
||||
}
|
||||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||||
let dataLines = [];
|
||||
let isDataLine = false;
|
||||
|
||||
// 分解当前事件为行
|
||||
const lines = event.split(/\r?\n/);
|
||||
|
||||
// 遍历每一行来找到 data 字段
|
||||
lines.forEach(line => {
|
||||
if (line.trim().startsWith('data:')) {
|
||||
isDataLine = true;
|
||||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||||
} else if (isDataLine && !line.trim()) {
|
||||
// 如果在 data 字段后遇到了空行,则停止收集
|
||||
isDataLine = false;
|
||||
} else if (isDataLine) {
|
||||
// 继续收集 data 字段的行(跨越多行的情况)
|
||||
dataLines.push(line.trim());
|
||||
}
|
||||
});
|
||||
|
||||
const dataJson = dataLines.join('');
|
||||
try {
|
||||
if (dataJson) {
|
||||
const eventData = JSON.parse(dataJson);
|
||||
if (eventData.output.finish_reason === "stop") {
|
||||
stopEvent = eventData;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||||
return stopEvent;
|
||||
}
|
||||
|
||||
// 筛选关键词
|
||||
function containsKeywordRegex(text) {
|
||||
var index = Items.findIndex(item => text.includes(item));
|
||||
|
||||
if (index !== -1) {
|
||||
return index
|
||||
} else {
|
||||
return 99999
|
||||
}
|
||||
}
|
||||
|
||||
// 语音所需时间
|
||||
function calculateSpeakingTime(text) {
|
||||
// 假设平均每分钟说230个单词
|
||||
const wordsPerMinute = 230;
|
||||
// 计算文字的长度
|
||||
const wordCount = text.trim().length;
|
||||
// 计算所需时间(以分钟为单位)
|
||||
const speakingTime = wordCount / wordsPerMinute;
|
||||
// 转换为秒数
|
||||
const speakingTimeInSeconds = speakingTime * 60;
|
||||
return speakingTimeInSeconds;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 讯飞语音识别 -->
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var demoData = {
|
||||
bloodGlucose: "血糖",
|
||||
SleepDatas: "睡眠",
|
||||
bloodOxygen: "血氧",
|
||||
bloodPressure: '血压',
|
||||
bloodLiquid: "血脂",
|
||||
meiTuo: '梅脱',
|
||||
pulseReat: '心率',
|
||||
bodyTemperature: '体温',
|
||||
ECGData: '心电图',
|
||||
bodyData: '身体成份',
|
||||
stepIndex: '运动'
|
||||
};
|
||||
|
||||
var videoElement = document.getElementById('myVideo');
|
||||
var startTime = 5; // 开始时间(以秒为单位)
|
||||
var endTime = 10; // 结束时间(以秒为单位)
|
||||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||||
|
||||
// 指定段落
|
||||
function playVideoSegment(startTime, endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
videoElement.play();
|
||||
|
||||
// 添加timeupdate事件监听器
|
||||
timeUpdateListener = function() {
|
||||
if (videoElement.currentTime >= endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
}
|
||||
};
|
||||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||||
}
|
||||
|
||||
// 重新播放
|
||||
function replayVideoSegment(startTime, endTime) {
|
||||
stopVideo();
|
||||
playVideoSegment(startTime, endTime);
|
||||
}
|
||||
|
||||
// 停止播放
|
||||
function stopVideo() {
|
||||
videoElement.pause();
|
||||
videoElement.currentTime = 0;
|
||||
|
||||
// 移除timeupdate事件监听器
|
||||
if (timeUpdateListener) {
|
||||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||||
timeUpdateListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前AI视频循环
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
// 获取 audio 元素的引用
|
||||
var audioElement = document.getElementById('myAudio');
|
||||
audioElement.muted = true; // 先静音
|
||||
|
||||
// 获取页面元素
|
||||
var element = document.getElementById("elementId");
|
||||
|
||||
// 获取遮罩和弹窗元素
|
||||
var dialog = document.getElementById('dialog');
|
||||
|
||||
// 点击事件
|
||||
var startRec = document.getElementsByClassName('startRec')[0];
|
||||
var runRec = document.getElementsByClassName('runRec')[0];
|
||||
var endRec = document.getElementsByClassName('endRec')[0];
|
||||
|
||||
var token = null;
|
||||
let times = null;
|
||||
|
||||
// 获取微软token
|
||||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||||
},
|
||||
}).then(async(response) => {
|
||||
token = await response.text();
|
||||
}).catch(e => {});
|
||||
|
||||
|
||||
// 文本输入框
|
||||
const voiceTxt = document.querySelector('#voice-txt');
|
||||
|
||||
// 防止多次请求
|
||||
var isCallbackExecuted = false;
|
||||
|
||||
|
||||
/*
|
||||
* 给予数据文字标识
|
||||
*/
|
||||
let exampleData = JSON.parse(getURLParameter('data'));
|
||||
for (let i = 0; i < exampleData.length; i++) {
|
||||
exampleData[i].name = demoData[exampleData[i].type]
|
||||
}
|
||||
|
||||
/*
|
||||
* 实例化迅飞语音听写(流式版)WebAPI
|
||||
*/
|
||||
const voice = new Voice({
|
||||
// 服务接口认证信息
|
||||
appId: '5f4ffdeb',
|
||||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||||
|
||||
onTextChange: function (text) {
|
||||
// 监听识别结果的变化
|
||||
console.log(text, '监听')
|
||||
voiceTxt.value = text;
|
||||
|
||||
// 3秒钟内没有说话,就自动关闭
|
||||
if (text) {
|
||||
clearTimeout(times);
|
||||
if (!isCallbackExecuted) {
|
||||
times = setTimeout(() => {
|
||||
this.stop();
|
||||
// voice.stop();
|
||||
|
||||
const params = { msg: text }
|
||||
|
||||
alert(params.msg)
|
||||
|
||||
/*
|
||||
* 拿到匹配的文字下标
|
||||
*/
|
||||
let QSindex = containsKeywordRegex(params.msg);
|
||||
console.log(QSindex)
|
||||
if (QSindex == 0) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodGlucose");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血糖为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 1) {
|
||||
const obj = exampleData.filter(item => item.type == "SleepDatas");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近睡眠时长为${obj[0].data_msg[0].sleepTotalTime}分钟`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 2) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodOxygen");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血氧为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 3) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodPressure");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血压为${obj[0].data_msg.bloodPressureLow}/${obj[0].data_msg.bloodPressureHigh}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 4 || QSindex == 11) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodLiquid");
|
||||
if (obj[0].data_msg.cholesterol) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血脂状况为,
|
||||
尿酸为${obj[0].data_msg.uricAcidVal/10},
|
||||
总胆固醇为${obj[0].data_msg.cholesterol/100},
|
||||
甘油三酯为${obj[0].data_msg.cholesterol/100},
|
||||
高密度脂蛋白为${obj[0].data_msg.cholesterol/100},
|
||||
低密度脂蛋白为${obj[0].data_msg.cholesterol/100}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 6) {
|
||||
const obj = exampleData.filter(item => item.type == "pulseReat");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的心率为${obj[0].data_msg[0]}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 7) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyTemperature");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的体温为${obj[0].data_msg}摄氏度`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 8) {
|
||||
const obj = exampleData.filter(item => item.type == "ECGData");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近心电图测量结果为${obj[0].data_msg.heartRate}次/分`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 9) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyData");
|
||||
if (obj[0].data_msg.BMI) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近身体成分结果为${obj[0].data_msg.BMI}`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 10) {
|
||||
const obj = exampleData.filter(item => item.type == "stepIndex");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的运动为${obj[0].data_msg.step}步数,
|
||||
${obj[0].data_msg.calorie/10}千卡,
|
||||
${obj[0].data_msg.distance/1000}公里`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 99999) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": params.msg},
|
||||
{"role": "user", "content": params.msg}
|
||||
]
|
||||
})
|
||||
|
||||
console.log(str.output.text, '返回的答案')
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
} else {
|
||||
/*
|
||||
* 调用接口 传递关键信息 文字转语音
|
||||
*/
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
|
||||
/*
|
||||
* 关键字转换
|
||||
*/
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": Question},
|
||||
{"role": "user", "content": `请问我${Items[QSindex]}正常吗`}
|
||||
]
|
||||
})
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 开始识别
|
||||
startRec.addEventListener("click", function() {
|
||||
/**开始识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.start();
|
||||
isCallbackExecuted = false;
|
||||
|
||||
// 先静音即可处理解决(提前做交互)
|
||||
audioElement.muted = false;
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
|
||||
startRec.style.display = 'none';
|
||||
runRec.style.display = 'block';
|
||||
// endRec.style.opacity = 0;
|
||||
showModal()
|
||||
});
|
||||
|
||||
|
||||
// 关闭识别
|
||||
endRec.addEventListener("click", function() {
|
||||
/**关闭识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.stop();
|
||||
// 音频
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
// 视频
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
isCallbackExecuted = false;
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
hideModal()
|
||||
});
|
||||
|
||||
|
||||
// 显示弹窗和遮罩
|
||||
function showModal() {
|
||||
// overlay.style.display = 'block';
|
||||
modal.style.display = 'block';
|
||||
dialog.style.display = 'block';
|
||||
}
|
||||
|
||||
// 隐藏弹窗和遮罩
|
||||
function hideModal() {
|
||||
// overlay.style.display = 'none';
|
||||
modal.style.display = 'none';
|
||||
dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/ai-persion-bg-tuya.jpeg');
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.video-wrap {
|
||||
width: 80%;
|
||||
top: 10%;
|
||||
height: auto;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/video-bg.png');
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myVideo {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myAudio {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#AiButton {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
#AiButton > img {
|
||||
width: 120px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* 遮罩样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||||
display: none; /* 初始隐藏 */
|
||||
z-index: 9999; /* 设置为最高层级 */
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ld {
|
||||
background: #000;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||||
|
||||
--dis: 20px; /*圆尺寸和偏移量*/
|
||||
--dur: 2s; /*动画时长*/
|
||||
}
|
||||
|
||||
.ld em {
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ld::before {
|
||||
background-color: #38597A;
|
||||
}
|
||||
|
||||
.ld::after {
|
||||
background-color: #3FEBFF;
|
||||
}
|
||||
|
||||
.ld em::before {
|
||||
background-color: #87B2DD;
|
||||
}
|
||||
|
||||
.ld em::after {
|
||||
background-color: #58D5FF;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld::after,
|
||||
.ld em::before,
|
||||
.ld em::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--dis);
|
||||
height: var(--dis);
|
||||
border-radius: var(--dis);
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation-duration: var(--dur);
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
--dir: 1;
|
||||
--tr: calc(var(--dir) * var(--dis));
|
||||
--ttr: calc(-1 * var(--tr));
|
||||
--tx: translateX(var(--tr));
|
||||
--ttx: translateX(var(--ttr));
|
||||
--ty: translateY(var(--tr));
|
||||
--tty: translateY(var(--ttr));
|
||||
}
|
||||
.ld::before,
|
||||
.ld::after {
|
||||
--dir: -1;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld em::before {
|
||||
transform: var(--ttx);
|
||||
animation-name: tx;
|
||||
}
|
||||
.ld::after,
|
||||
.ld em::after {
|
||||
transform: var(--tty);
|
||||
animation-name: ty;
|
||||
}
|
||||
|
||||
@keyframes tx {
|
||||
50% {
|
||||
transform: var(--tx);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ty {
|
||||
50% {
|
||||
transform: var(--ty);
|
||||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
width: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.rule::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.runRec {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.voice-input {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: none;
|
||||
line-height: 50px;
|
||||
border: 0;/*清除自带的2px的边框*/
|
||||
padding: 0;/*清除自带的padding间距*/
|
||||
outline: none;/*清除input点击之后的黑色边框*/
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,46 @@
|
|||
;
|
||||
(function() {
|
||||
let self = this
|
||||
self.onmessage = function(e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
; (function (window, voice) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(voice);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = voice();
|
||||
} else {
|
||||
window.Voice = voice();
|
||||
};
|
||||
}(typeof window !== "undefined" ? window : this, () => {
|
||||
"use strict";
|
||||
return class IatRecorder {
|
||||
constructor(opts = {}) {
|
||||
// 服务接口认证信息(语音听写(流式版)WebAPI)
|
||||
this.appId = opts.appId || '';
|
||||
this.apiKey = opts.apiKey || '';
|
||||
this.apiSecret = opts.apiSecret || '';
|
||||
// 识别监听方法
|
||||
this.onTextChange = opts.onTextChange || Function();
|
||||
this.onWillStatusChange = opts.onWillStatusChange || Function();
|
||||
// 方言/语种
|
||||
this.status = 'null'
|
||||
this.language = opts.language || 'zh_cn'
|
||||
this.accent = opts.accent || 'mandarin';
|
||||
// 流媒体
|
||||
this.streamRef = [];
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = '';
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = '';
|
||||
// 音频数据多线程
|
||||
this.init();
|
||||
};
|
||||
// WebSocket请求地址鉴权
|
||||
getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
try {
|
||||
const CryptoJS = require('crypto-js');
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
} catch (error) {
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
// 操作初始化
|
||||
init() {
|
||||
const self = this;
|
||||
try {
|
||||
if (!self.appId || !self.apiKey || !self.apiSecret) {
|
||||
alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!');
|
||||
} else {
|
||||
self.webWorker = new Worker('./js/transcode.worker.js');
|
||||
self.webWorker.onmessage = function (event) {
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
alert('对不起:请在服务器环境下运行!');
|
||||
console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error);
|
||||
};
|
||||
// console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;");
|
||||
};
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status);
|
||||
this.status = status;
|
||||
};
|
||||
// 设置识别结果内容
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '');
|
||||
resultText !== undefined && (this.resultText = resultText);
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp);
|
||||
};
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
};
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
};
|
||||
// 连接WebSocket
|
||||
connectWebSocket() {
|
||||
return this.getWebSocketUrl().then(url => {
|
||||
let iatWS;
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket!');
|
||||
return false;
|
||||
}
|
||||
this.webSocket = iatWS;
|
||||
this.setStatus('init');
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing');
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend();
|
||||
}, 500);
|
||||
};
|
||||
iatWS.onmessage = e => {
|
||||
this.webSocketRes(e.data);
|
||||
};
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
iatWS.onclose = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
})
|
||||
};
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.audioContext.resume();
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 获取浏览器录音权限成功时回调
|
||||
let getMediaSuccess = _ => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1);
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
if (this.status === 'ing') {
|
||||
// 多线程音频数据处理
|
||||
try {
|
||||
this.webWorker.postMessage(e.inputBuffer.getChannelData(0));
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef);
|
||||
this.mediaSource.connect(this.scriptProcessor);
|
||||
this.scriptProcessor.connect(this.audioContext.destination);
|
||||
this.connectWebSocket();
|
||||
};
|
||||
// 获取浏览器录音权限失败时回调
|
||||
let getMediaFail = (e) => {
|
||||
alert('对不起:录音权限获取失败!');
|
||||
this.audioContext && this.audioContext.close();
|
||||
this.audioContext = undefined;
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true
|
||||
}).then(stream => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}).catch(e => {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: true
|
||||
}, (stream) => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}, function (e) {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!');
|
||||
} else {
|
||||
alert('对不起:未识别到录音设备!');
|
||||
}
|
||||
this.audioContext && this.audioContext.close();
|
||||
return false;
|
||||
};
|
||||
};
|
||||
// 向webSocket发送数据(音频二进制数据经过Base64处理)
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) return false;
|
||||
// 音频数据
|
||||
const audioData = this.audioData.splice(0, 1280);
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData)
|
||||
}
|
||||
};
|
||||
// 发送数据
|
||||
this.webSocket.send(JSON.stringify(params));
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
return false;
|
||||
};
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: ''
|
||||
}
|
||||
})
|
||||
);
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(this.audioData.splice(0, 1280))
|
||||
}
|
||||
})
|
||||
);
|
||||
}, 40);
|
||||
};
|
||||
// 识别结束 webSocket返回数据
|
||||
webSocketRes(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(JSON.stringify(jsonData), 'websocket')
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result;
|
||||
let str = '';
|
||||
let ws = data.ws;
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w;
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp
|
||||
});
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str
|
||||
});
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
// 启动录音
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit();
|
||||
} else {
|
||||
this.audioContext.resume();
|
||||
this.connectWebSocket();
|
||||
}
|
||||
};
|
||||
// 停止录音
|
||||
recorderStop() {
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
this.audioContext && this.audioContext.suspend();
|
||||
}
|
||||
this.setStatus('end');
|
||||
try {
|
||||
// this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
|
||||
} catch (error) {
|
||||
console.error('暂停失败!');
|
||||
}
|
||||
};
|
||||
// 开始
|
||||
start() {
|
||||
this.recorderStart();
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' });
|
||||
};
|
||||
// 停止
|
||||
stop() {
|
||||
this.recorderStop();
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
let w_md5 = {}
|
||||
function hex_md5(string,bit) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
function md5_F(x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
}
|
||||
function md5_G(x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
}
|
||||
function md5_H(x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
function md5_I(x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
}
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
};
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
};
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
if(bit==32){
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
||||
return (md5_WordToHex(b) + md5_WordToHex(c)).toLowerCase();
|
||||
}
|
||||
//16位小写
|
||||
w_md5.hex_md5_16 = function (string) {
|
||||
return hex_md5(string,16);
|
||||
}
|
||||
//16位大写
|
||||
w_md5.hex_md5_16Upper = function (string) {
|
||||
return hex_md5(string,16).toUpperCase();
|
||||
}
|
||||
//32位小写
|
||||
w_md5.hex_md5_32 = function (string) {
|
||||
return hex_md5(string,32);
|
||||
}
|
||||
//32位大写
|
||||
w_md5.hex_md5_32Upper = function (string) {
|
||||
return hex_md5(string,32).toUpperCase();
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"autoclose":true,"delay":0,"target":"id:1","waiting":true},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"icon-android-hdpi.png","xhdpi":"icon-android-xhdpi.png","xxhdpi":"icon-android-xxhdpi.png","xxxhdpi":"icon-android-xxxhdpi.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"},"prerendered":"false"}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"],"packagename":"uni.UNI24DA8DD","aliasname":"platform","password":"aB6ADjKOYCOnZRmpzM7ptg==","keystore":"google-keystore.keystore","custompermissions":true},"apple":{"dSYMs":false,"devices":"universal"},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}},"orientation":"portrait-primary"},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html","adid":"123262070412"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"VideoPlayer":{},"Record":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"autoclose":true,"delay":0,"target":"id:1","waiting":true},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"icon-android-hdpi.png","xhdpi":"icon-android-xhdpi.png","xxhdpi":"icon-android-xxhdpi.png","xxxhdpi":"icon-android-xxxhdpi.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"},"prerendered":"false"}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>","<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"],"packagename":"uni.UNI24DA8DD","aliasname":"platform","password":"aB6ADjKOYCOnZRmpzM7ptg==","keystore":"google-keystore.keystore","custompermissions":true},"apple":{"dSYMs":false,"devices":"universal"},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}},"orientation":"portrait-primary"},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html","adid":"123262070412"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,808 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||||
<title>家庭管理语音AI页面</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 讯飞 -->
|
||||
<script src="./js/voice.js"></script>
|
||||
<script src="./js/crypto-js.min.js"></script>
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||||
<script src="js/w_md5.js"></script>
|
||||
|
||||
<!-- 构建界面 -->
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<!-- 加载遮罩 -->
|
||||
<div class="dialog" id="dialog" style="display: none;">
|
||||
<!-- <div class="overlay" id="overlay"></div> -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="ld"><em></em></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div class="video-wrap">
|
||||
<video id="myVideo" muted loop autoplay playsinline>
|
||||
<source src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<audio
|
||||
id="myAudio"
|
||||
autoplay
|
||||
controls
|
||||
style="width: 100% height: 10px">
|
||||
</audio>
|
||||
|
||||
<div id="AiButton">
|
||||
<!-- 录制 -->
|
||||
<img class="startRec" src="https://img.agrimedia.cn/bmsc/apps/start-tuya.png">
|
||||
<!-- 录制中 -->
|
||||
<img class="runRec" src="https://img.agrimedia.cn/bmsc/apps/runnig-tuya.png">
|
||||
<!-- 停止 -->
|
||||
<img class="endRec" src="https://img.agrimedia.cn/bmsc/apps/end-tuya.png">
|
||||
</div>
|
||||
|
||||
<!-- 讯飞测试 -->
|
||||
<div style="opacity: 1">
|
||||
<div class="voice-box">
|
||||
<input class="voice-input" type="search" name="voice" id="voice-txt" style="pointer-events: none"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂'];
|
||||
var Question = '';
|
||||
|
||||
function getURLParameter(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
// SEE事件解析
|
||||
function extractStopEvent(sseString) {
|
||||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||||
const events = sseString.split(/\r?\n\r?\n/);
|
||||
let stopEvent = null;
|
||||
|
||||
// 遍历每个事件
|
||||
events.forEach(event => {
|
||||
if (!event.trim()) {
|
||||
// 忽略空字符串
|
||||
return;
|
||||
}
|
||||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||||
let dataLines = [];
|
||||
let isDataLine = false;
|
||||
|
||||
// 分解当前事件为行
|
||||
const lines = event.split(/\r?\n/);
|
||||
|
||||
// 遍历每一行来找到 data 字段
|
||||
lines.forEach(line => {
|
||||
if (line.trim().startsWith('data:')) {
|
||||
isDataLine = true;
|
||||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||||
} else if (isDataLine && !line.trim()) {
|
||||
// 如果在 data 字段后遇到了空行,则停止收集
|
||||
isDataLine = false;
|
||||
} else if (isDataLine) {
|
||||
// 继续收集 data 字段的行(跨越多行的情况)
|
||||
dataLines.push(line.trim());
|
||||
}
|
||||
});
|
||||
|
||||
const dataJson = dataLines.join('');
|
||||
try {
|
||||
if (dataJson) {
|
||||
const eventData = JSON.parse(dataJson);
|
||||
if (eventData.output.finish_reason === "stop") {
|
||||
stopEvent = eventData;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||||
return stopEvent;
|
||||
}
|
||||
|
||||
// 筛选关键词
|
||||
function containsKeywordRegex(text) {
|
||||
var index = Items.findIndex(item => text.includes(item));
|
||||
|
||||
if (index !== -1) {
|
||||
return index
|
||||
} else {
|
||||
return 99999
|
||||
}
|
||||
}
|
||||
|
||||
// 语音所需时间
|
||||
function calculateSpeakingTime(text) {
|
||||
// 假设平均每分钟说230个单词
|
||||
const wordsPerMinute = 230;
|
||||
// 计算文字的长度
|
||||
const wordCount = text.trim().length;
|
||||
// 计算所需时间(以分钟为单位)
|
||||
const speakingTime = wordCount / wordsPerMinute;
|
||||
// 转换为秒数
|
||||
const speakingTimeInSeconds = speakingTime * 60;
|
||||
return speakingTimeInSeconds;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 讯飞语音识别 -->
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var demoData = {
|
||||
bloodGlucose: "血糖",
|
||||
SleepDatas: "睡眠",
|
||||
bloodOxygen: "血氧",
|
||||
bloodPressure: '血压',
|
||||
bloodLiquid: "血脂",
|
||||
meiTuo: '梅脱',
|
||||
pulseReat: '心率',
|
||||
bodyTemperature: '体温',
|
||||
ECGData: '心电图',
|
||||
bodyData: '身体成份',
|
||||
stepIndex: '运动'
|
||||
};
|
||||
|
||||
var videoElement = document.getElementById('myVideo');
|
||||
var startTime = 5; // 开始时间(以秒为单位)
|
||||
var endTime = 10; // 结束时间(以秒为单位)
|
||||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||||
|
||||
// 指定段落
|
||||
function playVideoSegment(startTime, endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
videoElement.play();
|
||||
|
||||
// 添加timeupdate事件监听器
|
||||
timeUpdateListener = function() {
|
||||
if (videoElement.currentTime >= endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
}
|
||||
};
|
||||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||||
}
|
||||
|
||||
// 重新播放
|
||||
function replayVideoSegment(startTime, endTime) {
|
||||
stopVideo();
|
||||
playVideoSegment(startTime, endTime);
|
||||
}
|
||||
|
||||
// 停止播放
|
||||
function stopVideo() {
|
||||
videoElement.pause();
|
||||
videoElement.currentTime = 0;
|
||||
|
||||
// 移除timeupdate事件监听器
|
||||
if (timeUpdateListener) {
|
||||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||||
timeUpdateListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前AI视频循环
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
// 获取 audio 元素的引用
|
||||
var audioElement = document.getElementById('myAudio');
|
||||
audioElement.muted = true; // 先静音
|
||||
|
||||
// 获取页面元素
|
||||
var element = document.getElementById("elementId");
|
||||
|
||||
// 获取遮罩和弹窗元素
|
||||
var dialog = document.getElementById('dialog');
|
||||
|
||||
// 点击事件
|
||||
var startRec = document.getElementsByClassName('startRec')[0];
|
||||
var runRec = document.getElementsByClassName('runRec')[0];
|
||||
var endRec = document.getElementsByClassName('endRec')[0];
|
||||
|
||||
var token = null;
|
||||
let times = null;
|
||||
|
||||
// 获取微软token
|
||||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||||
},
|
||||
}).then(async(response) => {
|
||||
token = await response.text();
|
||||
}).catch(e => {});
|
||||
|
||||
|
||||
// 文本输入框
|
||||
const voiceTxt = document.querySelector('#voice-txt');
|
||||
|
||||
// 防止多次请求
|
||||
var isCallbackExecuted = false;
|
||||
|
||||
|
||||
/*
|
||||
* 给予数据文字标识
|
||||
*/
|
||||
let exampleData = JSON.parse(getURLParameter('data'));
|
||||
for (let i = 0; i < exampleData.length; i++) {
|
||||
exampleData[i].name = demoData[exampleData[i].type]
|
||||
}
|
||||
|
||||
/*
|
||||
* 实例化迅飞语音听写(流式版)WebAPI
|
||||
*/
|
||||
const voice = new Voice({
|
||||
// 服务接口认证信息
|
||||
appId: '5f4ffdeb',
|
||||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||||
|
||||
onTextChange: function (text) {
|
||||
// 监听识别结果的变化
|
||||
console.log(text, '监听')
|
||||
voiceTxt.value = text;
|
||||
|
||||
// 3秒钟内没有说话,就自动关闭
|
||||
if (text) {
|
||||
clearTimeout(times);
|
||||
if (!isCallbackExecuted) {
|
||||
times = setTimeout(() => {
|
||||
this.stop();
|
||||
// voice.stop();
|
||||
|
||||
const params = { msg: text }
|
||||
|
||||
alert(params.msg)
|
||||
|
||||
/*
|
||||
* 拿到匹配的文字下标
|
||||
*/
|
||||
let QSindex = containsKeywordRegex(params.msg);
|
||||
console.log(QSindex)
|
||||
if (QSindex == 0) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodGlucose");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血糖为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 1) {
|
||||
const obj = exampleData.filter(item => item.type == "SleepDatas");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近睡眠时长为${obj[0].data_msg[0].sleepTotalTime}分钟`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 2) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodOxygen");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血氧为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 3) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodPressure");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血压为${obj[0].data_msg.bloodPressureLow}/${obj[0].data_msg.bloodPressureHigh}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 4 || QSindex == 11) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodLiquid");
|
||||
if (obj[0].data_msg.cholesterol) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血脂状况为,
|
||||
尿酸为${obj[0].data_msg.uricAcidVal/10},
|
||||
总胆固醇为${obj[0].data_msg.cholesterol/100},
|
||||
甘油三酯为${obj[0].data_msg.cholesterol/100},
|
||||
高密度脂蛋白为${obj[0].data_msg.cholesterol/100},
|
||||
低密度脂蛋白为${obj[0].data_msg.cholesterol/100}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 6) {
|
||||
const obj = exampleData.filter(item => item.type == "pulseReat");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的心率为${obj[0].data_msg[0]}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 7) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyTemperature");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的体温为${obj[0].data_msg}摄氏度`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 8) {
|
||||
const obj = exampleData.filter(item => item.type == "ECGData");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近心电图测量结果为${obj[0].data_msg.heartRate}次/分`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 9) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyData");
|
||||
if (obj[0].data_msg.BMI) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近身体成分结果为${obj[0].data_msg.BMI}`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 10) {
|
||||
const obj = exampleData.filter(item => item.type == "stepIndex");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的运动为${obj[0].data_msg.step}步数,
|
||||
${obj[0].data_msg.calorie/10}千卡,
|
||||
${obj[0].data_msg.distance/1000}公里`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 99999) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": params.msg},
|
||||
{"role": "user", "content": params.msg}
|
||||
]
|
||||
})
|
||||
|
||||
console.log(str.output.text, '返回的答案')
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
} else {
|
||||
/*
|
||||
* 调用接口 传递关键信息 文字转语音
|
||||
*/
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
|
||||
/*
|
||||
* 关键字转换
|
||||
*/
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": Question},
|
||||
{"role": "user", "content": `请问我${Items[QSindex]}正常吗`}
|
||||
]
|
||||
})
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 开始识别
|
||||
startRec.addEventListener("click", function() {
|
||||
/**开始识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.start();
|
||||
isCallbackExecuted = false;
|
||||
|
||||
// 先静音即可处理解决(提前做交互)
|
||||
audioElement.muted = false;
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
|
||||
startRec.style.display = 'none';
|
||||
runRec.style.display = 'block';
|
||||
// endRec.style.opacity = 0;
|
||||
showModal()
|
||||
});
|
||||
|
||||
|
||||
// 关闭识别
|
||||
endRec.addEventListener("click", function() {
|
||||
/**关闭识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.stop();
|
||||
// 音频
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
// 视频
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
isCallbackExecuted = false;
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
hideModal()
|
||||
});
|
||||
|
||||
|
||||
// 显示弹窗和遮罩
|
||||
function showModal() {
|
||||
// overlay.style.display = 'block';
|
||||
modal.style.display = 'block';
|
||||
dialog.style.display = 'block';
|
||||
}
|
||||
|
||||
// 隐藏弹窗和遮罩
|
||||
function hideModal() {
|
||||
// overlay.style.display = 'none';
|
||||
modal.style.display = 'none';
|
||||
dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/ai-persion-bg-tuya.jpeg');
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.video-wrap {
|
||||
width: 80%;
|
||||
top: 10%;
|
||||
height: auto;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/video-bg.png');
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myVideo {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myAudio {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#AiButton {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
#AiButton > img {
|
||||
width: 120px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* 遮罩样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||||
display: none; /* 初始隐藏 */
|
||||
z-index: 9999; /* 设置为最高层级 */
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ld {
|
||||
background: #000;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||||
|
||||
--dis: 20px; /*圆尺寸和偏移量*/
|
||||
--dur: 2s; /*动画时长*/
|
||||
}
|
||||
|
||||
.ld em {
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ld::before {
|
||||
background-color: #38597A;
|
||||
}
|
||||
|
||||
.ld::after {
|
||||
background-color: #3FEBFF;
|
||||
}
|
||||
|
||||
.ld em::before {
|
||||
background-color: #87B2DD;
|
||||
}
|
||||
|
||||
.ld em::after {
|
||||
background-color: #58D5FF;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld::after,
|
||||
.ld em::before,
|
||||
.ld em::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--dis);
|
||||
height: var(--dis);
|
||||
border-radius: var(--dis);
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation-duration: var(--dur);
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
--dir: 1;
|
||||
--tr: calc(var(--dir) * var(--dis));
|
||||
--ttr: calc(-1 * var(--tr));
|
||||
--tx: translateX(var(--tr));
|
||||
--ttx: translateX(var(--ttr));
|
||||
--ty: translateY(var(--tr));
|
||||
--tty: translateY(var(--ttr));
|
||||
}
|
||||
.ld::before,
|
||||
.ld::after {
|
||||
--dir: -1;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld em::before {
|
||||
transform: var(--ttx);
|
||||
animation-name: tx;
|
||||
}
|
||||
.ld::after,
|
||||
.ld em::after {
|
||||
transform: var(--tty);
|
||||
animation-name: ty;
|
||||
}
|
||||
|
||||
@keyframes tx {
|
||||
50% {
|
||||
transform: var(--tx);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ty {
|
||||
50% {
|
||||
transform: var(--ty);
|
||||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
width: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.rule::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.runRec {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.voice-input {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: none;
|
||||
line-height: 50px;
|
||||
border: 0;/*清除自带的2px的边框*/
|
||||
padding: 0;/*清除自带的padding间距*/
|
||||
outline: none;/*清除input点击之后的黑色边框*/
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,46 @@
|
|||
;
|
||||
(function() {
|
||||
let self = this
|
||||
self.onmessage = function(e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
; (function (window, voice) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(voice);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = voice();
|
||||
} else {
|
||||
window.Voice = voice();
|
||||
};
|
||||
}(typeof window !== "undefined" ? window : this, () => {
|
||||
"use strict";
|
||||
return class IatRecorder {
|
||||
constructor(opts = {}) {
|
||||
// 服务接口认证信息(语音听写(流式版)WebAPI)
|
||||
this.appId = opts.appId || '';
|
||||
this.apiKey = opts.apiKey || '';
|
||||
this.apiSecret = opts.apiSecret || '';
|
||||
// 识别监听方法
|
||||
this.onTextChange = opts.onTextChange || Function();
|
||||
this.onWillStatusChange = opts.onWillStatusChange || Function();
|
||||
// 方言/语种
|
||||
this.status = 'null'
|
||||
this.language = opts.language || 'zh_cn'
|
||||
this.accent = opts.accent || 'mandarin';
|
||||
// 流媒体
|
||||
this.streamRef = [];
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = '';
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = '';
|
||||
// 音频数据多线程
|
||||
this.init();
|
||||
};
|
||||
// WebSocket请求地址鉴权
|
||||
getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
try {
|
||||
const CryptoJS = require('crypto-js');
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
} catch (error) {
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
// 操作初始化
|
||||
init() {
|
||||
const self = this;
|
||||
try {
|
||||
if (!self.appId || !self.apiKey || !self.apiSecret) {
|
||||
alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!');
|
||||
} else {
|
||||
self.webWorker = new Worker('./js/transcode.worker.js');
|
||||
self.webWorker.onmessage = function (event) {
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
alert('对不起:请在服务器环境下运行!');
|
||||
console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error);
|
||||
};
|
||||
// console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;");
|
||||
};
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status);
|
||||
this.status = status;
|
||||
};
|
||||
// 设置识别结果内容
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '');
|
||||
resultText !== undefined && (this.resultText = resultText);
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp);
|
||||
};
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
};
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
};
|
||||
// 连接WebSocket
|
||||
connectWebSocket() {
|
||||
return this.getWebSocketUrl().then(url => {
|
||||
let iatWS;
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket!');
|
||||
return false;
|
||||
}
|
||||
this.webSocket = iatWS;
|
||||
this.setStatus('init');
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing');
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend();
|
||||
}, 500);
|
||||
};
|
||||
iatWS.onmessage = e => {
|
||||
this.webSocketRes(e.data);
|
||||
};
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
iatWS.onclose = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
})
|
||||
};
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.audioContext.resume();
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 获取浏览器录音权限成功时回调
|
||||
let getMediaSuccess = _ => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1);
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
if (this.status === 'ing') {
|
||||
// 多线程音频数据处理
|
||||
try {
|
||||
this.webWorker.postMessage(e.inputBuffer.getChannelData(0));
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef);
|
||||
this.mediaSource.connect(this.scriptProcessor);
|
||||
this.scriptProcessor.connect(this.audioContext.destination);
|
||||
this.connectWebSocket();
|
||||
};
|
||||
// 获取浏览器录音权限失败时回调
|
||||
let getMediaFail = (e) => {
|
||||
alert('对不起:录音权限获取失败!');
|
||||
this.audioContext && this.audioContext.close();
|
||||
this.audioContext = undefined;
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true
|
||||
}).then(stream => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}).catch(e => {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: true
|
||||
}, (stream) => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}, function (e) {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!');
|
||||
} else {
|
||||
alert('对不起:未识别到录音设备!');
|
||||
}
|
||||
this.audioContext && this.audioContext.close();
|
||||
return false;
|
||||
};
|
||||
};
|
||||
// 向webSocket发送数据(音频二进制数据经过Base64处理)
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) return false;
|
||||
// 音频数据
|
||||
const audioData = this.audioData.splice(0, 1280);
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData)
|
||||
}
|
||||
};
|
||||
// 发送数据
|
||||
this.webSocket.send(JSON.stringify(params));
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
return false;
|
||||
};
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: ''
|
||||
}
|
||||
})
|
||||
);
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(this.audioData.splice(0, 1280))
|
||||
}
|
||||
})
|
||||
);
|
||||
}, 40);
|
||||
};
|
||||
// 识别结束 webSocket返回数据
|
||||
webSocketRes(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(JSON.stringify(jsonData), 'websocket')
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result;
|
||||
let str = '';
|
||||
let ws = data.ws;
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w;
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp
|
||||
});
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str
|
||||
});
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
// 启动录音
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit();
|
||||
} else {
|
||||
this.audioContext.resume();
|
||||
this.connectWebSocket();
|
||||
}
|
||||
};
|
||||
// 停止录音
|
||||
recorderStop() {
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
this.audioContext && this.audioContext.suspend();
|
||||
}
|
||||
this.setStatus('end');
|
||||
try {
|
||||
// this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
|
||||
} catch (error) {
|
||||
console.error('暂停失败!');
|
||||
}
|
||||
};
|
||||
// 开始
|
||||
start() {
|
||||
this.recorderStart();
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' });
|
||||
};
|
||||
// 停止
|
||||
stop() {
|
||||
this.recorderStop();
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
let w_md5 = {}
|
||||
function hex_md5(string,bit) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
function md5_F(x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
}
|
||||
function md5_G(x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
}
|
||||
function md5_H(x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
function md5_I(x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
}
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
};
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
};
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
if(bit==32){
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
||||
return (md5_WordToHex(b) + md5_WordToHex(c)).toLowerCase();
|
||||
}
|
||||
//16位小写
|
||||
w_md5.hex_md5_16 = function (string) {
|
||||
return hex_md5(string,16);
|
||||
}
|
||||
//16位大写
|
||||
w_md5.hex_md5_16Upper = function (string) {
|
||||
return hex_md5(string,16).toUpperCase();
|
||||
}
|
||||
//32位小写
|
||||
w_md5.hex_md5_32 = function (string) {
|
||||
return hex_md5(string,32);
|
||||
}
|
||||
//32位大写
|
||||
w_md5.hex_md5_32Upper = function (string) {
|
||||
return hex_md5(string,32).toUpperCase();
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"VideoPlayer":{},"Record":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"unpackage/res/icons/72x72.png","xhdpi":"unpackage/res/icons/96x96.png","xxhdpi":"unpackage/res/icons/144x144.png","xxxhdpi":"unpackage/res/icons/192x192.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"}}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"]},"apple":{"dSYMs":false},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"VideoPlayer":{},"Record":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"unpackage/res/icons/72x72.png","xhdpi":"unpackage/res/icons/96x96.png","xxhdpi":"unpackage/res/icons/144x144.png","xxxhdpi":"unpackage/res/icons/192x192.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"}}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>","<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"]},"apple":{"dSYMs":false},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,808 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||||
<title>家庭管理语音AI页面</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 讯飞 -->
|
||||
<script src="./js/voice.js"></script>
|
||||
<script src="./js/crypto-js.min.js"></script>
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||||
<script src="js/w_md5.js"></script>
|
||||
|
||||
<!-- 构建界面 -->
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<!-- 加载遮罩 -->
|
||||
<div class="dialog" id="dialog" style="display: none;">
|
||||
<!-- <div class="overlay" id="overlay"></div> -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="ld"><em></em></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div class="video-wrap">
|
||||
<video id="myVideo" muted loop autoplay playsinline>
|
||||
<source src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<audio
|
||||
id="myAudio"
|
||||
autoplay
|
||||
controls
|
||||
style="width: 100% height: 10px">
|
||||
</audio>
|
||||
|
||||
<div id="AiButton">
|
||||
<!-- 录制 -->
|
||||
<img class="startRec" src="https://img.agrimedia.cn/bmsc/apps/start-tuya.png">
|
||||
<!-- 录制中 -->
|
||||
<img class="runRec" src="https://img.agrimedia.cn/bmsc/apps/runnig-tuya.png">
|
||||
<!-- 停止 -->
|
||||
<img class="endRec" src="https://img.agrimedia.cn/bmsc/apps/end-tuya.png">
|
||||
</div>
|
||||
|
||||
<!-- 讯飞测试 -->
|
||||
<div style="opacity: 1">
|
||||
<div class="voice-box">
|
||||
<input class="voice-input" type="search" name="voice" id="voice-txt" style="pointer-events: none"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂'];
|
||||
var Question = '';
|
||||
|
||||
function getURLParameter(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
// SEE事件解析
|
||||
function extractStopEvent(sseString) {
|
||||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||||
const events = sseString.split(/\r?\n\r?\n/);
|
||||
let stopEvent = null;
|
||||
|
||||
// 遍历每个事件
|
||||
events.forEach(event => {
|
||||
if (!event.trim()) {
|
||||
// 忽略空字符串
|
||||
return;
|
||||
}
|
||||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||||
let dataLines = [];
|
||||
let isDataLine = false;
|
||||
|
||||
// 分解当前事件为行
|
||||
const lines = event.split(/\r?\n/);
|
||||
|
||||
// 遍历每一行来找到 data 字段
|
||||
lines.forEach(line => {
|
||||
if (line.trim().startsWith('data:')) {
|
||||
isDataLine = true;
|
||||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||||
} else if (isDataLine && !line.trim()) {
|
||||
// 如果在 data 字段后遇到了空行,则停止收集
|
||||
isDataLine = false;
|
||||
} else if (isDataLine) {
|
||||
// 继续收集 data 字段的行(跨越多行的情况)
|
||||
dataLines.push(line.trim());
|
||||
}
|
||||
});
|
||||
|
||||
const dataJson = dataLines.join('');
|
||||
try {
|
||||
if (dataJson) {
|
||||
const eventData = JSON.parse(dataJson);
|
||||
if (eventData.output.finish_reason === "stop") {
|
||||
stopEvent = eventData;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||||
return stopEvent;
|
||||
}
|
||||
|
||||
// 筛选关键词
|
||||
function containsKeywordRegex(text) {
|
||||
var index = Items.findIndex(item => text.includes(item));
|
||||
|
||||
if (index !== -1) {
|
||||
return index
|
||||
} else {
|
||||
return 99999
|
||||
}
|
||||
}
|
||||
|
||||
// 语音所需时间
|
||||
function calculateSpeakingTime(text) {
|
||||
// 假设平均每分钟说230个单词
|
||||
const wordsPerMinute = 230;
|
||||
// 计算文字的长度
|
||||
const wordCount = text.trim().length;
|
||||
// 计算所需时间(以分钟为单位)
|
||||
const speakingTime = wordCount / wordsPerMinute;
|
||||
// 转换为秒数
|
||||
const speakingTimeInSeconds = speakingTime * 60;
|
||||
return speakingTimeInSeconds;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 讯飞语音识别 -->
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var demoData = {
|
||||
bloodGlucose: "血糖",
|
||||
SleepDatas: "睡眠",
|
||||
bloodOxygen: "血氧",
|
||||
bloodPressure: '血压',
|
||||
bloodLiquid: "血脂",
|
||||
meiTuo: '梅脱',
|
||||
pulseReat: '心率',
|
||||
bodyTemperature: '体温',
|
||||
ECGData: '心电图',
|
||||
bodyData: '身体成份',
|
||||
stepIndex: '运动'
|
||||
};
|
||||
|
||||
var videoElement = document.getElementById('myVideo');
|
||||
var startTime = 5; // 开始时间(以秒为单位)
|
||||
var endTime = 10; // 结束时间(以秒为单位)
|
||||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||||
|
||||
// 指定段落
|
||||
function playVideoSegment(startTime, endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
videoElement.play();
|
||||
|
||||
// 添加timeupdate事件监听器
|
||||
timeUpdateListener = function() {
|
||||
if (videoElement.currentTime >= endTime) {
|
||||
videoElement.currentTime = startTime;
|
||||
}
|
||||
};
|
||||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||||
}
|
||||
|
||||
// 重新播放
|
||||
function replayVideoSegment(startTime, endTime) {
|
||||
stopVideo();
|
||||
playVideoSegment(startTime, endTime);
|
||||
}
|
||||
|
||||
// 停止播放
|
||||
function stopVideo() {
|
||||
videoElement.pause();
|
||||
videoElement.currentTime = 0;
|
||||
|
||||
// 移除timeupdate事件监听器
|
||||
if (timeUpdateListener) {
|
||||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||||
timeUpdateListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前AI视频循环
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
// 获取 audio 元素的引用
|
||||
var audioElement = document.getElementById('myAudio');
|
||||
audioElement.muted = true; // 先静音
|
||||
|
||||
// 获取页面元素
|
||||
var element = document.getElementById("elementId");
|
||||
|
||||
// 获取遮罩和弹窗元素
|
||||
var dialog = document.getElementById('dialog');
|
||||
|
||||
// 点击事件
|
||||
var startRec = document.getElementsByClassName('startRec')[0];
|
||||
var runRec = document.getElementsByClassName('runRec')[0];
|
||||
var endRec = document.getElementsByClassName('endRec')[0];
|
||||
|
||||
var token = null;
|
||||
let times = null;
|
||||
|
||||
// 获取微软token
|
||||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||||
},
|
||||
}).then(async(response) => {
|
||||
token = await response.text();
|
||||
}).catch(e => {});
|
||||
|
||||
|
||||
// 文本输入框
|
||||
const voiceTxt = document.querySelector('#voice-txt');
|
||||
|
||||
// 防止多次请求
|
||||
var isCallbackExecuted = false;
|
||||
|
||||
|
||||
/*
|
||||
* 给予数据文字标识
|
||||
*/
|
||||
let exampleData = JSON.parse(getURLParameter('data'));
|
||||
for (let i = 0; i < exampleData.length; i++) {
|
||||
exampleData[i].name = demoData[exampleData[i].type]
|
||||
}
|
||||
|
||||
/*
|
||||
* 实例化迅飞语音听写(流式版)WebAPI
|
||||
*/
|
||||
const voice = new Voice({
|
||||
// 服务接口认证信息
|
||||
appId: '5f4ffdeb',
|
||||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||||
|
||||
onTextChange: function (text) {
|
||||
// 监听识别结果的变化
|
||||
console.log(text, '监听')
|
||||
voiceTxt.value = text;
|
||||
|
||||
// 3秒钟内没有说话,就自动关闭
|
||||
if (text) {
|
||||
clearTimeout(times);
|
||||
if (!isCallbackExecuted) {
|
||||
times = setTimeout(() => {
|
||||
this.stop();
|
||||
// voice.stop();
|
||||
|
||||
const params = { msg: text }
|
||||
|
||||
alert(params.msg)
|
||||
|
||||
/*
|
||||
* 拿到匹配的文字下标
|
||||
*/
|
||||
let QSindex = containsKeywordRegex(params.msg);
|
||||
console.log(QSindex)
|
||||
if (QSindex == 0) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodGlucose");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血糖为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 1) {
|
||||
const obj = exampleData.filter(item => item.type == "SleepDatas");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近睡眠时长为${obj[0].data_msg[0].sleepTotalTime}分钟`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 2) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodOxygen");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血氧为${obj[0].data_msg}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 3) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodPressure");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血压为${obj[0].data_msg.bloodPressureLow}/${obj[0].data_msg.bloodPressureHigh}毫摩尔/升`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 4 || QSindex == 11) {
|
||||
const obj = exampleData.filter(item => item.type == "bloodLiquid");
|
||||
if (obj[0].data_msg.cholesterol) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血脂状况为,
|
||||
尿酸为${obj[0].data_msg.uricAcidVal/10},
|
||||
总胆固醇为${obj[0].data_msg.cholesterol/100},
|
||||
甘油三酯为${obj[0].data_msg.cholesterol/100},
|
||||
高密度脂蛋白为${obj[0].data_msg.cholesterol/100},
|
||||
低密度脂蛋白为${obj[0].data_msg.cholesterol/100}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 6) {
|
||||
const obj = exampleData.filter(item => item.type == "pulseReat");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的心率为${obj[0].data_msg[0]}, `
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 7) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyTemperature");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的体温为${obj[0].data_msg}摄氏度`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 8) {
|
||||
const obj = exampleData.filter(item => item.type == "ECGData");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近心电图测量结果为${obj[0].data_msg.heartRate}次/分`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (QSindex == 9) {
|
||||
const obj = exampleData.filter(item => item.type == "bodyData");
|
||||
if (obj[0].data_msg.BMI) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近身体成分结果为${obj[0].data_msg.BMI}`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 10) {
|
||||
const obj = exampleData.filter(item => item.type == "stepIndex");
|
||||
if (obj[0].data_msg) {
|
||||
Question = `请模仿全科医生的口吻与我对话,我最近测量的运动为${obj[0].data_msg.step}步数,
|
||||
${obj[0].data_msg.calorie/10}千卡,
|
||||
${obj[0].data_msg.distance/1000}公里`
|
||||
} else {
|
||||
alert ('当前数据为空');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (QSindex == 99999) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": params.msg},
|
||||
{"role": "user", "content": params.msg}
|
||||
]
|
||||
})
|
||||
|
||||
console.log(str.output.text, '返回的答案')
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
} else {
|
||||
/*
|
||||
* 调用接口 传递关键信息 文字转语音
|
||||
*/
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||||
|
||||
|
||||
/*
|
||||
* 关键字转换
|
||||
*/
|
||||
var data = JSON.stringify({
|
||||
"messages": [
|
||||
{"role": "system", "content": Question},
|
||||
{"role": "user", "content": `请问我${Items[QSindex]}正常吗`}
|
||||
]
|
||||
})
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
const chunk = xhr.responseText;
|
||||
const str = extractStopEvent(chunk);
|
||||
|
||||
/*
|
||||
* 微软接口识别
|
||||
*/
|
||||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||||
<voice name="zh-CN-XiaoxiaoNeural">
|
||||
<mstts:express-as style="Default" >
|
||||
<prosody rate="0%" pitch="0%">
|
||||
${str.output.text}
|
||||
</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak> `,
|
||||
}).then(async(response) => {
|
||||
const content_bytes = await response.arrayBuffer();
|
||||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 设置音频源
|
||||
audioElement.src = blobUrl;
|
||||
|
||||
// 播放音频
|
||||
audioElement.play();
|
||||
|
||||
// 循环视频
|
||||
replayVideoSegment(60, 120);
|
||||
|
||||
// 计算所需时间
|
||||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||||
|
||||
// 开始倒计时
|
||||
var totalTime = speakingTime;
|
||||
var countdown = setInterval(function() {
|
||||
// 更新剩余时间
|
||||
totalTime --;
|
||||
if (totalTime <= 0) {
|
||||
// 停止倒计时
|
||||
clearInterval(countdown);
|
||||
replayVideoSegment(0, 60);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hideModal()
|
||||
}).catch(e => {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
isCallbackExecuted = true;
|
||||
return
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 开始识别
|
||||
startRec.addEventListener("click", function() {
|
||||
/**开始识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.start();
|
||||
isCallbackExecuted = false;
|
||||
|
||||
// 先静音即可处理解决(提前做交互)
|
||||
audioElement.muted = false;
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
|
||||
startRec.style.display = 'none';
|
||||
runRec.style.display = 'block';
|
||||
// endRec.style.opacity = 0;
|
||||
showModal()
|
||||
});
|
||||
|
||||
|
||||
// 关闭识别
|
||||
endRec.addEventListener("click", function() {
|
||||
/**关闭识别**/
|
||||
voiceTxt.value = '';
|
||||
voice.stop();
|
||||
// 音频
|
||||
audioElement.pause();
|
||||
audioElement.currentTime = 0;
|
||||
// 视频
|
||||
replayVideoSegment(0, 60);
|
||||
|
||||
isCallbackExecuted = false;
|
||||
|
||||
startRec.style.display = 'block';
|
||||
runRec.style.display = 'none';
|
||||
// endRec.style.opacity = 0;
|
||||
hideModal()
|
||||
});
|
||||
|
||||
|
||||
// 显示弹窗和遮罩
|
||||
function showModal() {
|
||||
// overlay.style.display = 'block';
|
||||
modal.style.display = 'block';
|
||||
dialog.style.display = 'block';
|
||||
}
|
||||
|
||||
// 隐藏弹窗和遮罩
|
||||
function hideModal() {
|
||||
// overlay.style.display = 'none';
|
||||
modal.style.display = 'none';
|
||||
dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/ai-persion-bg-tuya.jpeg');
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.video-wrap {
|
||||
width: 80%;
|
||||
top: 10%;
|
||||
height: auto;
|
||||
background-image: url('https://img.agrimedia.cn/bmsc/index/video-bg.png');
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myVideo {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#myAudio {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#AiButton {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
#AiButton > img {
|
||||
width: 120px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* 遮罩样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||||
display: none; /* 初始隐藏 */
|
||||
z-index: 9999; /* 设置为最高层级 */
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ld {
|
||||
background: #000;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||||
|
||||
--dis: 20px; /*圆尺寸和偏移量*/
|
||||
--dur: 2s; /*动画时长*/
|
||||
}
|
||||
|
||||
.ld em {
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ld::before {
|
||||
background-color: #38597A;
|
||||
}
|
||||
|
||||
.ld::after {
|
||||
background-color: #3FEBFF;
|
||||
}
|
||||
|
||||
.ld em::before {
|
||||
background-color: #87B2DD;
|
||||
}
|
||||
|
||||
.ld em::after {
|
||||
background-color: #58D5FF;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld::after,
|
||||
.ld em::before,
|
||||
.ld em::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--dis);
|
||||
height: var(--dis);
|
||||
border-radius: var(--dis);
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation-duration: var(--dur);
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
--dir: 1;
|
||||
--tr: calc(var(--dir) * var(--dis));
|
||||
--ttr: calc(-1 * var(--tr));
|
||||
--tx: translateX(var(--tr));
|
||||
--ttx: translateX(var(--ttr));
|
||||
--ty: translateY(var(--tr));
|
||||
--tty: translateY(var(--ttr));
|
||||
}
|
||||
.ld::before,
|
||||
.ld::after {
|
||||
--dir: -1;
|
||||
}
|
||||
|
||||
.ld::before,
|
||||
.ld em::before {
|
||||
transform: var(--ttx);
|
||||
animation-name: tx;
|
||||
}
|
||||
.ld::after,
|
||||
.ld em::after {
|
||||
transform: var(--tty);
|
||||
animation-name: ty;
|
||||
}
|
||||
|
||||
@keyframes tx {
|
||||
50% {
|
||||
transform: var(--tx);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ty {
|
||||
50% {
|
||||
transform: var(--ty);
|
||||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
width: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.rule::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
background: #aaa;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.runRec {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.voice-input {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: none;
|
||||
line-height: 50px;
|
||||
border: 0;/*清除自带的2px的边框*/
|
||||
padding: 0;/*清除自带的padding间距*/
|
||||
outline: none;/*清除input点击之后的黑色边框*/
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,46 @@
|
|||
;
|
||||
(function() {
|
||||
let self = this
|
||||
self.onmessage = function(e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
; (function (window, voice) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(voice);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = voice();
|
||||
} else {
|
||||
window.Voice = voice();
|
||||
};
|
||||
}(typeof window !== "undefined" ? window : this, () => {
|
||||
"use strict";
|
||||
return class IatRecorder {
|
||||
constructor(opts = {}) {
|
||||
// 服务接口认证信息(语音听写(流式版)WebAPI)
|
||||
this.appId = opts.appId || '';
|
||||
this.apiKey = opts.apiKey || '';
|
||||
this.apiSecret = opts.apiSecret || '';
|
||||
// 识别监听方法
|
||||
this.onTextChange = opts.onTextChange || Function();
|
||||
this.onWillStatusChange = opts.onWillStatusChange || Function();
|
||||
// 方言/语种
|
||||
this.status = 'null'
|
||||
this.language = opts.language || 'zh_cn'
|
||||
this.accent = opts.accent || 'mandarin';
|
||||
// 流媒体
|
||||
this.streamRef = [];
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = '';
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = '';
|
||||
// 音频数据多线程
|
||||
this.init();
|
||||
};
|
||||
// WebSocket请求地址鉴权
|
||||
getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
try {
|
||||
const CryptoJS = require('crypto-js');
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
} catch (error) {
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
// 操作初始化
|
||||
init() {
|
||||
const self = this;
|
||||
try {
|
||||
if (!self.appId || !self.apiKey || !self.apiSecret) {
|
||||
alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!');
|
||||
} else {
|
||||
self.webWorker = new Worker('./js/transcode.worker.js');
|
||||
self.webWorker.onmessage = function (event) {
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
alert('对不起:请在服务器环境下运行!');
|
||||
console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error);
|
||||
};
|
||||
// console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;");
|
||||
};
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status);
|
||||
this.status = status;
|
||||
};
|
||||
// 设置识别结果内容
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '');
|
||||
resultText !== undefined && (this.resultText = resultText);
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp);
|
||||
};
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
};
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
};
|
||||
// 连接WebSocket
|
||||
connectWebSocket() {
|
||||
return this.getWebSocketUrl().then(url => {
|
||||
let iatWS;
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket!');
|
||||
return false;
|
||||
}
|
||||
this.webSocket = iatWS;
|
||||
this.setStatus('init');
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing');
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend();
|
||||
}, 500);
|
||||
};
|
||||
iatWS.onmessage = e => {
|
||||
this.webSocketRes(e.data);
|
||||
};
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
iatWS.onclose = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
})
|
||||
};
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.audioContext.resume();
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 获取浏览器录音权限成功时回调
|
||||
let getMediaSuccess = _ => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1);
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
if (this.status === 'ing') {
|
||||
// 多线程音频数据处理
|
||||
try {
|
||||
this.webWorker.postMessage(e.inputBuffer.getChannelData(0));
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef);
|
||||
this.mediaSource.connect(this.scriptProcessor);
|
||||
this.scriptProcessor.connect(this.audioContext.destination);
|
||||
this.connectWebSocket();
|
||||
};
|
||||
// 获取浏览器录音权限失败时回调
|
||||
let getMediaFail = (e) => {
|
||||
alert('对不起:录音权限获取失败!');
|
||||
this.audioContext && this.audioContext.close();
|
||||
this.audioContext = undefined;
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true
|
||||
}).then(stream => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}).catch(e => {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: true
|
||||
}, (stream) => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}, function (e) {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!');
|
||||
} else {
|
||||
alert('对不起:未识别到录音设备!');
|
||||
}
|
||||
this.audioContext && this.audioContext.close();
|
||||
return false;
|
||||
};
|
||||
};
|
||||
// 向webSocket发送数据(音频二进制数据经过Base64处理)
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) return false;
|
||||
// 音频数据
|
||||
const audioData = this.audioData.splice(0, 1280);
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData)
|
||||
}
|
||||
};
|
||||
// 发送数据
|
||||
this.webSocket.send(JSON.stringify(params));
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
return false;
|
||||
};
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: ''
|
||||
}
|
||||
})
|
||||
);
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(this.audioData.splice(0, 1280))
|
||||
}
|
||||
})
|
||||
);
|
||||
}, 40);
|
||||
};
|
||||
// 识别结束 webSocket返回数据
|
||||
webSocketRes(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(JSON.stringify(jsonData), 'websocket')
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result;
|
||||
let str = '';
|
||||
let ws = data.ws;
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w;
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp
|
||||
});
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str
|
||||
});
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
// 启动录音
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit();
|
||||
} else {
|
||||
this.audioContext.resume();
|
||||
this.connectWebSocket();
|
||||
}
|
||||
};
|
||||
// 停止录音
|
||||
recorderStop() {
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
this.audioContext && this.audioContext.suspend();
|
||||
}
|
||||
this.setStatus('end');
|
||||
try {
|
||||
// this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
|
||||
} catch (error) {
|
||||
console.error('暂停失败!');
|
||||
}
|
||||
};
|
||||
// 开始
|
||||
start() {
|
||||
this.recorderStart();
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' });
|
||||
};
|
||||
// 停止
|
||||
stop() {
|
||||
this.recorderStop();
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
let w_md5 = {}
|
||||
function hex_md5(string,bit) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
function md5_F(x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
}
|
||||
function md5_G(x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
}
|
||||
function md5_H(x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
function md5_I(x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
}
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
};
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
};
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
};
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
if(bit==32){
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
||||
return (md5_WordToHex(b) + md5_WordToHex(c)).toLowerCase();
|
||||
}
|
||||
//16位小写
|
||||
w_md5.hex_md5_16 = function (string) {
|
||||
return hex_md5(string,16);
|
||||
}
|
||||
//16位大写
|
||||
w_md5.hex_md5_16Upper = function (string) {
|
||||
return hex_md5(string,16).toUpperCase();
|
||||
}
|
||||
//32位小写
|
||||
w_md5.hex_md5_32 = function (string) {
|
||||
return hex_md5(string,32);
|
||||
}
|
||||
//32位大写
|
||||
w_md5.hex_md5_32Upper = function (string) {
|
||||
return hex_md5(string,32).toUpperCase();
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"unpackage/res/icons/72x72.png","xhdpi":"unpackage/res/icons/96x96.png","xxhdpi":"unpackage/res/icons/144x144.png","xxxhdpi":"unpackage/res/icons/192x192.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"}}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"]},"apple":{"dSYMs":false},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__24DA8DD","name":"中鼎云医","version":{"name":"1.2.5","code":125},"description":"中鼎云医","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"VideoPlayer":{},"Record":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"icons":{"android":{"hdpi":"unpackage/res/icons/72x72.png","xhdpi":"unpackage/res/icons/96x96.png","xxhdpi":"unpackage/res/icons/144x144.png","xxxhdpi":"unpackage/res/icons/192x192.png"},"ios":{"appstore":"unpackage/res/icons/1024x1024.png","ipad":{"app":"unpackage/res/icons/76x76.png","app@2x":"unpackage/res/icons/152x152.png","notification":"unpackage/res/icons/20x20.png","notification@2x":"unpackage/res/icons/40x40.png","proapp@2x":"unpackage/res/icons/167x167.png","settings":"unpackage/res/icons/29x29.png","settings@2x":"unpackage/res/icons/58x58.png","spotlight":"unpackage/res/icons/40x40.png","spotlight@2x":"unpackage/res/icons/80x80.png"},"iphone":{"app@2x":"unpackage/res/icons/120x120.png","app@3x":"unpackage/res/icons/180x180.png","notification@2x":"unpackage/res/icons/40x40.png","notification@3x":"unpackage/res/icons/60x60.png","settings@2x":"unpackage/res/icons/58x58.png","settings@3x":"unpackage/res/icons/87x87.png","spotlight@2x":"unpackage/res/icons/80x80.png","spotlight@3x":"unpackage/res/icons/120x120.png"}}},"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>","<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"]},"apple":{"dSYMs":false},"plugins":{"ad":{},"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.15","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html"},"screenOrientation":["portrait-primary","portrait-secondary"]}
|
||||
Binary file not shown.
Loading…
Reference in New Issue