RecordRTC:利用WebRTC在Web端录制视频

利用RecordRTC打开手机或者电脑摄像头,进行录像,完成后对视频文件进行压缩。兼容Firefox 17+Chrome 21+Edge 12+Chrome for Android等。以下代码使用Vue框架,如果不适用可借鉴 qq_34527715的博客

这个只在安卓机适用。如果你是在微信浏览器做录制视频的功能,建议不要使用这种方法。因为录制的视频并没有保存下来,最终传给服务器的文件只剩下390字节的头,视频内容不存在。需要修改底层的RecordRTC.js,我也没有尝试,希望有其他人尝试过,可以留言交流。

一、首先在index.html引入getHTMLMediaElement文件

<script src="https://cdn.webrtc-experiment.com/getHTMLMediaElement.js"></script>

二、下载依赖包

npm i recordrtc
npm i timers

三、在Vue框架可直接使用如下代码块

<template>
<div class="center">
  <div class="settime">
    还剩:{{sec}} 秒
  </div>
  <div v-if="finish" class="finish">
    已录制完成!<br/>请点击右下角上传,或点击中间按钮重新录制
  </div>
  <div id="recording-player"></div>
  <button id="btn-start-recording" class="play">
    <span :class="platStatus?'stop': 'start'"></span>
  </button>
  <button class="back" @click="$router.back(-1)">返回</button>
  <button id="save-to-disk" class="save">上传</button>
</div>
</template>

