移动端H5录音组件开发(react版本)

基于AudioContextmediaDevices实现的原生js的录音功能

// recorder.js,这个是在网上找的,具体地址不记得了,这个存在一个问题就是,他分段之后会把audioData清空,导致最后结束的时候,audioData是一个空值,如果需要把整段的录音转化成一个音频文件,不考虑分片的话,可以把onaudioprocess里面的sendData注释掉,没错,我就是这样搞的,只需要一个完整的音频,如果需要分段传送,就把注释打开,然后作出对应的处理
const Recorder = function (stream, callback) {
  const sampleBits = 16; //输出采样数位 8, 16
  const sampleRate = 8000; //输出采样率
  const context = new AudioContext();
  const audioInput = context.createMediaStreamSource(stream);
  const recorder = context.createScriptProcessor(4096, 1, 1);
  const audioData = {
    size: 0, //录音文件长度
    buffer: [], //录音缓存
    inputSampleRate: 48000, //输入采样率
    inputSampleBits: 16, //输入采样数位 8, 16
    outputSampleRate: sampleRate, //输出采样数位
    oututSampleBits: sampleBits, //输出采样率
    clear: function () {
      this.buffer = [];
      this.size = 0;
    },
    input: function (data) {
      this.buffer.push(new Float32Array(data));
      this.size += data.length;
    },
    compress: function () { //合并压缩
      //合并
      const data = new Float32Array(this.size);
      let offset = 0;
      for (let i = 0; i < this.buffer.length; i++) {
        data.set(this.buffer[i], offset);
        offset += this.buffer[i].length;
      }
      //压缩
      const compression = parseInt(this.inputSampleRate / this.outputSampleRate);
      const length = data.length / compression;
      const result = new Float32Array(length);
      let index = 0,
        j = 0;
      while (index < length) {
        result[index] = data[j];
        j += compression;
        index++;
      }
      return result;
    },
    encodePCM: function () { //这里不对采集到的数据进行其他格式处理,如有需要均交给服务器端处理。
      const sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
      const sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
      const bytes = this.compress();
      const dataLength = bytes.length * (sampleBits / 8);
      const buffer = new ArrayBuffer(dataLength);
      const data = new DataView(buffer);
      let offset = 0;
      for (let i = 0; i < bytes.length; i++, offset += 2) {
        const s = Math.max(-1, Math.min(1, bytes[i]));
        data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
      }
      return new Blob([data], { ‘type‘: ‘audio/pcm‘ });
    }
  };

  const sendData = function () { //对以获取的数据进行处理(分包)
    const reader = new FileReader();
    reader.onload = e => {
      const outbuffer = e.target.result;
      // callback && callback(outbuffer);
      const arr = new Int8Array(outbuffer);
      if (arr.length > 0) {
        let tmparr = new Int8Array(1024);
        let j = 0;
        for (let i = 0; i < arr.byteLength; i++) {
          tmparr[j++] = arr[i];
          if (((i + 1) % 1024) == 0) {
            callback && callback(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) {
            callback && callback(tmparr);
          }
        }
      }
    };
    reader.readAsArrayBuffer(audioData.encodePCM());
    audioData.clear();//每次发送完成则清理掉旧数据
  };

  this.start = function () {
    audioInput.connect(recorder);
    recorder.connect(context.destination);
  }

  this.stop = function () {
    recorder.disconnect();
  }

  this.getBlob = function () {
    return audioData.encodePCM();
  }

  this.clear = function () {
    audioData.clear();
  }

  recorder.onaudioprocess = function (e) {
    const inputBuffer = e.inputBuffer.getChannelData(0);
    audioData.input(inputBuffer);
    // sendData();
  }
}

export default Recorder;
/**
 * 录音组件
 */
// RecordItem.js
import React, { Component } from ‘react‘;
import { Icon } from ‘antd‘;
import { Toast } from ‘antd-mobile‘;

import Recorder from ‘./Recorder‘;

import ‘./RecordItem.less‘;

class RecordItem extends Component {

  state = {
    isRecording: false, // 是否正在录音
  }

  timer = null; // 判断长按的定时器

  handleTouchStart = () => {
    this.timer = setTimeout(() => {
      this.recorder.start();
      this.setState({
        isRecording: true
      });
    }, 300);
  }

  handleTouchEnd = () => {
    if (this.timer) {
      clearTimeout(this.timer);
    }
    this.recorder.stop();
    this.setState({
      isRecording: false
    }, () => {
      const { onEnd } = this.props;
      onEnd && onEnd(this.recorder.getBlob());
    });
  }

  // 处理录音的回调
  handleMsg = (data) => {
    const { onProgress } = this.props;
    onProgress && onProgress(data);
  }

  componentDidMount() {
    const constraints = { audio: true };
    navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
      this.recorder = new Recorder(stream, this.handleMsg);
    }, err => {
      switch (err.message || err.name) {
        case ‘PERMISSION_DENIED‘:
        case ‘PermissionDeniedError‘:
          Toast.info(‘用户拒绝提供信息。‘);
          break;
        case ‘NOT_SUPPORTED_ERROR‘:
        case ‘NotSupportedError‘:
          Toast.info(‘浏览器不支持硬件设备。‘);
          break;
        case ‘MANDATORY_UNSATISFIED_ERROR‘:
        case ‘MandatoryUnsatisfiedError‘:
          Toast.info(‘无法发现指定的硬件设备。‘);
          break;
        default:
          Toast.info(‘无法打开麦克风。异常信息:‘ + (err.code || err.name));
          break;
      }
    });
    // this.recorder = new Recorder({
    //   callback: this.handleMsg
    // });
  }

  render() {
    const { isRecording } = this.state;
    return (
      <div
        className={`RecordItem ${isRecording ? ‘recording‘ : ‘‘}`}
        onTouchStart={this.handleTouchStart}
        onTouchEnd={this.handleTouchEnd}
      >
        <Icon type="audio" />
      </div>
    );
  }
}

export default RecordItem;
// RecordItem.less
.RecordItem {
  color: #333333;

  &.recording {
    color: #10C0DC;
  }

  .anticon {
    font-size: 1.5rem;
  }
}

移动端H5录音组件开发(react版本)

上一篇:linux键值到Android键值的转换与自定义


下一篇:Appium问题解决方案(9)- Original error: Failed to launch Appium Settings app: Condition unmet after 5090 ms