美文网首页
【前端】vue+recorder实现录音功能

【前端】vue+recorder实现录音功能

作者: 北极星丶超帅的 | 来源:发表于2019-11-28 11:47 被阅读0次

    重要的事情说三遍:要在https下才能实现! 要在https下才能实现!! 要在https下才能实现!!!
    我当时就是忽略了这个点折腾了很久,最后根据这篇文章:Vue.js实战——封装浏览器录音组件_6,实现了录音功能。
    因为本地是http,不能看到效果,建议使用【whistle】强大的web调试代理工具来进行代理本地,随时查看修改的效果

    因为原文章不能抛出录音失败的错误,所以在此基础修改下

    //recorder.js
    export default class Recorder {
      constructor(stream, config) {
        //兼容
        window.URL = window.URL || window.webkitURL;
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    
        config = config || {};
        config.sampleBits = config.sampleBits || 16;   //采样数位 8, 16
        config.sampleRate = config.sampleRate || 8000; //采样率(1/6 44100)
    
        this.context = new (window.webkitAudioContext || window.AudioContext)();
        this.audioInput = this.context.createMediaStreamSource(stream);
        this.createScript = this.context.createScriptProcessor || this.context.createJavaScriptNode;
        this.recorder = this.createScript.apply(this.context, [4096, 1, 1]);
    
        this.audioData = {
          size: 0,          //录音文件长度
          buffer: [],    //录音缓存
          inputSampleRate: this.context.sampleRate,   //输入采样率
          inputSampleBits: 16,     //输入采样数位 8, 16
          outputSampleRate: config.sampleRate,   //输出采样率
          oututSampleBits: config.sampleBits,       //输出采样数位 8, 16
          input: function (data) {
            this.buffer.push(new Float32Array(data));
            this.size += data.length;
          },
          compress: function () { //合并压缩
            //合并
            let 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;
            }
            //压缩
            let compression = parseInt(this.inputSampleRate / this.outputSampleRate);
            let length = data.length / compression;
            let result = new Float32Array(length);
            let index = 0, j = 0;
            while (index < length) {
              result[index] = data[j];
              j += compression;
              index++;
            }
            return result;
          },
          encodeWAV: function () {
            let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
            let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
            let bytes = this.compress();
            let dataLength = bytes.length * (sampleBits / 8);
            let buffer = new ArrayBuffer(44 + dataLength);
            let data = new DataView(buffer);
    
            let channelCount = 1;//单声道
            let offset = 0;
    
            let writeString = function (str) {
              for (let i = 0; i < str.length; i++) {
                data.setUint8(offset + i, str.charCodeAt(i));
              }
            };
    
            // 资源交换文件标识符
            writeString('RIFF');
            offset += 4;
            // 下个地址开始到文件尾总字节数,即文件大小-8
            data.setUint32(offset, 36 + dataLength, true);
            offset += 4;
            // WAV文件标志
            writeString('WAVE');
            offset += 4;
            // 波形格式标志
            writeString('fmt ');
            offset += 4;
            // 过滤字节,一般为 0x10 = 16
            data.setUint32(offset, 16, true);
            offset += 4;
            // 格式类别 (PCM形式采样数据)
            data.setUint16(offset, 1, true);
            offset += 2;
            // 通道数
            data.setUint16(offset, channelCount, true);
            offset += 2;
            // 采样率,每秒样本数,表示每个通道的播放速度
            data.setUint32(offset, sampleRate, true);
            offset += 4;
            // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
            data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true);
            offset += 4;
            // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
            data.setUint16(offset, channelCount * (sampleBits / 8), true);
            offset += 2;
            // 每样本数据位数
            data.setUint16(offset, sampleBits, true);
            offset += 2;
            // 数据标识符
            writeString('data');
            offset += 4;
            // 采样数据总数,即数据总大小-44
            data.setUint32(offset, dataLength, true);
            offset += 4;
            // 写入采样数据
            if (sampleBits === 8) {
              for (let i = 0; i < bytes.length; i++ , offset++) {
                let s = Math.max(-1, Math.min(1, bytes[i]));
                let val = s < 0 ? s * 0x8000 : s * 0x7FFF;
                val = parseInt(255 / (65535 / (val + 32768)));
                data.setInt8(offset, val, true);
              }
            } else {
              for (let i = 0; i < bytes.length; i++ , offset += 2) {
                let 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/wav' });
          }
        };
      }
    
      //开始录音
      start () {
        this.audioInput.connect(this.recorder);
        this.recorder.connect(this.context.destination);
    
        //音频采集
        let self = this;
        this.recorder.onaudioprocess = function (e) {
          self.audioData.input(e.inputBuffer.getChannelData(0));
        };
      };
    
      //停止
      stop () {
        this.recorder.disconnect();
      };
    
      //获取音频文件
      getBlob () {
        this.stop();
        return this.audioData.encodeWAV();
      };
    
      //回放
      play (audio) {
        audio.src = window.URL.createObjectURL(this.getBlob());
      };
    
      //清理缓存的录音数据
      clear (audio) {
        this.audioData.buffer = [];
        this.audioData.size = 0;
        audio.src = ''
      };
    
      static checkError (e) {
        const { name } = e;
        let errorMsg = ''
        switch (name) {
          case 'AbortError': errorMsg = '录音设备无法被使用'; break;
          case 'NotAllowedError': errorMsg = '用户已禁止网页调用录音设备'; break;
          case 'PermissionDeniedError': errorMsg = '用户已禁止网页调用录音设备'; break;     // 用户拒绝
          case 'NotFoundError': errorMsg = '录音设备未找到'; break;
          case 'DevicesNotFoundError': errorMsg = '录音设备未找到'; break;
          case 'NotReadableError': errorMsg = '录音设备无法使用'; break;
          case 'NotSupportedError': errorMsg = '不支持录音功能'; break;
          case 'MandatoryUnsatisfiedError': errorMsg = '无法发现指定的硬件设备'; break;
          default: errorMsg = '录音调用错误'; break;
        }
        return { error: errorMsg }
      };
    
      static get (callback, config) {
        if (callback) {
          if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then((stream) => {
              let rec = new Recorder(stream, config);
              callback(rec);
            }).catch((e) => {
              callback(Recorder.checkError(e));
            })
          } else {
            navigator.getUserMedia({ audio: true, video: false }).then((stream) => {
              let rec = new Recorder(stream, config);
              callback(rec);
            }).catch((e) => {
              // Recorder.checkError(e)
              callback(Recorder.checkError(e));
            })
          }
        }
      };
    }
    
    //record-sdk.js
    import Recorder from "./recorder";
    export default class Record {
      startRecord (param) {
        let self = this;
        try {
          Recorder.get(rec => {
            if (rec.error) return param.error(rec.error);
            self.recorder = rec;
            self.recorder.start();
            param.success("开始录音");
          })
        } catch (e) {
          param.error("开始录音失败" + e);
        }
      }
    
      stopRecord (param) {
        let self = this;
        try {
          let blobData = self.recorder.getBlob();
          param.success(blobData);
        } catch (e) {
          param.error("结束录音失败" + e);
        }
      }
    
      play (audio) {
        let self = this;
        try {
          self.recorder.play(audio);
        } catch (e) {
          console.error("录音播放失败" + e);
        }
      }
    
      clear (audio) {
        let self = this;
        try {
          self.recorder.clear(audio);
        } catch (e) {
          console.error("清空录音失败" + e);
        }
      }
    }
    
    //voice.vue
    ...
      <div class="record">
          <h1>{{tipMsg}}</h1>
          <button @click="onStartVoice">开始</button>
          <button @click="onEndVoice">结束</button>
          <button @click="onPlayAudio">播放</button>
          <div class="record-play"
               v-show="isFinished">
            <h2>Current voice player is:</h2>
            <audio id="audioVoice"
                   controls
                   autoplay></audio>
          </div>
        </div>
    ...
    <script>
    import Record from '@/plugins/recorder/record-sdk';
    export default {
      data () {
        return {
          isVoice: false,
          isFinished: false,
          tipMsg: '录音',
          audio: "",
          recorder: new Record()
        }
      },
     methods: {
         // 开始录音
        onStartVoice () {
          this.onStopAudio()
          this.isFinished = false;
          this.recorder.startRecord({
            success: res => {
              this.isVoice = true
            },
            error: e => {
              this.isVoice = false
              this.$toast(e)
            }
          });
        },
    
        // 结束录音
        onEndVoice () {
          this.isFinished = false;
          this.recorder.stopRecord({
            success: res => {
              this.isVoice = false
              //此处可以获取音频源文件(res),用于上传等操作
              console.log('音频源文件', res)
            },
            error: e => {
              this.isVoice = false
            }
          });
        },
    
        // 播放录音
        onPlayAudio () {
          this.isVoice = false
          this.isFinished = true;
          this.audio = document.getElementById("audioVoice");
          this.recorder.play(this.audio);
        },
    
        // 停止播放录音
        onStopAudio () {
          this.recorder.clear(this.audio);
        }
      }
    }
    </script>
    

    因为项目可能要实现语音录菜功能,便提前看下h5是否可以录音,如果后期真要实现语音录菜功能,,再来更新

    相关文章

      网友评论

          本文标题:【前端】vue+recorder实现录音功能

          本文链接:https://www.haomeiwen.com/subject/hvovwctx.html