<script>
import RecordRTC from 'recordrtc'
import { setInterval, clearInterval } from "timers";
export default {
  data() {
    return {
      winWidth: window.innerWidth,
      platStatus: false, //按钮显示‘开始’
      saveVideo: false,
      sec: 10,
      bloburl: '',
      interval: '',
      finish: false
    }
  },
  created(){
    
  },
  mounted(){
    console.log(this.winWidth)
    const that = this;
    var video = document.createElement('video');
    video.controls = false;
    var mediaElement = getHTMLMediaElement(video, {
        buttons: ['full-screen', 'take-snapshot'],
        showOnMouseEnter: false,
        width: that.winWidth,
    });
    document.getElementById('recording-player').appendChild(video);
    var div = document.createElement('section');
    mediaElement.media.parentNode.appendChild(div);
    div.appendChild(mediaElement.media);
    var recordingPlayer = mediaElement.media;
    var btnStartRecording = document.querySelector('#btn-start-recording');
    var saveRecording = document.querySelector('#save-to-disk');
    var mimeType = 'video/webm';
    var fileExtension = 'webm';
    var recorderType = null;
    var type = 'video';
    var videoBitsPerSecond = null;
    var button = btnStartRecording;
    function getURL(arg) {
      console.log("getURL:", arg);
        var url = arg;
        var str = typeof(arg);
        // if(arg instanceof Blob || arg instanceof File) {
        //   url = window.URL.createObjectURL(arg);
        // }
        // if(arg instanceof RecordRTC || arg.getBlob) {
        //   url = window.URL.createObjectURL(arg.getBlob());
        // }
        // if(arg instanceof MediaStream || arg.getTracks || arg.getVideoTracks || arg.getAudioTracks) {
        //     // url = URL.createObjectURL(arg);
        // }
        if(str == 'string'){
          that.finish = true;
        }
        that.bloburl = url;
        // return url;
    }
    function setVideoURL(arg, forceNonImage) {
      console.log("setVideoURL")
        var url = getURL(arg);
        var parentNode = recordingPlayer.parentNode;
        parentNode.removeChild(recordingPlayer);
        parentNode.innerHTML = '';
        var elem = 'video';
        recordingPlayer = document.createElement(elem);
        if(arg instanceof MediaStream) {
            recordingPlayer.muted = true;
        }
        recordingPlayer.addEventListener('loadedmetadata', function() {
            if(navigator.userAgent.toLowerCase().indexOf('android') == -1) return;
            // android
            setTimeout(function() {
                if(typeof recordingPlayer.play === 'function') {
                    recordingPlayer.play();
                }
            }, 2000);
        }, false);
        recordingPlayer.poster = '';
        if(arg instanceof MediaStream) {
            recordingPlayer.srcObject = arg;
        }
        else {
            recordingPlayer.src = url;
        }
        if(typeof recordingPlayer.play === 'function') {
            recordingPlayer.play();
        }
        
        recordingPlayer.addEventListener('ended', function() {
            url = getURL(arg);
            
            if(arg instanceof MediaStream) {
                recordingPlayer.srcObject = arg;
            }
            else {
                recordingPlayer.src = url;
            }
        });
        parentNode.appendChild(recordingPlayer);
    }
    
    button.mediaCapturedCallback = function() {
      console.log("mediaCapturedCallback")
    
        var options = {
            type: type,
            mimeType: mimeType,
            getNativeBlob: false, // enable it for longer recordings
            video: recordingPlayer
        };
        options.ignoreMutedMedia = false;
        button.recordRTC = RecordRTC(button.stream, options);
        button.recordingEndedCallback = function(url) {
            setVideoURL(url);
        };
        button.recordRTC.startRecording();
    
    }
    //初始化
    var commonConfig = {
        onMediaCaptured: function(stream) {
          // that.finish = false;
            button.stream = stream;
            if(button.mediaCapturedCallback) {
                button.mediaCapturedCallback();
            }
            
            // button.innerHTML = "停止";
            button.disabled = false;
            that.platStatus = true;
            that.interval = setInterval(() => {
              that.sec--;
              if (that.sec <= 0) {
                clearInterval(that.interval);
                btnStartRecording.onclick();
              }
            }, 1000);
        },
        onMediaStopped: function() {
            // button.innerHTML = "开始";
            that.platStatus = false;
            that.sec = 10
            if(!button.disableStateWaiting) {
                button.disabled = false;
            }
        },
        onMediaCapturingFailed: function(error) {
            console.error('onMediaCapturingFailed:', error);
    
            if(error.toString().indexOf('no audio or video tracks available') !== -1) {
                alert('RecordRTC failed to start because there are no audio or video tracks available.');
            }
    
            if(DetectRTC.browser.name === 'Safari') return;
    
            if(error.name === 'PermissionDeniedError' && DetectRTC.browser.name === 'Firefox') {
                alert('Firefox requires version >= 52. Firefox also requires HTTPs.');
            }
    
            commonConfig.onMediaStopped();
        }
    };
    
    //调起摄像头
    function captureUserMedia(mediaConstraints, successCallback, errorCallback) {
      console.log("captureUserMedia")
        if(mediaConstraints.video == true) {
            mediaConstraints.video = {};
        }
    
        navigator.mediaDevices.getUserMedia(mediaConstraints).then(function(stream) {
            successCallback(stream);
            setVideoURL(stream, true);
        }).catch(function(error) {
            if(error && error.name === 'ConstraintNotSatisfiedError') {
                alert('Your camera or browser does NOT supports selected resolutions or frame-rates. \n\nPlease select "default" resolutions.');
            }
            errorCallback(error);
        });
    }
    
    function addStreamStopListener(stream, callback) {
      console.log("addStreamStopListener")
        var streamEndedEvent = 'ended';
        if ('oninactive' in stream) {
            streamEndedEvent = 'inactive';
        }
        stream.addEventListener(streamEndedEvent, function() {
            callback();
            callback = function() {};
        }, false);
        stream.getAudioTracks().forEach(function(track) {
            track.addEventListener(streamEndedEvent, function() {
                callback();
                callback = function() {};
            }, false);
        });
        stream.getVideoTracks().forEach(function(track) {
            track.addEventListener(streamEndedEvent, function() {
                callback();
                callback = function() {};
            }, false);
        });
    }

    function captureAudioPlusVideo(config) {
      that.finish = false;
      console.log(captureAudioPlusVideo)
        captureUserMedia({video: true, audio: true}, function(audioVideoStream) {
            config.onMediaCaptured(audioVideoStream);
            if(audioVideoStream instanceof Array) {
                audioVideoStream.forEach(function(stream) {
                    addStreamStopListener(stream, function() {
                        config.onMediaStopped();
                    });
                });
                return;
            }
            addStreamStopListener(audioVideoStream, function() {
                config.onMediaStopped();
            });
        }, function(error) {
            config.onMediaCapturingFailed(error);
        });
    }
    
    function stopStream() {
      console.log("stopStream")
        if(button.stream && button.stream.stop) {
            button.stream.stop();
            button.stream = null;
        }
    
        if(button.stream instanceof Array) {
            button.stream.forEach(function(stream) {
                stream.stop();
            });
            button.stream = null;
        }
    
        videoBitsPerSecond = null;
        var html = 'Recording status: stopped';
        html += '<br>Size: ' + button.recordRTC.getBlob().size;
    }
    
    function getFileName(fileExtension) {
        var d = new Date().getTime();
        // var year = d.getUTCFullYear();
        // var month = d.getUTCMonth() + 1;
        // var date = d.getUTCDate();
        return 'RecordRTC-' + d + '.' + fileExtension;
    }
    
    function saveToDiskOrOpenNewTab(recordRTC) {
        var fileName = getFileName(fileExtension);
        saveRecording.onclick = function(event) {
            if(!recordRTC) return alert('No recording found.');
            var file = new File([recordRTC.getBlob()], fileName, {
                type: mimeType
            });
            console.log(file);
            // that.$store.commit("changeAndroidVideo", file);
            // that.$router.back(-1);
            // invokeSaveAsDialog(file, file.name);
        }
    }
    //操作录像
    btnStartRecording.onclick = function(event) {
      clearInterval(that.interval);
      console.log(that.platStatus)
        if(that.platStatus == true) {
            button.disabled = true;
            button.disableStateWaiting = true;
            setTimeout(function() {
                button.disabled = false;
                button.disableStateWaiting = false;
            }, 2000);
            // button.innerHTML = "开始";
            that.platStatus = false;
            that.saveVideo = true;
            if(button.recordRTC) {
                if(button.recordRTC.length) {
                    button.recordRTC[0].stopRecording(function(url) {
                        if(!button.recordRTC[1]) {
                            button.recordingEndedCallback(url);
                            stopStream();
                            saveToDiskOrOpenNewTab(button.recordRTC[0]);
                            return;
                        }
                        button.recordRTC[1].stopRecording(function(url) {
                            button.recordingEndedCallback(url);
                            stopStream();
                        });
                    });
                }
                else {
                    button.recordRTC.stopRecording(function(url) {
                        button.recordingEndedCallback(url);
                        saveToDiskOrOpenNewTab(button.recordRTC);
                        stopStream();
                    });
                }
            }
            return;
        }
        captureAudioPlusVideo(commonConfig);
    }
  },
  methods: {
    
      
  }
};
</script>
<style scoped>
.play{
  width: 50px;
  height: 50px;
  line-height: 52px;
  text-align: center;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 50%;
  position: absolute;
  bottom: 20px;
  left: 50%;
  margin-left: -25px;
  /* color: #fff; */
}
.start{
  display: inline-block;
  width: 35px;
  height: 35px;
  border-radius: 50%;
  background-color: #ccc;
  margin-top: 6px;
}
.stop{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 5px;
  background-color: red;
  margin-top: 16px;
}
.save{
  width: 50px;
  height: 35px;
  line-height: 35px;
  text-align: center;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 3px;
  position: absolute;
  bottom: 25px;
  right: 20px;
}
.back{
  width: 50px;
  height: 35px;
  line-height: 35px;
  text-align: center;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 3px;
  position: absolute;
  bottom: 25px;
  left: 20px;
}
.settime{
  width: 100%;
  height: 40px;
  line-height: 40px;
  background-color: rgba(0, 0, 0, 0.5);
  color: #fff;
  text-align: center;
}
.finish{
  text-align: center;
  margin-top: 100px;
  padding: 20px;
}
</style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容