web前端录制canvas视频和video的声音,并合并成一个文件进行下载

发布于:2025-02-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

 一、captureStream

captureStream‌是一个Web API方法,用于捕获指定元素的媒体流。该方法通常用于从<video>、<audio>或<canvas>元素中捕获实时视频流或音频流,以便进行进一步的处理,如直播、录制或分析‌。

captureStream()方法能够实时捕获视频流,适用于直播、实时监控等场景。

它返回一个MediaStream对象,该对象包含了捕获的视频流数据。相比其他视频捕获方法,captureStream()提供了较低的延迟和更好的灵活性‌。

1、captureStream() 返回的 MediaStream 对象为空

  • 视频元素尚未加载完成,导致无法捕获视频流。确保视频元素已经加载完成,可以通过监听 loadedmetadatacanplay 事件来确认。
  • 视频元素的源路径不正确或无法访问。检查视频元素的源路径是否正确,并确保视频文件可以正常访问。
const videoElement = document.createElement('video');
videoElement.src = 'video.mp4';

videoElement.addEventListener('loadedmetadata', () => {
  const mediaStream = videoElement.captureStream();
  if (mediaStream) {
    // 处理捕获的视频流
  } else {
    console.error('Failed to capture stream');
  }
});

2、处理 captureStream() 返回的 MediaStream 对象

  • 使用 MediaRecorder 将 MediaStream 对象录制为视频文件。
  • 使用 RTCPeerConnectionMediaStream 对象推送到 WebRTC 服务器,实现实时通信。
const mediaRecorder = new MediaRecorder(mediaStream);

mediaRecorder.ondataavailable = (event) => {
  if (event.data.size > 0) {
    // 处理录制的视频数据
  }
};
mediaRecorder.start();

3、AudioContext对象

如果浏览器不支持在video、audio对象上使用captureStream获取流数据,可以考虑使用AudioContext对象进行声音数据获取。

const audioContext = new (window.AudioContext || window.webkitAudioContext)()
const source = audioContext.createMediaElementSource(videoElement)
const destination = audioContext.createMediaStreamDestination()
source.connect(destination)
const audioStream = destination.stream

二、MediaStream()

构造函数**MediaStream()** 返回新建的 MediaStream 实例,该实例作为媒体流的内容的集合载体,其可能包含多个媒体数据轨,每个数据轨则由一个 MediaStreamTrack 对象表示。如果给出相应参数,在指定的数据轨则被添加到新的流中。否则,该流中不包含任何数据轨。

newStream = new MediaStream();
newStream = new MediaStream(stream);
newStream = new MediaStream(tracks[]);

参数:

stream:这是另一个 MediaStream 对象,其数据轨会被自动添加到新建的流中。且这些数据轨不会从原流中移除,即变成了两条流共享的数据。

tracks:这是 MediaStreamTrack 对象的 Array 类型的成员,代表了每一个添加到流中的数据轨。

返回值:

新建的 MediaStream 对象,会包含创建时已给的数据轨内容,若没有给定任何数据轨则内容为空。

合并录制的视频流和音频流

let canvas = document.getElementById('canvas')
let stream = canvas.captureStream(30)
let video = document.getElementById('video')
let audioStream = video.captureStream(30)


// 创建一个新的MediaStream,将视频跟音频流合并进去
let combinedStream = new MediaStream([ 
    ...stream.getTracks(), 
    ...audioStream.getAudioTracks()
]);

 

 三、录制Demo

