amr-recorder
Version:
amr 音频录制与播放(微信语音所用的格式)
237 lines (212 loc) • 8.22 kB
JavaScript
/**
* @file 公共的 Web Audio API Context
* @author BenzLeung(https://github.com/BenzLeung)
* @date 2017/11/12
* Created by JetBrains PhpStorm.
*
* 每位工程师都有保持代码优雅的义务
* each engineer has a duty to keep the code elegant
*/
import Recorder from 'benz-recorderjs';
const AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
let ctx = null;
let isSupport = true;
if (!AudioContext) {
isSupport = false;
console.warn('Web Audio API is Unsupported.');
}
export default class RecorderControl {
_recorderStream = null;
_recorderStreamSourceNode = null;
_recorder = null;
_isRecording = false;
_curSourceNode = null;
playPcm (samples, sampleRate, onEnded, startPos) {
if (!ctx || ctx.state === 'closed') {
ctx = new AudioContext();
}
if (ctx.state === 'interrupted' || ctx.state === 'suspended') {
ctx.resume();
}
sampleRate = sampleRate || 8000;
this.stopPcm();
let _samples = (startPos && startPos > 0.001) ? (
// 根据开始位置(秒数)截取播放采样
samples.slice(sampleRate * startPos)
) : samples;
if (!_samples.length) {
return onEnded();
}
let buffer, channelBuffer;
this._curSourceNode = ctx['createBufferSource']();
try {
buffer = ctx['createBuffer'](1, _samples.length, sampleRate);
} catch (e) {
// iOS 不支持 22050 以下的采样率,于是先提升采样率,然后用慢速播放
if (sampleRate < 11025) {
/*buffer = ctx['createBuffer'](1, _samples.length * 3, sampleRate * 3);
_samples = this._increaseSampleRate(_samples, 3);*/
buffer = ctx['createBuffer'](1, _samples.length, sampleRate * 4);
this._curSourceNode['playbackRate'].value = 0.25;
} else {
/*buffer = ctx['createBuffer'](1, _samples.length * 2, sampleRate * 2);
_samples = this._increaseSampleRate(_samples, 2);*/
buffer = ctx['createBuffer'](1, _samples.length, sampleRate * 2);
this._curSourceNode['playbackRate'].value = 0.5;
}
}
if (buffer['copyToChannel']) {
buffer['copyToChannel'](_samples, 0, 0)
} else {
channelBuffer = buffer['getChannelData'](0);
channelBuffer.set(_samples);
}
this._curSourceNode['buffer'] = buffer;
this._curSourceNode['loop'] = false;
this._curSourceNode['connect'](ctx['destination']);
this._curSourceNode.onended = onEnded;
this._curSourceNode.start();
}
stopPcm() {
if (this._curSourceNode) {
this._curSourceNode.stop();
this._curSourceNode = null;
}
}
stopPcmSilently() {
this._curSourceNode.onended = null;
this.stopPcm();
}
initRecorder() {
return new Promise((resolve, reject) => {
let s = (stream) => {
this._recorderStream = stream;
this._recorderStreamSourceNode = ctx.createMediaStreamSource(stream);
this._recorder = new Recorder(this._recorderStreamSourceNode);
this._isRecording = false;
resolve();
};
let j = (e) => {
reject(e);
};
if (!this._recorder) {
if (window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia) {
window.navigator.mediaDevices.getUserMedia({audio: true}).then(s).catch(j);
} else if (window.navigator.getUserMedia) {
window.navigator.getUserMedia({audio: true}, s, j);
} else {
j();
}
} else {
resolve();
}
});
}
isRecording() {
return this._recorder && this._isRecording;
}
startRecord() {
if (this._recorder) {
this._recorder.clear();
this._recorder.record();
this._isRecording = true;
}
}
stopRecord() {
if (this._recorder) {
this._recorder.stop();
this._isRecording = false;
}
}
generateRecordSamples() {
return new Promise((resolve) => {
if (this._recorder) {
this._recorder.getBuffer((buffers) => {
resolve(buffers[0]);
});
}
});
}
releaseRecord() {
if (this._recorderStream && this._recorderStream.getTracks) {
this._recorderStream.getTracks().forEach((track) => {
track.stop();
});
this._recorderStream = null;
}
if (this._recorder) {
this._recorder.release();
this._recorder = null;
}
}
static isPlaySupported() {
return isSupport;
}
static isRecordSupported() {
return !!(window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia || window.navigator.getUserMedia);
}
static getCtxSampleRate() {
return ctx && ctx.sampleRate || 0;
}
static getCtxTime() {
return ctx && ctx.currentTime || 0;
}
static decodeAudioArrayBufferByContext(array) {
return new Promise((resolve, reject) => {
ctx['decodeAudioData'](array, (audioBuf) => {
// 把多声道音频 mix 成单声道
const numberOfChannels = audioBuf.numberOfChannels;
let dest = new Float32Array(audioBuf.length);
switch (numberOfChannels) {
default:
case 1: {
dest = audioBuf.getChannelData(0);
break;
}
case 2: {
const left = audioBuf.getChannelData(0);
const right = audioBuf.getChannelData(1);
for (let i = 0, l = dest.length; i < l; i++) {
dest[i] = .5 * (left[i] + right[i])
}
break;
}
case 4: {
const left = audioBuf.getChannelData(0);
const right = audioBuf.getChannelData(1);
const sLeft = audioBuf.getChannelData(2);
const sRight = audioBuf.getChannelData(3);
for (let i = 0, l = dest.length; i < l; i++) {
dest[i] = .25 * (left[i] + right[i] + sLeft[i] + sRight[i])
}
break;
}
case 6: {
const left = audioBuf.getChannelData(0);
const right = audioBuf.getChannelData(1);
const center = audioBuf.getChannelData(2);
const sLeft = audioBuf.getChannelData(4);
const sRight = audioBuf.getChannelData(5);
for (let i = 0, l = dest.length; i < l; i++) {
dest[i] = 0.7071 * (left[i] + right[i]) + center[i] + 0.5 * (sLeft[i] + sRight[i])
}
break;
}
}
resolve(dest);
}, reject);
});
}
/*
static _increaseSampleRate(samples, multiple) {
let sampleLen = samples.length;
let newSamples = new Float32Array(sampleLen * multiple);
for (let i = 0; i < sampleLen; i ++) {
for (let j = 0; j < multiple; j ++) {
newSamples[i * multiple + j] = samples[i];
}
}
return newSamples;
};
*/
}