前端设置
前端界面参考了ns2250225/audioRecord: 📢 仿微信的语音段传输 (github.com), 使用 html+css+js 编写网页,其中html控制页面中有哪些组成部分,css控制各组成部分的外观,JS控制页面的执行逻辑。
前端代码中,较为重要的两个步骤在于音频录制和音频传输。以下分辨介绍这两个部分。
1. 音频录制
音频录制代码如下,主要基于 MediaRecorder 服务。
if (navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia supported.');
var constraints = { audio: true };
var chunks = [];
var onSuccess = function (stream) {
var mediaRecorder = new MediaRecorder(stream);
record.onclick = function () {
mediaRecorder.start();
console.log(mediaRecorder.state);
console.log("recorder started");
stop.disabled = false;
record.disabled = true;
}
stop.onclick = function () {
mediaRecorder.stop();
console.log(mediaRecorder.state);
console.log("recorder stopped");
stop.disabled = true;
record.disabled = false;
}
mediaRecorder.onstop = function (e) {
console.log("data available after MediaRecorder.stop() called.");
// 保存录音
var blob = new Blob(chunks, { 'type': 'audio/ogg; codecs=opus' });
// 生成录音框
createAudioBox(blob, msg_content)
// 发送录音
ws.send(blob)
// 重置录音数据
chunks = [];
console.log("recorder stopped");
}
// 录音逻辑
mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
}
}
var onError = function (err) {
console.log('The following error occured: ' + err);
}
// 开始获取音频流
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
} else {
console.log('getUserMedia not supported on your browser!');
}
以上代码中的录音框函数createAudioBox()
如下所示。注意这里的一个坑是,正常设置录音框时,audio.duration
属性,这说明计算机不清楚音频的具体时长。这种情况下,需要将audio.currentTime
赋一个非常大的值,相当于直接将进度条拉到终点,以检测音频具体的时间。
/* 创建音频框 */
function createAudioBox(blob_obj, msg_content) {
var tmp_div = document.createElement('div');
var audio = document.createElement('audio');
var tmp_span = document.createElement('span');
var tmp_btn = document.createElement('img');
tmp_div.setAttribute('class', 'myAudio');
tmp_span.setAttribute('class', 'audio_time');
tmp_btn.setAttribute('class', 'play_btn');
tmp_btn.src = "/static/images/audio-high.png"
audio.setAttribute('id', 'myAudio');
audio.src = window.URL.createObjectURL(blob_obj);
audio.addEventListener('loadedmetadata', () => {
if (audio.duration === Infinity || isNaN(Number(audio.duration))) {
audio.currentTime = 1e101 // 相当于快进
audio.addEventListener('timeupdate', getDuration)
}
})
function getDuration(event) {
event.target.currentTime = 0
event.target.removeEventListener('timeupdate', getDuration)
tmp_span.innerHTML = event.target.duration + '"'
}
2. 音频传输
音频传输服务代码如下, 核心是利用 WebSocket()
与服务器之间建立套接字。
// 注册PWA服务
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/js/sw.js')
.then(function () {
console.log('SW registered');
});
}
// 设置websocket服务器地址
const wsUrl = 'ws://localhost:8000/';
const ws = new WebSocket(wsUrl);
ws.binaryType = "arraybuffer";
// Websocket钩子方法
ws.onopen = function (evt) {
console.log('ws open()');
}
ws.onerror = function (err) {
console.error('ws onerror() ERR:', err);
}
ws.onmessage = function (evt) {
console.log('ws onmessage() data:', typeof (evt.data));
// 判断 typeof (evt.data) 是否为文本类型
if (typeof (evt.data) === 'string') {
// 添加文本显示框
var tmp_div = document.createElement('div');
tmp_div.setAttribute('class', 'myText');
tmp_div.innerHTML = evt.data;
msg_content.appendChild(tmp_div);
} else if (typeof (evt.data) === 'object') {
// TODO: 未来添加TTS服务时,可在此增加接受语音的逻辑
} else {
console.error('Unexpected data type:', typeof (evt.data));
}
}