import RecordRTC from 'recordrtc'; // getDisplayMedia 提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口) // getUserMedia 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。 const _videoFormat = 'mp4'; const _videoType = `video/${_videoFormat}`; // 录制相关 Stream 流 window.recordStream = { desktopStream: null, // 屏幕画面&系统声音 由 getDisplayMedia 提供 voiceStream: null, // 当前主播麦克风声音 由 getUserMedia 提供 localStream: null, // trtc 初始化本地音视频流对象 trtcStream: [], // TRTC 音轨道 }; // 录制 RecordRTC 对象 window.recordVideo = null; // 保存录制时的 blob 对象 const binaryData = []; // 检查用户浏览器录制情况 const hasGetUserMedia = !!(navigator.getUserMedia || navigator.getDisplayMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); // 初始化录制器 export function initRecordRTC(){ if(!hasGetUserMedia) { alert("Your browser cannot stream from your webcam. Please switch to Chrome or Firefox."); return; } window.recordVideo = null; window.recordStream = { desktopStream: null, // 屏幕画面&系统声音 由 getDisplayMedia 提供 voiceStream: null, // 当前主播麦克风声音 由 getUserMedia 提供 localStream: null, // trtc 初始化本地音视频流对象 trtcStream: [], // TRTC 音轨道 }; requestUserMedia(); } // 开始录制 export function startRecord(success = null, error = null){ console.log('window.recordVideo', window.recordVideo); captureDisplayMedia((stream) =>{ console.log('captureDisplayMedia', stream); window.recordStream.desktopStream = new MediaStream(stream); // 添加主播麦克风音轨道 let mainTrack = null if (window.recordStream.voiceStream) { console.log('window.recordStream.voiceStream.getAudioTracks()', window.recordStream.voiceStream.getAudioTracks()); mainTrack = window.recordStream.voiceStream.getAudioTracks()[0]; } else if (window.recordStream.localStream){ console.log('window.recordStream.localStream.getAudioTrack()', window.recordStream.localStream.getAudioTrack()); mainTrack = window.recordStream.localStream.getAudioTrack(); } if (mainTrack) { window.recordStream.desktopStream.addTrack(mainTrack); } // 初始化 RecordRTC window.recordVideo = RecordRTC(window.recordStream.desktopStream, { type: 'video' }) // 初始化 binaryData binaryData.splice(-1, binaryData.length); binaryData.push(stream); // 开始录制 window.recordVideo.startRecording(); // 录制视频渲染 document.querySelector('video#record_rtc_video').srcObject = stream; let url = (URL || webkitURL).createObjectURL(new Blob(binaryData, { type: _videoType })); document.querySelector('video#record_rtc_video').src = url; success && success('ok') }, (err)=>{ console.error('captureDisplayMedia', err); error && error(err) }) } // 停止录制 export function stopRecord(){ console.log('window.recordVideo', window.recordVideo); // 停止录制 window.recordVideo && window.recordVideo.stopRecording(()=>{ // 初始化视频渲染 document.querySelector('video#record_rtc_video').srcObject = null; document.querySelector('video#record_rtc_video').src = null; // 初始化 binaryData binaryData.splice(-1, binaryData.length); binaryData.push(window.recordVideo.blob); let url = (URL || webkitURL).createObjectURL(new Blob(binaryData, { type: _videoType })); document.querySelector('video#record_rtc_video').src = url; }) } // 检测是否存在异常 export function hasError(){ return !window.recordVideo ? true : false; } // 下载录制视频 export function downloadRecord(){ console.log('window.recordVideo', window.recordVideo); // 初始化 binaryData binaryData.splice(-1, binaryData.length); binaryData.push(window.recordVideo.blob); let url = (URL || webkitURL).createObjectURL(new Blob(binaryData, { type: _videoType })); let fileName = new Date().getTime() + "." + _videoFormat; const aLink = document.createElement('a'); document.body.appendChild(aLink); aLink.style.display='none'; aLink.href = url; aLink.download = fileName; aLink.click(); document.body.removeChild(aLink); window.URL.revokeObjectURL(url); } // 播放录制视频 export function playRecord(){ console.log('window.recordVideo', window.recordVideo); var recVideo = document.querySelector('video#record_rtc_video'); // 初始化 binaryData binaryData.splice(-1, binaryData.length); binaryData.push(window.recordVideo.blob); let url = (URL || webkitURL).createObjectURL(new Blob(binaryData, { type: _videoType })); recVideo.src = url; recVideo.srcObject = null; recVideo.controls = true; recVideo.volume = 1; recVideo.play(); } // 当录制过程,有音频插入时 export function recordAudioInsert(trtcStream){ // trtcStream 记录音轨道 window.recordStream.trtcStream.push(trtcStream); // 停止录制 window.recordVideo && window.recordVideo.stopRecording(()=>{ // binaryData 追加录制视频 binaryData.push(window.recordVideo.blob); // 整合 stream 流 const stream = mixerTRTC(window.recordStream.desktopStream, trtcStream); // 初始化 RecordRTC window.recordVideo = null; window.recordVideo = RecordRTC(stream, { type: 'video' }) // 开始录制 window.recordVideo.startRecording(); }) } // 混音 TRTC function mixerTRTC(stream1, stream2){ const ctx = new AudioContext(); const dest = ctx.createMediaStreamDestination(); if( stream1 && stream1.getAudioTracks().length > 0) ctx.createMediaStreamSource(stream1).connect(dest); if( stream2 && stream2.getAudioTrack()) ctx.createMediaStreamSource(stream2).connect(dest); let tracks = dest.stream && dest.stream.getTracks(); tracks = tracks.concat(stream1.getVideoTracks()).concat([stream2.getAudioTrack()]); return new MediaStream(tracks) } // 混音 function mixer(stream1, stream2) { const ctx = new AudioContext(); const dest = ctx.createMediaStreamDestination(); if(stream1.getAudioTracks().length > 0) ctx.createMediaStreamSource(stream1).connect(dest); if(stream2.getAudioTracks().length > 0) ctx.createMediaStreamSource(stream2).connect(dest); let tracks = dest.stream.getTracks(); tracks = tracks.concat(stream1.getVideoTracks()).concat(stream2.getVideoTracks()); return new MediaStream(tracks) } // 捕捉屏幕媒体 function captureDisplayMedia(callback, error){ var params = {video: true, audio: true}; if( navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { navigator.mediaDevices.getDisplayMedia(params).then(callback).catch((err) => { console.error(err.name + ": " + err.message); if (err.name == 'NotFoundError' || err.name == 'NotAllowedError') { // alert("用户拒绝了使用权限"); } error && error(err) }); } else { navigator.getDisplayMedia(params).then(callback).catch((err) => { console.error(err.name + ": " + err.message); error && error(err) }); } } // 请求用户媒体 function requestUserMedia(){ getUserMedia((stream) => { window.recordStream.voiceStream = stream; console.log('checkUserMedia', stream) // document.getElementById("record_rtc_audio").srcObject = stream; }, (err) => { console.error("getUserMedia", err.name + ": " + err.message); }); } // 获取用户媒体 function getUserMedia(callback, error){ var params = { audio: true }; navigator.getUserMedia = ( navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia ); if( typeof navigator.mediaDevices.getUserMedia === 'undefined') { navigator.getUserMedia(params).then(callback).catch((err) => { console.error("getUserMedia", err.name + ": " + err.message); error && error(err) }); } else { navigator.mediaDevices.getUserMedia(params).then(callback).catch((err) => { console.error("getUserMedia", err.name + ": " + err.message); if (err.name == "NotFoundError" || err.name == "DevicesNotFoundError") { //required track is missing alert("没有找到合适的媒体"); console.error("没有找到合适的媒体"); } else if (err.name == "NotReadableError" || err.name == "TrackStartError") { //webcam or mic are already in use alert("网络摄像头或麦克风已经投入使用") console.error("网络摄像头或麦克风已经投入使用") } else if (err.name == "OverconstrainedError" || err.name == "ConstraintNotSatisfiedError") { //constraints can not be satisfied by avb. devices } else if (err.name == "NotAllowedError" || err.name == "PermissionDeniedError") { //permission denied in browser alert("浏览器拒绝权限"); console.error("浏览器拒绝权限") } else if (err.name == "TypeError" || err.name == "TypeError") { //empty constraints object } else { //other errors } error && error(err) }); } }