// 判断是否是微信环境
export function isWeChatEnv() {
  const userAgent = navigator.userAgent.toLowerCase();
  return /micromessenger/.test(userAgent);
}
    let recorder = null; // 录制对象

    // 录制视频
    recordVideo(type) {
      
      console.error('录制视频')
    

      // 开始执行渲染canvas和播放video的代码逻辑
      、、、、、、


      this.recording = true

    

      let canvas = document.getElementById('canvas')
      let stream = canvas.captureStream(30)



      // 获取音频上下文
      let audioStreamArr = []

      // let audioContextArr = []
      // let sourceArr = []

      for(let key in this.videoEles) {

        if(this.videoEles[key]?.captureStream) {
          const audioStream = this.videoEles[key].captureStream(30)
          audioStreamArr.push(audioStream)
        } else {

          const audioContext = new (window.AudioContext || window.webkitAudioContext)()
          const source = audioContext.createMediaElementSource(this.videoEles[key])
          const destination = audioContext.createMediaStreamDestination()
          source.connect(destination)
          const audioStream = destination.stream
          audioStreamArr.push(audioStream)

          // const source = new MediaElementAudioSourceNode(audioContext, {
          //   mediaElement: this.videoEles[key]
          // })
          // const gainNode = new GainNode(audioContext)
          // source.connect(gainNode)
          // const destination = audioContext.createMediaStreamDestination()
          // gainNode.connect(destination)
          // const audioStream = destination.stream
  
          // audioContextArr.push(audioContext)
          // sourceArr.push(source)
        }


        
      }

      let audioChunks = []
      audioStreamArr.map(it => {
        audioChunks.push(...it.getAudioTracks())
      })


      // 创建一个新的MediaStream,将视频跟音频流合并进去
      let combinedStream = new MediaStream([ 
        ...stream.getTracks(), 
        ...audioChunks
      ]);

      const userAgent = navigator.userAgent || navigator.vendor
      console.error('userAgent::', userAgent)
      let options = {}

      /*
      if ('MediaSource' in window && MediaSource.isTypeSupported('video/webm; codecs="vp9"')) {
        console.log('Your browser supports VP9 via MediaSource Extensions');
      } else {
        console.log('Your browser does NOT support VP9 via MediaSource Extensions');
      }
        */

      // Android
      if (userAgent.match(/Android/i)) {
        options = {
          audioBitsPerSecond: 128000,
          videoBitsPerSecond: 2500000,
          // mimeType: 'video/webm; codecs="vp8,opus"',
          mimeType: isWeChatEnv() ? 'video/webm; codecs="vp8,opus"' : 'video/mp4; codecs="avc1.64001e"',
        }
      } else if (/iPad|iPhone|iPod/.test(userAgent)) {
        options = {
          audioBitsPerSecond: 128000,
          videoBitsPerSecond: 2500000,
          mimeType: 'video/mp4; codecs="avc1"',
        }
      } else {
      
        options = {
          audioBitsPerSecond: 128000,
          videoBitsPerSecond: 2500000,
          // mimeType: 'video/webm; codecs="vp8"',
          mimeType: 'video/mp4; codecs="avc1"',
        }
      }

      // 初始化录制对象
      recorder = new MediaRecorder(combinedStream, options)
      let recordData = []

      // 收集录制数据
      recorder.ondataavailable = function(event) {
        if(event.data && event.data.size) {
          recordData.push(event.data)
        }
      }

      // 监听录制结束事件
      recorder.onstop = async () => {

          console.log('recorder onstop')
  
          let  blobType = 'video/mp4'
        

          let url = URL.createObjectURL(new Blob(recordData, {type: blobType}))
          console.log(url)
          // const a = document.createElement('a');
          // a.href = url;
          // a.download = 'recording.webm';
          // a.click();
          // URL.revokeObjectURL(url);

          const time = new Date().toJSON()
          if (window.navigator && window.navigator?.msSaveOrOpenBlob) {

            window.navigator.msSaveBlob(url, `${'recording'}_${time}.mp4`);
            console.log('IOS下载', url)
          } else {

            console.log('安卓下载', url)

            const link = document.createElement("a");
            link.style.display = "none";
            link.href = url;
            // link.target = '_blank';
            link.setAttribute("download", `${'recording'}_${time}.mp4`);
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
          }

        
        
      }

      recorder.start(10000)

      
    }



    // 结束录制
    recorder.stop()