提示:webRTC要https协议或者127.0.0.1或localhost访问,出于安全性考虑
首先要知道nodejs怎么上传文件,如果不会,请移步这里https://blog.csdn.net/qq_38652871/article/details/89641769
没有multiparty的自行安装一下
npm install multiparty
上传文件会了之后这个就简单了,recorder.js就是基于webRTC做的
~~
~~
~~
~~看目录
recorder.js的例子只是一个前端的录音和播放等,没涉及到服务端功能,给服务端传啥啊(其实是自己没发现而已),这不行啊,于是就开始读源码,准备改改让其符合自己的需求,结果读完发现,自己要的东西都有了,没必要改了,其实也没啥好改的了,只需要将录音得到的东西传给服务端不就哦了
突然间发现,其实就是一个录音功能和上传文件并返回文件地址的一个功能,感觉没啥好讲的了,不信你看哈:
上传的ajax封装好了,只需要调用就行,录音功能recorder.js也封装好了,又是调用,里面也有例子,算了,把我写好的例子放这儿吧
//app.js
app.use('/users', usersRouter); //这里添加一个路由,录音完成要上传用的路由
//router/users.js //这里是nodejs服务端保存文件用的 ~em mmm~ 接口
var express = require('express');
var path = require('path')
var router = express.Router();
var multiparty = require('multiparty');
var util = require('util');
var fs = require('fs');
//这是直接借用了之前的例子,反正报错和不报错都能走到页面,就它了,不要在意细节~~~
router.get('/',function(req,res,next){
db.query("select * from user",function(err,rows){
if(err){
res.render("users",{title:'用户列表',datas:[],s_name:''});
}else{
res.render("users",{title:'用户列表',datas:rows,s_name:''});
}
})
});
router.post('/loadwav', function (req, res, next) {
let storagePath = __dirname + '/../public/files/'
var form = new multiparty.Form({ uploadDir: storagePath });
form.parse(req, function (err, fields, files) {
console.log(files, fields);
if (err) {
console.log('parse error: ' + err);
res.status(200).json({ success:false,msg: '上传失败', err, fileUrl: '' });
res.end();
} else {
let inputFile = files.file[0],myDate = new Date();
inputFile.originalFilename = 'record'+myDate.getTime()+''+parseInt(Math.random()*1000) + '.' + fields.type
let tmpPath = inputFile.path;
let backUrl = 'files/'+inputFile.originalFilename;
// let finalPath = storagePath + inputFile.originalFilename;
let finalPath = __dirname + '/../public/' + backUrl;
//移动并重命名为真实文件名
fs.rename(tmpPath, finalPath, function (err) {
if (err) {
console.log('rename error: ' + err);
} else {
console.log('rename ok');
}
});
res.status(200).json({ success:true,msg: '上传成功', fileUrl: backUrl, err: '' });
res.end();
}
// res.writeHead(200, { 'content-type': 'application/json;charset=utf-8' });
// res.write('received upload:\n\n');
})
});
module.exports = router;
下面这个是views/user.ejs模板,没写样式,注意看重点,不要在意细节
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %>title>
head>
<body>
<ul id="audioBox">ul>
<input type="button" value="录音开启" id="startRecord" />
<input type="button" value="暂停" id="pauseRecord" />
<input type="button" value="恢复" id="resumeRecord" />
<input type="button" value="录音停止" id="endRecord" />
<input type="button" value="播放录音" id="playRecord" />
<button id="wavBlobBtn">上传wavblob语音对象button>
<h3>
下载
h3>
<input type="button" value="下载pcm" id="downloadPCM" />
<input type="button" value="下载wav" id="downloadWAV" />
<h3>
录音时长:<span id="time">0span>s
h3>
<h3>
声音波形图如下:
h3>
<canvas id="canvas">canvas>
<h3>
加载播放:
h3>
<input type="file" value="加载文件播放" id="uploadAudio" />
<script src="javascripts/ajax.js">script>
<script src="javascripts/recorder.js">script>
<script>
var oDiv = document.getElementById('box'),
audio = document.getElementById('audio'),
oTime = document.getElementById('time'),
recorder = null,
oCanvas = document.getElementById("canvas"), // 显示波形的canvas
ctx = oCanvas.getContext("2d"),
drawRecordId = null,
audiObj = document.getElementById('audioTag'),
ulObj = document.getElementById('audioBox');
// 按钮事件绑定
document.getElementById('startRecord').addEventListener('click', startRecord);
document.getElementById('pauseRecord').addEventListener('click', pauseRecord);
document.getElementById('resumeRecord').addEventListener('click', resumeRecord);
document.getElementById('endRecord').addEventListener('click', endRecord);
document.getElementById('playRecord').addEventListener('click', playRecord);
document.getElementById('downloadPCM').addEventListener('click', downloadPCM);
document.getElementById('downloadWAV').addEventListener('click', downloadWAV);
document.getElementById('uploadAudio').addEventListener('change', uploadAudio);
document.getElementById('wavBlobBtn').addEventListener('click', getWavBlob);
// canvas背景初始化
initCanvasBg()
// 开始录音
function startRecord() {
if (!recorder) {
recorder = new Recorder({
// 以下是默认配置
sampleBits: 16,
// sampleRate: 浏览器默认的输入采样率,
numChannels: 1,
});
recorder.onprocess = function(duration) {
oTime.innerText = duration;
}
}
recorder.start();
// 开始绘制canvas
drawRecord();
}
// 暂停录音
function pauseRecord() {
recorder && recorder.pause();
}
// 恢复录音
function resumeRecord() {
recorder && recorder.resume();
}
// 结束录音
function endRecord (e) {
recorder && recorder.stop();
drawRecordId && cancelAnimationFrame(drawRecordId);
drawRecordId = null;
}
// 播放录音
function playRecord() {
recorder && recorder.play();
drawRecordId && cancelAnimationFrame(drawRecordId);
drawRecordId = null;
}
// 下载pcm
function downloadPCM() {
recorder && recorder.downloadPCM();
}
// 下载wav
function downloadWAV() {
recorder && recorder.downloadWAV();
}
// canvas波形绘制函数
function drawRecord() {
// 用requestAnimationFrame稳定60fps绘制
drawRecordId = requestAnimationFrame(drawRecord);
// 实时获取音频大小数据
var dataArray = recorder.getRecordAnalyseData(),
bufferLength = dataArray.length;
// 填充背景色
ctx.fillStyle = 'rgb(200, 200, 200)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 设定波形绘制颜色
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.beginPath();
var sliceWidth = canvas.width * 1.0 / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制
x = 0; // 绘制点的x轴位置
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 128.0;
var y = v * canvas.height / 2;
if (i === 0) {
// 第一个点
ctx.moveTo(x, y);
} else {
// 剩余的点
ctx.lineTo(x, y);
}
// 依次平移,绘制所有点
x += sliceWidth;
}
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
}
// canvas背景初始化
function initCanvasBg() {
ctx.fillStyle = 'rgb(200, 200, 200)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 加载音频文件并播放
function uploadAudio(e) {
Recorder.playAudio(this.files[0]);
}
//获取wavblob对象
function getWavBlob(){
recorder && recorder.stop();
drawRecordId && cancelAnimationFrame(drawRecordId);
drawRecordId = null;
let url = 'http://localhost:3000/users/loadwav'; //上传地址
let wavBlob = recorder.getWAVBlob();
let formData = new FormData();
formData.append('file',wavBlob);
formData.append('name','recorder');
formData.append('type','wav');
new Promise((resolve,reject)=>{
Ajax({
url,
data:formData,
resolve,
reject,
headerType:2
})
}).then(function(resp){
if(resp.success){
let li = document.createElement("li");
let audio = document.createElement("audio");
li.appendChild(audio);
audio.setAttribute("controls","controls");
audio.src = resp.fileUrl;
ulObj.appendChild(li);
}else{
alert("发送语音失败")
}
}).catch(function(error){
console.log(error);
})
}
script>
body>
html>
超级low的demo
总结:写好了以后发现,挺简单挺烂的一个demo,将就看吧,这就是用来实现功能的,然后可以拿到自己的项目里,那时候才是花里胡哨的页面的时候,不喜勿喷哦 (* ̄3 ̄)╭
2019-06-21更新:
追加vue版本demo:
添加对应demo的router就不一 一叙说了,直接看例子,对了,这里的vue版本本地调试还是用的上面的后台
app.all('*', function(req, res, next) {
console.log('req.url',req.url);
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
'/users': {
changeOrigin: true,
target: 'http://localhost:3000', //后台反向代理到的地址
pathRewrite: {
'^users': ''
}
}
<template>
<div class="voice">
<ul class="msg-box">
<li v-for="(item,index) of voiceList" :key="index">
<audio :src="item.fileUrl" controls>audio>
li>
ul>
<div class="btn-box">
<p class="time" v-show="canvasFlag">{{time}}sp>
<canvas id="canvas" v-show="canvasFlag">canvas>
<el-button type="primary" size="small" @click="startRecord">开始录音el-button>
<el-button type="primary" size="small" @click="sendVoice">发送录音el-button>
div>
div>
template>
<script>
import Recorder from "@/plugins/recorder.js";
export default {
data() {
return {
visible: false,
msgList: [],
time: 0,
recorder: null,
oCanvas: null,
ctx: null,
canvasFlag: false,
drawRecordId: null
};
},
mounted() {
this.initCanvasBg();
},
computed: {
voiceList() { //这个是获取到语音消息列表之后,利用计算属性给每个语音地址转换成绝对路径地址
let tmpList = [];
tmpList = JSON.parse(JSON.stringify(this.msgList));
tmpList.forEach((el, i) => {
el.fileUrl = 'http://localhost:3000/'+el.fileUrl;
})
return tmpList;
}
},
methods: {
startRecord() { //开始录音
this.canvasFlag = true;
if (!this.recorder) {
this.recorder = new Recorder({
// 以下是默认配置
sampleBits: 16,
numChannels: 1
});
this.recorder.onprocess = (duration)=> {
this.time = duration.toFixed(4);
};
}
this.recorder.start();
// 开始绘制canvas
this.drawRecord();
},
sendVoice() { //发送语音消息
this.canvasFlag = false;
this.recorder && this.recorder.stop();
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
let url = "/users/loadwav"; //上传地址
let wavBlob = this.recorder.getWAVBlob();
let data = new FormData();
data.append("file", wavBlob);
data.append("type", "wav");
this.$axios
.post(url, data, {
headers: { "Content-Type": "multipart/form-data" }
})
.then(res => {
if(res.data.success){
let tmpObj = {};
tmpObj.fileUrl = res.data.fileUrl;
this.msgList.push(tmpObj);
}else{
this.$message({
message: "消息发送失败,请重试",
type: "warning"
});
}
})
.catch(err => {
this.$message({
message: "消息发送失败,请重试",
type: "warning"
});
});
},
initCanvasBg() {
this.oCanvas = document.getElementById("canvas"); // 显示波形的canvas
this.ctx = this.oCanvas.getContext("2d");
this.ctx.fillStyle = "#f5f5f5";
this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height);
},
drawRecord() {
// 用requestAnimationFrame稳定60fps绘制
this.drawRecordId = requestAnimationFrame(this.drawRecord);
// 实时获取音频大小数据
let dataArray = this.recorder.getRecordAnalyseData(),
bufferLength = dataArray.length;
// 填充背景色
this.ctx.fillStyle = "#f5f5f5";
this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height);
// 设定波形绘制颜色
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = "rgba(0, 0, 0, 0.3)";
this.ctx.beginPath();
let sliceWidth = (this.oCanvas.width * 1.0) / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制
x = 0; // 绘制点的x轴位置
for (let i = 0; i < bufferLength; i++) {
let v = dataArray[i] / 128.0;
let y = (v * this.oCanvas.height) / 2;
if (i === 0) {
// 第一个点
this.ctx.moveTo(x, y);
} else {
// 剩余的点
this.ctx.lineTo(x, y);
}
// 依次平移,绘制所有点
x += sliceWidth;
}
this.ctx.lineTo(this.oCanvas.width, this.oCanvas.height / 2);
this.ctx.stroke();
}
}
};
script>
<style lang="less" scoped>
.voice {
width: 100%;
height: 100%;
background: #ffffff;
padding: 10px;
.msg-box {
min-height: 150px;
max-height: calc(100% - 80px);
width: 500px;
overflow-y: auto;
border: 1px solid #333333;
margin-bottom: 15px;
}
.btn-box {
position: relative;
#canvas {
position: absolute;
bottom: 40px;
z-index: 1;
}
.time {
position: absolute;
bottom: 50px;
left: 15px;
z-index: 2;
}
}
}
style>