RecordRTC:利用WebRTC在Web端錄制視頻

利用RecordRTC打開手機或者電腦攝像頭返劲,進行錄像玲昧,完成后對視頻文件進行壓縮。兼容Firefox 17+篮绿、Chrome 21+Edge 12+吕漂、Chrome for Android等亲配。以下代碼使用Vue框架,如果不適用可借鑒 qq_34527715的博客

這個只在安卓機適用惶凝。如果你是在微信瀏覽器做錄制視頻的功能吼虎,建議不要使用這種方法。因為錄制的視頻并沒有保存下來苍鲜,最終傳給服務(wù)器的文件只剩下390字節(jié)的頭思灰,視頻內(nèi)容不存在。需要修改底層的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();
        }
    };
    
    //調(diào)起攝像頭
    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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喊括,隨后出現(xiàn)的幾起案子胧瓜,更是在濱河造成了極大的恐慌,老刑警劉巖郑什,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贷痪,死亡現(xiàn)場離奇詭異,居然都是意外死亡蹦误,警方通過查閱死者的電腦和手機劫拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來强胰,“玉大人舱沧,你說我怎么就攤上這事∨佳螅” “怎么了熟吏?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玄窝。 經(jīng)常有香客問我牵寺,道長,這世上最難降的妖魔是什么恩脂? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任帽氓,我火速辦了婚禮,結(jié)果婚禮上俩块,老公的妹妹穿的比我還像新娘黎休。我一直安慰自己,他們只是感情好玉凯,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布势腮。 她就那樣靜靜地躺著,像睡著了一般漫仆。 火紅的嫁衣襯著肌膚如雪捎拯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天盲厌,我揣著相機與錄音署照,去河邊找鬼祸泪。 笑死,一個胖子當(dāng)著我的面吹牛藤树,可吹牛的內(nèi)容都是我干的浴滴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼岁钓,長吁一口氣:“原來是場噩夢啊……” “哼升略!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屡限,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤品嚣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钧大,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翰撑,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年啊央,在試婚紗的時候發(fā)現(xiàn)自己被綠了眶诈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓜饥,死狀恐怖逝撬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乓土,我是刑警寧澤宪潮,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站趣苏,受9級特大地震影響狡相,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜食磕,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一尽棕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芬为,春花似錦萄金、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽日戈。三九已至询张,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浙炼,已是汗流浹背份氧。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工唯袄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜗帜。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓恋拷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親厅缺。 傳聞我的和親對象是個殘疾皇子蔬顾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 用到的組件 1、通過CocoaPods安裝 2湘捎、第三方類庫安裝 3诀豁、第三方服務(wù) 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,606評論 1 180
  • 本篇背景介紹:精神分析大師弗洛伊德有本著作叫做《日常生活的心理分析》,在這本書中窥妇,他將潛意識的領(lǐng)域擴大到日常的生活...
    朱孝文心理咨詢師閱讀 682評論 0 1
  • 第一類:經(jīng)典 1.《莊子》A 2.《孟子》A 3.《史記》A 4.《圣經(jīng)》B 5.《論法的精神》A 6.《社會契約...
    haitangwei閱讀 355評論 0 1
  • AI軟件 1.新建畫布:1024*1024px 2.選擇橢圓工具舷胜,在空白區(qū)域,鼠標(biāo)左鍵點擊活翩,設(shè)置寬高為1024*1...
    UI小白師閱讀 1,164評論 0 0
  • 我自己開口說烹骨,帶上我吧,因為我怕下雨材泄。誰知道沮焕,雨越下越大,所以臨時決定要送我回家了脸爱。拋開人家想看我的裝修這件事情遇汞,...
    lygly9閱讀 261評論 0 0