参考 :
http://air.ghost.io/recording-to-an-audio-file-using-html5-and-js/ (html5 基础)
https://github.com/muaz-khan/RecordRTC
https://github.com/webpack-contrib/worker-loader
https://github.com/webpack-contrib/file-loader
https://github.com/muaz-khan/RecordRTC/issues/31 (wav 太大的解决方案)
https://github.com/muaz-khan/Ffmpeg.js/blob/master/wav-to-ogg.html#L209 ( ftmpeg wav to ogg 压缩方案 )
http://audior.ec/blog/recording-mp3-using-only-html5-and-javascript-recordmp3-js/ (wav to mp3 压缩方案, 超小, 声音也差.., 可以使用 lamejs 比较新)
使用插件
npm install recordrtc
npm install --save-dev file-loader
npm install --save-dev worker-loader
要使用 file,worker loading 需要在 tsconfig.app.json 加上 "node"
"compilerOptions": {"types": [ "node" ] },
startRecord() { navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { let recordRTC = RecordRTC(stream, { type: 'audio', recorderType: RecordRTC.StereoAudioRecorder, disableLogs: true //leftChannel: true, //numberOfAudioChannels: 1 // or leftChannel:true }); recordRTC.startRecording(); this.recordRTC = recordRTC; this.stream = stream; }).catch(() => { console.log('user no allow'); }); }
通过 html5 的 navigator.mediaDevices.getUserMedia 获取用户的 permission, 然后就可以获取到声音了
通过 RecordRTC 来做录制. 如果是要 to mp3 的话, 可以选择单声道 (不过我试过 to mp3 效果声音效果不好, 所以最好用了 .ogg)
stop() { let recordRTC = this.recordRTC; recordRTC.stopRecording(() => { // 关闭 html5 navigator.mediaDevices.getUserMedia var track = this.stream.getTracks()[0]; // 0 是因为我们只有一个 track, 是可以 track 2 个的, 声音和影像 track.stop(); // ff 可以 skip 掉 fileReader, 直接拿 recordRTC.blob new File 也可以, 因为 ff 返回的就是 ogg 了 // 下面是针对 chrome 返回的是 wav, 很大, 所以使用 ffmpeg 压缩去 ogg let fileReader = new FileReader(); fileReader.onload = () => { recordRTC.clearRecordedData(); // reader 读出来后就可以释放 recordRTC 了. //压缩是很慢的, 所以要另开一个线程 var WorkerConstructor = require("worker-loader!./worker2.js"); var worker = new WorkerConstructor(); worker.onmessage = (e) => { var result = e.data[0]; // edge 不可以跑哦, 只有 chrome and ff ok var blob = new File([result.data], 'whatever.ogg', { type: 'audio/ogg' }); let formData = new FormData(); formData.append('uploadFile', blob, 'whatever.ogg'); // 上传 this.http.post('/api/uploadFile', formData).subscribe(() => { console.log('done'); }); // 做成 audio let audio = new Audio(); audio.controls = true; let url = window.URL.createObjectURL(blob); audio.src = url; let recordMp3Container = document.getElementById('recordmp3-container'); recordMp3Container.appendChild(audio); audio.play(); // 关闭 worker worker.terminate(); }; worker.postMessage(fileReader.result); }; fileReader.readAsArrayBuffer(recordRTC.blob); }); }
worker2.js
let url = require("file-loader!./ffmpeg_asm.js"); self.importScripts(url); let print = () => { }; self.onmessage = (event) => { let blob = event.data; let result = ffmpeg_run({ print: print, printErr: print, files: [ { data: new Uint8Array(blob), name: "whatever.wav" } ], arguments: '-i whatever.wav -c:a vorbis -b:a 4800k -strict experimental output.ogg'.split(' ') }); self.postMessage(result); };
关键就是调用了 ffmpeg_run
https://archive.org/download/ffmpeg_asm/ffmpeg_asm.js
这个转换器 18mb 非常大哦. 不过只要下载一次, 所以 ok 啦.
wav to mp3
步骤和上面一下, 使用单声道.
然后 worker 用下面这个
//var url = require("file-loader!./lame.all.js"); var url = require("file-loader!lamejs/lame.all.js"); self.importScripts(url); self.onmessage = function (e) { let stream = e.data.stream; let streamArray = new Int16Array(stream); streamArray = streamArray.slice(50); //去掉一开始的杂音 let buffer = []; let mp3encoder = new lamejs.Mp3Encoder(1, 44100, 128); let mp3Data = mp3encoder.encodeBuffer(streamArray); buffer.push(mp3Data); mp3Data = mp3encoder.flush(); //获取最后一个 part buffer.push(mp3Data); let blob = new Blob(buffer, { type: 'audio/mp3' }); var workerResult = { stream: blob }; self.postMessage(workerResult); }
调用 lamejs 去压缩.
最后想说的是... RecordRTC 这个 plugin 是使用原生 MediaRecorder 接口来实现的
MediaRecorder 只有 chrome and firefox 支持. chrome 输出的格式是 webm, firefox 则是 ogg, RecordRTC 做了些修改输出的是 wav
以上 3 种格式 ios safari 都不支持, 它只支持 mp3... 伤感...
recordRTC
import { HttpClient } from '@angular/common/http'; import { ChangeDetectionStrategy, Component } from '@angular/core'; import * as RecordRTC from 'recordrtc'; declare let MediaRecorder: any; function invokeSaveAsDialog(file, fileName) { if (!file) { throw 'Blob object is required.'; } if (!file.type) { try { file.type = 'video/webm'; } catch (e) { } } var fileExtension = (file.type || 'video/webm').split('/')[1]; if (fileName && fileName.indexOf('.') !== -1) { var splitted = fileName.split('.'); fileName = splitted[0]; fileExtension = splitted[1]; } var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { return navigator.msSaveOrOpenBlob(file, fileFullName); } else if (typeof navigator.msSaveBlob !== 'undefined') { return navigator.msSaveBlob(file, fileFullName); } var hyperlink = document.createElement('a'); hyperlink.href = URL.createObjectURL(file); hyperlink.download = fileFullName; (document.body || document.documentElement).appendChild(hyperlink); if (typeof hyperlink.click === 'function') { hyperlink.click(); } else { hyperlink.target = '_blank'; hyperlink.dispatchEvent(new MouseEvent('click', { view: window, bubbles: true, cancelable: true })); } URL.revokeObjectURL(hyperlink.href); } let mp3 = true; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor( private http: HttpClient ) { } title = 'app'; recordRTC: any; stop() { let recordRTC = this.recordRTC; recordRTC.stopRecording(() => { // 关闭 html5 navigator.mediaDevices.getUserMedia var track = this.stream.getTracks()[0]; // 0 是因为我们只有一个 track, 是可以 track 2 个的, 声音和影像 track.stop(); // ff 可以 skip 掉 fileReader, 直接拿 recordRTC.blob new File 也可以, 因为 ff 返回的就是 ogg 了 // 下面是针对 chrome 返回的是 wav, 很大, 所以使用 ffmpeg 压缩去 ogg let fileReader = new FileReader(); fileReader.onload = () => { recordRTC.clearRecordedData(); // reader 读出来后就可以释放 recordRTC 了. if (mp3) { //压缩是很慢的, 所以要另开一个线程 var WorkerConstructor = require("worker-loader!./worker.js"); var worker = new WorkerConstructor(); } else { //压缩是很慢的, 所以要另开一个线程 var WorkerConstructor = require("worker-loader!./worker2.js"); var worker = new WorkerConstructor(); } worker.onmessage = (e) => { if (mp3) { console.log(e); var result = e.data.stream; invokeSaveAsDialog(result, 'output.mp3'); // recordRTC.save('dadad'); } else { var result = e.data[0]; // edge 不可以跑哦, 只有 chrome and ff ok var blob = new File([result.data], 'output.ogg', { type: 'audio/ogg' }); let formData = new FormData(); formData.append('uploadFile', blob, 'output.ogg'); // 上传 // this.http.post('/api/uploadFile', formData).subscribe(() => { // console.log('done'); // }); invokeSaveAsDialog(blob, 'output.ogg'); // 做成 audio let audio = new Audio(); audio.controls = true; let url = window.URL.createObjectURL(blob); audio.src = url; let recordMp3Container = document.getElementById('recordmp3-container'); recordMp3Container.appendChild(audio); audio.play(); // 关闭 worker worker.terminate(); } }; worker.postMessage(fileReader.result); }; fileReader.readAsArrayBuffer(recordRTC.blob); }); } stream: any go() { navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { let obj = { type: 'audio', recorderType: RecordRTC.StereoAudioRecorder, disableLogs: true, // numberOfAudioChannels: 1 // or leftChannel:true } if(mp3) obj['leftChannel'] = true; let recordRTC = RecordRTC(stream,obj); recordRTC.startRecording(); this.recordRTC = recordRTC; this.stream = stream; }).catch(() => { console.log('user no allow'); }); } }
mp3
//var url = require("file-loader!./lame.all.js"); var url = require("file-loader!lamejs/lame.all.js"); self.importScripts(url); self.onmessage = function (e) { let stream = e.data; let streamArray = new Int16Array(stream); streamArray = streamArray.slice(50); //去掉一开始的杂音 let buffer = []; let mp3encoder = new lamejs.Mp3Encoder(1, 44100, 128); let mp3Data = mp3encoder.encodeBuffer(streamArray); buffer.push(mp3Data); mp3Data = mp3encoder.flush(); //获取最后一个 part buffer.push(mp3Data); let blob = new Blob(buffer, { type: 'audio/mp3' }); var workerResult = { stream: blob }; self.postMessage(workerResult); }
ogg
let url = require("file-loader!./ffmpeg_asm.js"); self.importScripts(url); let print = (text) => { console.log(text); }; self.onmessage = (event) => { console.log('worker e ', event); console.log('worker e.data ', event.data); let blob = event.data; let result = ffmpeg_run({ print: print, printErr: print, files: [ { data: new Uint8Array(blob), name: "whatever.wav" } ], arguments: '-i whatever.wav -c:a vorbis -b:a 4800k -strict experimental output.ogg'.split(' ') }); self.postMessage(result); };
.