笔记参照来源,建议跳转查看更加详细详情:前端实现在浏览器网页中录音 - 浅笑· - 博客园 (cnblogs.com)
record.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>H5录音组件收录</title> <link rel="stylesheet" type="text/css" href="./PlugIn/tipsDialogTypeG/tipsDialogTypeG.css"> <style type="text/css"> html, body, #paperFrame { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } .header-area { width: 100%; height: 50px; } .header-area-title { width: calc(100% - 150px); height: 50px; line-height: 50px; text-align: left; padding-left: 10px; float: left; font-weight: bold; font-size: 20px; } .header-area-close { width: 30px; height: 50px; float: right; margin-right: 10px; background-image: url("./Images/关闭1.png"); background-repeat: no-repeat; background-position: right 13px; } .header-area-close:hover { background-image: url("./Images/关闭1【经过】.png"); cursor: pointer; } .setting-info-area{ width: 100%; height: 50px; overflow: hidden; } .setting-btn{ padding: 0 15px; height: 30px; line-height: 30px; float: left; margin-left: 15px; background-color: #409eff; color: white; border-radius: 5px; } .setting-btn:hover{ cursor: pointer; } </style> </head> <body> <div id="paperFrame"> <div class="header-area"> <div class="header-area-title">H5录音组件收录</div> <div class="header-area-close" @click="testCloseFrame"></div> </div> <div class="setting-info-area"> <div class="setting-btn recoding-btn" @click="recodingBtn">录音</div> <div class="setting-btn recoded-btn" @click="recodedBtn">结束</div> <div class="setting-btn upload-btn" @click="uploadBtn">上传</div> </div> <audio v-if="audioUrl" controls :src="audioUrl"></audio> </div> <script type="text/javascript" src="./JS/jquery-1.12.4.min.js"></script> <script type="text/javascript" src="./JS/vue.2.5.17.min.js"></script> <script type="text/javascript" src="./JS/vuescroll.js"></script> <script type="text/javascript" src="./PlugIn/tipsDialogTypeG/tipsDialogTypeG.js"></script> <script type="text/javascript"> var paperFrame = new Vue({ el: "#paperFrame", data: { constraints:{audio: true}, mediaRecorder:null, recodChunks:[], audioUrl:null, audioFile:null, }, methods: { // 上传 uploadBtn:function (){ if(this.audioFile==null){ return; } /* 通过节点获取文件 var files = $('.div-cls')[0].files; var uploadFiles = new FormData(); uploadFiles.append('uploadFiles', files); */ //上传文件 var uploadFiles = new FormData(); uploadFiles.append('uploadFiles', this.audioFile); $.ajax({ method: 'POST', url: 'http://127.0.0.1:8080/api/Resource/upLoadFiles', data: uploadFiles, processData: false, contentType: false, success: function (res) { console.log("上传文件成功!!!"); console.log(res); }, error: function(jqXHR, textStatus, errorThrown) { console.log("上传文件失败!!!"); console.log(textStatus,errorThrown); } }) }, // 结束 recodedBtn:function (){ var _this=this; try{ _this.mediaRecorder.stop(); console.log(_this.recodChunks) _this.mediaRecorder.onstop = e => { /** 文本 * // 假设你已经有了一个Blob对象 * const blob = new Blob(["Hello, world!"], { type: "text/plain" }); * * // 创建File对象 * const file = new File([blob], "hello-world.txt", { * lastModified: new Date().getTime(), * type: blob.type * }); * */ // 音频 var blob = new Blob(_this.recodChunks, {type: "audio/ogg; codecs=opus"}); // 合成音频文件 _this.audioFile=new File([blob], "1.mp3", { lastModified: new Date().getTime(), type: blob.type }); // 合成音频路径 _this.audioUrl = window.URL.createObjectURL(blob); }; console.log("录音结束"); tipsDialogGcomponent.dialogShow({ type: 3, content: "录音结束", timeOut: 3000 }); }catch (e){ tipsDialogGcomponent.dialogShow({ type: 1, content: e, timeOut: 3000 }); } }, // 录音 recodingBtn:function (){ try{ this.mediaRecorder.start(); var _this=this; _this.recodChunks = []; this.mediaRecorder.ondataavailable = function (e) { _this.recodChunks.push(e.data); }; this.mediaRecorder.onerror = (event) => { // 当发生错误时调用 // 错误信息可以通过 event.error 获取 console.log(event); console.log(event.error); }; console.log("录音中..."); }catch (e) { tipsDialogGcomponent.dialogShow({ type: 1, content: e, timeOut: 3000 }); } }, // 关闭窗体 testCloseFrame: function () { try{ window.controllers.closeFrame(1); }catch (e) { tipsDialogGcomponent.dialogShow({ type: 1, content: e, timeOut: 3000 }); } }, }, created: function () { }, //el挂载完后调用 mounted: function () { var _this=this; try{ navigator.mediaDevices.ondevicechange = () => { console.log('设备发生变化'); tipsDialogGcomponent.dialogShow({ type: 3, content: "设备发生变化!!!", timeOut: 3000 }); }; navigator.mediaDevices.enumerateDevices().then(devices => { const audioInputs = devices.filter(device => device.kind === 'audioinput'); console.log("当前可用的麦克风设备:"); for(let device of audioInputs) { console.log(device); console.log(device.label+"=>"+device.deviceId); } if(audioInputs.length==0){ console.log("没有可用的设备!"); }else{ console.log("检测麦克风已接入。"); } }); navigator.mediaDevices.getUserMedia(_this.constraints).then( stream => { _this.mediaRecorder = new MediaRecorder(stream); /** * // 设置输出格式为音频 * mediaRecorder.mimeType = 'audio/webm'; * // 设置音频比特率为128kbps * mediaRecorder.audioBitsPerSecond = 128000; * */ _this.recodChunks = []; tipsDialogGcomponent.dialogShow({ type: 3, content: "授权成功!", timeOut: 3000 }); /** 未验证 来源:https://zhuanlan.zhihu.com/p/26536898?from_voters_page=true */ var audioContext = window.AudioContext || window.webkitAudioContext; var context = new audioContext(); //创建一个管理、播放声音的对象 var liveSource = context.createMediaStreamSource(stream); //将麦克风的声音输入这个对象 var levelChecker = context.createScriptProcessor(4096,1,1); //创建一个音频分析对象,采样的缓冲区大小为4096,输入和输出都是单声道 liveSource.connect(levelChecker); //将该分析对象与麦克风音频进行连接 levelChecker.connect(context.destination); // 此举无甚效果,仅仅是因为解决 Chrome 自身的 bug levelChecker.onaudioprocess = function(e) { //开始处理音频 var buffer = e.inputBuffer.getChannelData(0); //获得缓冲区的输入音频,转换为包含了PCM通道数据的32位浮点数组 //创建变量并迭代来获取最大的音量值 var maxVal = 0; for (var i = 0; i < buffer.length; i++) { if (maxVal < buffer[i]) { maxVal = buffer[i]; } } //显示音量值 console.log("您的音量值:"+Math.round(maxVal*100)); if(maxVal>.5){ console.log("您的声音太响了!!"); //当音量值大于0.5时,显示“声音太响”字样,并断开音频连接 console.log("您的声音太响了!!"); liveSource.disconnect(levelChecker); } }; }, () => { console.error("授权失败!"); tipsDialogGcomponent.dialogShow({ type: 3, content: "授权失败!", timeOut: 3000 }); } ); }catch (e) { tipsDialogGcomponent.dialogShow({ type: 1, content: e, timeOut: 3000 }); } /** 原始数据,来源:https://www.cnblogs.com/qianxiaox/p/14973950.html * 也可以参照:https://imangodoc.com/K4VjVUvb.html if (navigator.mediaDevices.getUserMedia) { const constraints = {audio: true}; navigator.mediaDevices.getUserMedia(constraints).then( stream => { console.log("授权成功!"); const recordBtn = document.querySelector(".record-btn"); const mediaRecorder = new MediaRecorder(stream); var chunks = []; recordBtn.onclick = () => { if (mediaRecorder.state === "recording") { mediaRecorder.stop(); console.log(chunks) mediaRecorder.onstop = e => { var blob = new Blob(chunks, {type: "audio/ogg; codecs=opus"}); chunks = []; var audioURL = window.URL.createObjectURL(blob); const audioSrc = document.querySelector(".audio-player"); audioSrc.src = audioURL; }; recordBtn.textContent = "record"; console.log("录音结束"); } else { mediaRecorder.start(); mediaRecorder.ondataavailable = function (e) { chunks.push(e.data); }; console.log(chunks) console.log("录音中..."); recordBtn.textContent = "stop"; } console.log("录音器状态:", mediaRecorder.state); }; }, () => { console.error("授权失败!"); } ); } else { console.error("浏览器不支持 getUserMedia"); } */ } }); </script> </body> </html>
后台接收:
@RequestMapping(value = "/upLoadFiles", method = RequestMethod.POST)public String upLoadFiles(HttpServletRequest req, MultipartFile[] uploadFiles) { ApiResponseV1<List<String>> _data = new ApiResponseV1<>(); try { *** } catch (Exception err) { *** } return ***; }
待续。。。