Electron + react 实现录音分段播放

        背景: 临近端午想着没事周末加个班换几天调休,领导就给我调到一个比较忙的项目中写一个录音组件,直接 996

        需求:整体需求 做一个录音组件,可以录音,可以播放完整录音(我的活),可以针对某个问题进行标记(我的活),可以播放标记录音(我的活)

录音的功能我猜想就还是 webRtc 然后组内架构提供了一个 npm依赖 方法都有 主要写写样式

https://www.npmjs.com/package/recorder-core

录音播放不用说了就是一个 audio标签 分段合并播放怎么搞呢?

两种方案 1.使用webaudio API 合并 一个音频 2.通过数据seek 到一个播放器的多个不同位置播放

一.web audio API

  1. 请求录音文件数据
/**
 *请求录音文件
 *@param url 录音文件地址
 */
export function getAudioArraybuffer(url: string): Promise<ArrayBuffer> {
  return request.get(url, { responseType: 'arraybuffer' }).then((response) => response.data);
}

这里使用axios 封装的请求 返回的数据是 arraybuffer 格式

2.创建音频上下文 想操作音频需使用这个 这里没做兼容处理 

const audioContext = new window.AudioContext();
//解码变成audioBuffer
  function getDecodedAudioData(): Promise<AudioBuffer> {
    return audioContext.decodeAudioData(audioData);
  }

3.获取录音片段 声道处理那块看着没什么用 必须要有 不然放不出声

function getSegment(decodedData: AudioBuffer): AudioBuffer {
    const duration = endTime - startTime;
    const slicedBuffer: AudioBuffer = audioContext.createBuffer(
      decodedData.numberOfChannels,
      duration * decodedData.sampleRate,
      decodedData.sampleRate,
    );
    const startOffset = startTime;
    const endOffset = endTime;
    for (let channel = 0; channel < decodedData.numberOfChannels; channel++) {
      // 声道处理
      const channelData = decodedData.getChannelData(channel);
      const slicedChannelData = slicedBuffer.getChannelData(channel);
      const startSample = Math.floor(startOffset * decodedData.sampleRate);
      const endSample = Math.min(Math.ceil(endOffset * decodedData.sampleRate), decodedData.length);
      for (let i = startSample; i < endSample; i++) {
        slicedChannelData[i - startSample] = channelData[i];
      }
    }
    return slicedBuffer;
  }

到这里基本就可以放 网上搜的 可以直接用 sourceNode 再 链接 设备 可以直接放 但是api 配合样式比较复杂 想着是否可以 直接把这个资源给audio 标签帮我播放 我直接用 audio 标签的api。

结果发现audio 标签 必须是url 的 不能是 流格式的 

那就转格式

4.AudioBuffer 转 Blob

/**
 * audioBuffer转成Blob
 * @param audioBuffer
 * @returns Blob
 */

export function audioBufferToBlob(audioBuffer: AudioBuffer): Blob {
  const { numberOfChannels } = audioBuffer;
  const { sampleRate } = audioBuffer;
  const channelData: Float32Array[] = [];

  for (let channel = 0; channel < numberOfChannels; channel++) {
    channelData.push(audioBuffer.getChannelData(channel));
  }

  const interleavedData = interleaveChannels(channelData);
  const buffer = createWavBuffer(interleavedData, sampleRate, numberOfChannels);

  const blob = new Blob([buffer], { type: 'audio/wav' });
  return blob;
}
/**
 * 转 Float32Array
 * @param channels
 * @returns
 */

function interleaveChannels(channels: Float32Array[]): Float32Array {
  const channelCount = channels.length;
  const frameCount = channels[0].length;
  const result = new Float32Array(frameCount * channelCount);

  for (let i = 0; i < frameCount; i++) {
    for (let channel = 0; channel < channelCount; channel++) {
      result[i * channelCount + channel] = channels[channel][i];
    }
  }

  return result;
}
/**
 * 转WAV文件类型 基本支持web audio api 都支持WAV
 * @param audioData
 * @param sampleRate
 * @param numChannels
 * @returns
 */

export function createWavBuffer(audioData: Float32Array, sampleRate: number, numChannels: number): ArrayBuffer {
  const buffer = new ArrayBuffer(44 + audioData.length * 2);
  const view = new DataView(buffer);

  // WAV header
  writeString(view, 0, 'RIFF'); // ChunkID
  view.setUint32(4, 36 + audioData.length * 2, true); // ChunkSize
  writeString(view, 8, 'WAVE'); // Format
  writeString(view, 12, 'fmt '); // Subchunk1ID
  view.setUint32(16, 16, true); // Subchunk1Size
  view.setUint16(20, 1, true); // AudioFormat (PCM)
  view.setUint16(22, numChannels, true); // NumChannels
  view.setUint32(24, sampleRate, true); // SampleRate
  view.setUint32(28, sampleRate * numChannels * 2, true); // ByteRate
  view.setUint16(32, numChannels * 2, true); // BlockAlign
  view.setUint16(34, 16, true); // BitsPerSample
  writeString(view, 36, 'data'); // Subchunk2ID
  view.setUint32(40, audioData.length * 2, true); // Subchunk2Size

  // Audio data
  let offset = 44;
  // eslint-disable-next-line no-bitwise
  const volume = 1 << 15;
  for (let i = 0; i < audioData.length; i++, offset += 2) {
    view.setInt16(offset, audioData[i] * volume, true);
  }

  return buffer;
}
export function writeString(view: DataView, offset: number, string: string): void {
  for (let i = 0; i < string.length; i++) {
    view.setUint8(offset + i, string.charCodeAt(i));
  }
}

 最后 通过 URL.createObjectURL(audioBlob) 创建一个url 就可以 给audio 标签 使用 

但是 合并 之后音频 音质 不行 但是也能用

二. seek 到播放器指定时间段 

正常的标签播放 直接调用  audio.currentTime = 第几秒;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值