js實(shí)現(xiàn)mp3錄音通過(guò)websocket實(shí)時(shí)傳送+簡(jiǎn)易波形圖效果
波形圖:http://www.dbjr.com.cn/article/188545.htm
廢話(huà):想不到我的第一篇博客是關(guān)于前端,作為一名后端的小菜,前端方面肯定還有很多不足之處,如果文章有任何問(wèn)題歡迎指正。感謝大家。好了!廢話(huà)不多說(shuō)下面講一下需求。
需求:公司要求實(shí)現(xiàn)web端的錄音并通過(guò)websocket實(shí)時(shí)上傳至java后臺(tái),而且能通過(guò)vlc實(shí)時(shí)播放,簡(jiǎn)單一點(diǎn)講就是我用網(wǎng)頁(yè)在那一邊講話(huà),一個(gè)大喇叭就能實(shí)時(shí)把我的話(huà)播出去,這樣是不是通俗易懂呀,而且呢公司要求用mp3格式。當(dāng)然啦!為了知道自己在講話(huà)需要一個(gè)波形圖,這里主要實(shí)現(xiàn)前半部分功能,后半部分臣妾也做不到呀!后半部分的vlc播放呢如果大家想知道,可以留言,屆時(shí)可以給大家指條明路
前端實(shí)現(xiàn):
引入:
<script type="text/javascript" src="/js/recorder/recordmp3.js"></script>
這個(gè)跟大佬的js有點(diǎn)不一樣,我在里面加了一點(diǎn)東西,而且在這個(gè)js里面引入了兩個(gè)另外的js,lame.min.js和worker-realtime.js,這倆在大佬的代碼里有
頁(yè)面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>
<title>測(cè)試</title>
</head>
<body>
<button id="intercomBegin">開(kāi)始對(duì)講</button>
<button id="intercomEnd">關(guān)閉對(duì)講</button>
<canvas id="casvased" style="width: 400px;height: 100px"></canvas>
</body>
<script type="text/javascript" src="/js/jquery-3.3.1.js"></script>
<script type="text/javascript" src="/js/recorder/recordmp3.js"></script>
<script type="text/javascript">
var begin = document.getElementById('intercomBegin');
var end = document.getElementById('intercomEnd');
var canvas = document.getElementById("casvased");
var canvasCtx = canvas.getContext("2d");
var ws = null; //實(shí)現(xiàn)WebSocket
var recorder;
/*
* WebSocket
*/
function useWebSocket() {
ws = new WebSocket("ws://127.0.0.1:8089/send/voice");
ws.binaryType = 'arraybuffer'; //傳輸?shù)氖?ArrayBuffer 類(lèi)型的數(shù)據(jù)
ws.onopen = function () {
console.log('握手成功');
if (ws.readyState == 1) { //ws進(jìn)入連接狀態(tài),則每隔500毫秒發(fā)送一包數(shù)據(jù)
recorder.start();
}
};
ws.onmessage = function (msg) {
console.info(msg)
}
ws.onerror = function (err) {
console.info(err)
}
}
/*
* 開(kāi)始對(duì)講
*/
begin.onclick = function () {
recorder = new MP3Recorder({
debug: true,
funOk: function () {
console.log('點(diǎn)擊錄制,開(kāi)始錄音! ');
},
funCancel: function (msg) {
console.log(msg);
recorder = null;
}
});
}
/*
* 關(guān)閉對(duì)講
*/
end.onclick = function () {
if (ws) {
ws.close();
recorder.stop();
console.log('關(guān)閉對(duì)講以及WebSocket');
}
}
var sendData = function() { //對(duì)以獲取的數(shù)據(jù)進(jìn)行處理(分包)
var reader = new FileReader();
reader.onload = e => {
var outbuffer = e.target.result;
var arr = new Int8Array(outbuffer);
if (arr.length > 0) {
var tmparr = new Int8Array(1024);
var j = 0;
for (var i = 0; i < arr.byteLength; i++) {
tmparr[j++] = arr[i];
if (((i + 1) % 1024) == 0) {
ws.send(tmparr);
if (arr.byteLength - i - 1 >= 1024) {
tmparr = new Int8Array(1024);
} else {
tmparr = new Int8Array(arr.byteLength - i - 1);
}
j = 0;
}
if ((i + 1 == arr.byteLength) && ((i + 1) % 1024) != 0) {
ws.send(tmparr);
}
}
}
};
recorder.getMp3Blob(function (blob) {
reader.readAsArrayBuffer(blob);//這里拿到mp3格式的音頻流寫(xiě)入到reader中
})
}; </script> </html>
recordmp3.js
(function (exports) {
var MP3Recorder = function (config) {
var recorder = this;
config = config || {};
config.sampleRate = config.sampleRate || 44100;
config.bitRate = config.bitRate || 128;
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (navigator.getUserMedia) {
navigator.getUserMedia({
audio: true
},
function (stream) {
var context = new AudioContext(),
microphone = context.createMediaStreamSource(stream),
processor = context.createScriptProcessor(16384, 1, 1),//bufferSize大小,輸入channel數(shù),輸出channel數(shù)
mp3ReceiveSuccess, currentErrorCallback;
var height = 100;
var width = 400;
const analyser = context.createAnalyser()
analyser.fftSize = 1024
//連接到音頻源
microphone.connect(analyser);
analyser.connect(context.destination);
const bufferLength = analyser.frequencyBinCount // 返回的是 analyser的fftsize的一半
const dataArray = new Uint8Array(bufferLength);
function draw() {
canvasCtx.clearRect(0, 0, width, height); //清除畫(huà)布
analyser.getByteFrequencyData(dataArray); // 將當(dāng)前頻率數(shù)據(jù)復(fù)制到傳入其中的Uint8Array
const requestAnimFrame = window.requestAnimationFrame(draw) || window.webkitRequestAnimationFrame(draw);
canvasCtx.fillStyle = '#000130';
canvasCtx.fillRect(0, 0, width, height);
let barWidth = (width / bufferLength) * 2;
let barHeight;
let x = 0;
let c = 2
for (let i = 0; i < bufferLength; i++) {
barHeight = c+(dataArray[i]/400)*height;
canvasCtx.fillStyle = 'rgb(0, 255, 30)';
canvasCtx.fillRect(x, height / 2 - barHeight / 2, barWidth, barHeight);
x += barWidth + 1;
}
}
draw();
useWebSocket();
config.sampleRate = context.sampleRate;
processor.onaudioprocess = function (event) {
//邊錄音邊轉(zhuǎn)換
var array = event.inputBuffer.getChannelData(0);
realTimeWorker.postMessage({cmd: 'encode', buf: array});
sendData();
};
var realTimeWorker = new Worker('/js/recorder/worker-realtime.js');
realTimeWorker.onmessage = function (e) {
switch (e.data.cmd) {
case 'init':
log('初始化成功');
if (config.funOk) {
config.funOk();
}
break;
case 'end':
log('MP3大?。?, e.data.buf.length);
if (mp3ReceiveSuccess) {
mp3ReceiveSuccess(new Blob(e.data.buf, {type: 'audio/mp3'}));
}
break;
case 'error':
log('錯(cuò)誤信息:' + e.data.error);
if (currentErrorCallback) {
currentErrorCallback(e.data.error);
}
break;
default:
log('未知信息:', e.data);
}
};
recorder.getMp3Blob = function (onSuccess, onError) {
currentErrorCallback = onError;
mp3ReceiveSuccess = onSuccess;
realTimeWorker.postMessage({cmd: 'finish'});
};
recorder.start = function () {
if (processor && microphone) {
microphone.connect(processor);
processor.connect(context.destination);
log('開(kāi)始錄音');
}
}
recorder.stop = function () {
if (processor && microphone) {
microphone.disconnect();
processor.disconnect();
log('錄音結(jié)束');
}
}
realTimeWorker.postMessage({
cmd: 'init',
config: {
sampleRate: config.sampleRate,
bitRate: config.bitRate
}
});
},
function (error) {
var msg;
switch (error.code || error.name) {
case 'PERMISSION_DENIED':
case 'PermissionDeniedError':
msg = '用戶(hù)拒絕訪問(wèn)麥客風(fēng)';
break;
case 'NOT_SUPPORTED_ERROR':
case 'NotSupportedError':
msg = '瀏覽器不支持麥客風(fēng)';
break;
case 'MANDATORY_UNSATISFIED_ERROR':
case 'MandatoryUnsatisfiedError':
msg = '找不到麥客風(fēng)設(shè)備';
break;
default:
msg = '無(wú)法打開(kāi)麥克風(fēng),異常信息:' + (error.code || error.name);
break;
}
if (config.funCancel) {
config.funCancel(msg);
}
});
} else {
if (config.funCancel) {
config.funCancel('當(dāng)前瀏覽器不支持錄音功能');
}
}
function log(str) {
if (config.debug) {
console.log(str);
}
}
}
exports.MP3Recorder = MP3Recorder;
})(window);
后端websocket:
這里實(shí)現(xiàn)的是保存為mp3文件
package com.jetosend.common.socket;
import com.jetosend.common.utils.Utils;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Hashtable;
import java.util.Map;
@ServerEndpoint("/send/{key}")
@Component
public class ServerSocket {
private static final Map<String, Session> connections = new Hashtable<>();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
/***
* @Description:打開(kāi)連接
* @Param: [id, 保存對(duì)方平臺(tái)的資源編碼
* session]
* @Return: void
* @Author: Liting
* @Date: 2019-10-10 09:22
*/
@OnOpen
public void onOpen(@PathParam("key") String id, Session session) {
System.out.println(id + "連上了");
connections.put(id, session);
}
/**
* 接收消息
*/
@OnMessage
public void onMessage(@PathParam("key") String id, InputStream inputStream) {
System.out.println("來(lái)自" + id);
try {
int rc = 0;
byte[] buff = new byte[100];
while ((rc = inputStream.read(buff, 0, 100)) > 0) {
byteArrayOutputStream.write(buff, 0, rc);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 異常處理
*
* @param throwable
*/
@OnError
public void onError(Throwable throwable) {
throwable.printStackTrace();
//TODO 日志打印異常
}
/**
* 關(guān)閉連接
*/
@OnClose
public void onClose(@PathParam("key") String id) {
System.out.println(id + "斷開(kāi)");
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = null;
try {
file = new File("D:\\testtest.mp3");
//輸出流
fos = new FileOutputStream(file);
//緩沖流
bos = new BufferedOutputStream(fos);
//將字節(jié)數(shù)組寫(xiě)出
bos.write(byteArrayOutputStream.toByteArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
connections.remove(id);
}
實(shí)現(xiàn)效果:
總結(jié)
到此這篇關(guān)于js實(shí)現(xiàn)mp3錄音通過(guò)websocket實(shí)時(shí)傳送+簡(jiǎn)易波形圖效果的文章就介紹到這了,更多相關(guān)js實(shí)現(xiàn)mp3錄音內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 如何用JS WebSocket實(shí)現(xiàn)簡(jiǎn)單聊天
- 原生nodejs使用websocket代碼分享
- node.js基于express使用websocket的方法
- JS實(shí)現(xiàn)websocket長(zhǎng)輪詢(xún)實(shí)時(shí)消息提示的效果
- nodejs+websocket實(shí)時(shí)聊天系統(tǒng)改進(jìn)版
- websocket+node.js實(shí)現(xiàn)實(shí)時(shí)聊天系統(tǒng)問(wèn)題咨詢(xún)
- 基于Node.js的WebSocket通信實(shí)現(xiàn)
- Node.js websocket使用socket.io庫(kù)實(shí)現(xiàn)實(shí)時(shí)聊天室
- Javascript WebSocket使用實(shí)例介紹(簡(jiǎn)明入門(mén)教程)
- 詳解JS WebSocket斷開(kāi)原因和心跳機(jī)制
相關(guān)文章
JavaScript插件化開(kāi)發(fā)教程 (三)
前面我們學(xué)習(xí)了jQuery的方式開(kāi)發(fā)插件,講訴的都是些基礎(chǔ)的理論知識(shí),今天開(kāi)始,我們就來(lái)實(shí)戰(zhàn)一下,學(xué)習(xí)開(kāi)發(fā)自己的插件庫(kù)。2015-01-01
基于JS實(shí)現(xiàn)Android,iOS一個(gè)手勢(shì)動(dòng)畫(huà)效果
這篇文章主要介紹了基于JS實(shí)現(xiàn)Android,iOS一個(gè)手勢(shì)動(dòng)畫(huà)效果 的相關(guān)資料,需要的朋友可以參考下2016-04-04
我也種棵OO樹(shù)JXTree[js+css+xml]
我也種棵OO樹(shù)JXTree[js+css+xml]...2007-04-04
對(duì)TypeScript庫(kù)進(jìn)行單元測(cè)試的方法
這篇文章主要介紹了對(duì)TypeScript庫(kù)進(jìn)行單元測(cè)試的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
JavaScript中的"=、==、==="區(qū)別講解
今天小編就為大家分享一篇關(guān)于JavaScript中的"=、==、==="區(qū)別講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
微信小程序彈窗禁止頁(yè)面滾動(dòng)的實(shí)現(xiàn)代碼
這篇文章主要介紹了微信小程序彈窗禁止頁(yè)面滾動(dòng)的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

