首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Electron音视频录制

Electron音视频录制

作者头像
码客说
发布于 2019-10-21 09:14:43
发布于 2019-10-21 09:14:43
4.2K10
代码可运行
举报
文章被收录于专栏:码客码客
运行总次数:0
代码可运行

获取设备的支持情况

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var types = [
  "video/webm",
	"audio/webm",
	"video/webm\;codecs=vp8",
	"video/webm\;codecs=daala",
	"video/webm\;codecs=h264",
	"audio/webm\;codecs=opus",
	"video/mpeg"
];

for (var i in types) {
	console.log(types[i] + ":" + (MediaRecorder.isTypeSupported(types[i]) ? "支持" : "不支持"));
}

获取可用的视频源

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
desktopCapturer.getSources({
  types: ['screen']
}, (error, sources) => {
  if (error) throw error
  for (let source of sources) {
    console.info(source);
  }
});

定义全局变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let audioStream;
let vedioStream;

let mediaRecorder;
let recordedChunks = [];

获取音频流

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function getAudioStream() {
  navigator.mediaDevices.getUserMedia({audio: true, video: false})
      .then(function (stream) {
          audioStream = stream;
          getVedioStream()
          stream.onended = () => {
              console.log('Micro audio ended.')
          }
      })
      .catch(function (error) {
          console.log('getUserMedia() failed.')
      });
}

或者

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function getAudioStream() {
  navigator.webkitGetUserMedia(
    {audio: true, video: false},
    function (stream) {
      console.log('Received audio stream.')
      audioStream = stream;
      getVedioStream()
      stream.onended = () => {
        console.log('Micro audio ended.')
      }
    },
    function () {
      console.log('getUserMedia() failed.')
    }
  );
}

获取视频流

sourceid可以通过获取可用的录制源返回source.id获取

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function getVedioStream() {
  navigator.mediaDevices.getUserMedia({
    audio: false,
    video: {
      mandatory: {
        chromeMediaSource: 'desktop',
        // chromeMediaSourceId: sourceid,
        maxWidth: window.screen.width,
        maxHeight: window.screen.height
      }
    }
  }).then(function (stream) {
    vedioStream = stream;
    let liveVideo = document.querySelector("video");
    liveVideo.srcObject = stream;
    liveVideo.muted = true;
    liveVideo.play();

    startRecord();
  }).catch(function (err) {
    console.log('The following error occured: ' + err);
  })
}

或者

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function getVedioStream() {
  navigator.webkitGetUserMedia({
    audio: false,
    video: {
      mandatory: {
        chromeMediaSource: 'desktop',
        // chromeMediaSourceId: sourceid,
        maxWidth: window.screen.width,
        maxHeight: window.screen.height
      }
    }
  }, function (stream) {
    vedioStream = stream;
    let liveVideo = document.querySelector("video");
    liveVideo.srcObject = stream;
    liveVideo.muted = true;
    liveVideo.play();
    startRecord();
  }, function () {

  })
}

录制带声音

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function startRecord() {
  if (audioStream) {
    let audioTracks = audioStream.getAudioTracks();
    vedioStream.addTrack(audioTracks[0]);
  }

  mediaRecorder = new MediaRecorder(vedioStream);

  mediaRecorder.ondataavailable = (event) => {
    if (event.data && event.data.size > 0) {
      recordedChunks.push(event.data)
    }
  };

  mediaRecorder.onstart = () => {
    console.log("开始录制")
  };
  mediaRecorder.onstop = () => {
    console.log("停止录制")
  };

  mediaRecorder.start();
}

停止录制

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 结束录制
function stopRecord() {
  if (mediaRecorder) {
    mediaRecorder.stop();
  } else {
    alert("还没有开始。");
  }
}

保存

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function formatLength(str, length) {
  str += '';
  if (str.length < length)
    return formatLength('0' + str, length)
  else
    return str
}

function getnowstr() {
  var now = new Date();
  var year = now.getFullYear(); //得到年份
  var month = formatLength(now.getMonth(), 2);//得到月份
  var date = formatLength(now.getDate(), 2);//得到日期
  var hour = formatLength(now.getHours(), 2);//得到小时
  var minu = formatLength(now.getMinutes(), 2);//得到分钟
  var all_time = year + "-" + month + "-" + date + "_" + hour + "-" + minu;
  return all_time;
}

// 保存视频
function saveRecord() {
  let blob = new Blob(recordedChunks, {type: "video/x-matroska;codecs=avc1,opus"});
  let url = URL.createObjectURL(blob);
  let a = document.createElement('a');
  var all_time = getnowstr();
  document.body.appendChild(a);
  a.style = 'display: none';
  a.href = url;
  a.download = all_time + 'video.webm';
  a.click()
  setTimeout(function () {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url)
  }, 100)
}

播放

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function playRecord() {
  let video = document.querySelector('video')
  video.controls = true;
  video.muted = false;
  let blob = new Blob(recordedChunks, {type: "video/x-matroska;codecs=avc1,opus"})
  video.src = window.URL.createObjectURL(blob)
  video.play();
}

整体代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Document</title>
</head>

<body>
<video id="live"></video>

<div class="toolbar">
    <span class="timer"></span>
    <button id="startOrStop">开始</button>
    <button id="save">保存</button>
    <button id="play">播放</button>
</div>

<style>
    body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        width: 100vw;
        height: 100vh;
        display: flex;
        flex-direction: column;
    }

    #live {
        width: 100%;
        height: 0;
        flex-grow: 1;
    }

    .toolbar {
        width: 100%;
        height: 60px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .toolbar button {
        margin-left: 6px;
        margin-right: 6px;
        padding: 10px 16px;
        border-radius: 4px;
        background-color: blue;
        color: white;
    }

    .timer {
        width: 60px;
        text-align: center;
    }
</style>

<script>
    let n = 0;
    let timer;

    let audioStream;
    let vedioStream;

    let mediaRecorder;
    let recordedChunks = [];

    const startOrStopButton = document.getElementById("startOrStop");
    const saveButton = document.getElementById("save");
    const playButton = document.getElementById("play");
    startOrStopButton.addEventListener("click", startOrStopRecord);
    saveButton.addEventListener("click", saveRecord);
    playButton.addEventListener("click", playRecord);

    function getAudioStream() {
        navigator.mediaDevices.getUserMedia({audio: true, video: false})
            .then(function (stream) {
                audioStream = stream;
                getVedioStream()
                stream.onended = () => {
                    console.log('Micro audio ended.')
                }
            })
            .catch(function (error) {
                console.log('getUserMedia() failed.')
            });
    }

    function getVedioStream() {
        navigator.mediaDevices.getUserMedia({
            audio: false,
            video: {
                mandatory: {
                    chromeMediaSource: 'desktop',
                    maxWidth: window.screen.width,
                    maxHeight: window.screen.height
                }
            }
        }).then(function (stream) {
            vedioStream = stream;
            let liveVideo = document.querySelector("video");
            liveVideo.src = null;
            liveVideo.srcObject = vedioStream;
            liveVideo.controls = false;
            liveVideo.muted = true;
            liveVideo.play();

        }).catch(function (err) {
            console.log('The following error occured: ' + err);
        })
    }

    // 显示录制的秒数
    function startTimer() {
        const timerEl = document.querySelector(".timer");
        n = 0;
        timerEl.textContent = `${n}s`;
        timer = setInterval(() => {
            n += 1;
            timerEl.textContent = `${n}s`;
        }, 1000);
    }

    // 开始录制
    function startRecord() {
        if (audioStream) {
            let audioTracks = audioStream.getAudioTracks();
            vedioStream.addTrack(audioTracks[0]);
        }

        mediaRecorder = new MediaRecorder(vedioStream);
        mediaRecorder.ondataavailable = (event) => {
            if (event.data && event.data.size > 0) {
                recordedChunks.push(event.data)
            }
        };

        mediaRecorder.onstart = () => {
            console.log("开始录制")
        };
        mediaRecorder.onstop = () => {
            console.log("停止录制")
        };

        mediaRecorder.start();
    }

    // 结束录制
    function startOrStopRecord() {
        if (startOrStopButton.innerHTML === "开始") {
            recordedChunks = [];
            startTimer();
            startRecord();
            startOrStopButton.innerHTML = "停止"
        } else {
            clearInterval(timer);
            if (mediaRecorder) {
                mediaRecorder.stop();
                //停止视频音频流
                // vedioStream.getVideoTracks()[0].stop();
                // audioStream.getAudioTracks()[0].stop();
            }
            startOrStopButton.innerHTML = "开始"
        }
    }

    function formatLength(str, length) {
        str += '';
        if (str.length < length)
            return formatLength('0' + str, length);
        else
            return str
    }

    function getnowstr() {
        let now = new Date();
        let year = now.getFullYear(); //得到年份
        let month = formatLength(now.getMonth(), 2);//得到月份
        let date = formatLength(now.getDate(), 2);//得到日期
        let hour = formatLength(now.getHours(), 2);//得到小时
        let minu = formatLength(now.getMinutes(), 2);//得到分钟
        let time_all = year + "-" + month + "-" + date + "_" + hour + ":" + minu;
        return time_all;
    }

    // 保存视频
    function saveRecord() {
        let blob = new Blob(recordedChunks, {type: "video/x-matroska;codecs=avc1,opus"});
        let url = URL.createObjectURL(blob);
        let a = document.createElement('a');
        var all_time = getnowstr();
        document.body.appendChild(a);
        a.style = 'display: none';
        a.href = url;
        a.download = all_time + 'vedio.webm';
        a.click()
        setTimeout(function () {
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url)
        }, 100)
    }

    function playRecord() {
        if (playButton.innerHTML === "播放") {
            let liveVideo = document.querySelector('video');
            liveVideo.controls = true;
            liveVideo.muted = false;
            let blob = new Blob(recordedChunks, {type: "video/x-matroska;codecs=avc1,opus"});
            liveVideo.srcObject = null;
            liveVideo.src = window.URL.createObjectURL(blob);
            liveVideo.play();
            playButton.innerHTML = "取消播放"
        } else {
            let liveVideo = document.querySelector("video");
            liveVideo.src = null;
            liveVideo.srcObject = vedioStream;
            liveVideo.controls = false;
            liveVideo.muted = true;
            liveVideo.play();
            playButton.innerHTML = "播放"
        }
    }

    getAudioStream();
</script>
</body>
</html>

工具类

为了方便使用封装成了工具类方便使用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 *自动录屏模块*录制桌面
 *
 * @class Recorder
 */
class Recorder {
    constructor(path) {
        this.mediaOutputPath = path;
    }

    /**
     *开始录制
     *
     * @memberof Recorder
     */
    startRecord = () => {
        /* 要获取桌面音频必须设置audio约束如下 */
        this.getVedioStream().then(vedioStream => {
            this.getAudioStream().then((audioStream) => {
                vedioStream.addTrack(audioStream.getAudioTracks()[0])//注!此处添加麦克风音轨无效
                this.startRecorder(vedioStream);
            });
        }).catch(err => {
            this.getUserMediaError(err);
        });
    };

    /**
     *获取麦克风音频流
     *
     * @memberof Recorder
     */
    getAudioStream = () => {
        return navigator.mediaDevices.getUserMedia({audio: true, video: false})
    };


    /**
     *获取屏幕视频流
     *
     * @memberof Recorder
     */
    getVedioStream = () => {
        return navigator.mediaDevices.getUserMedia({
            audio: false,
            video: {
                mandatory: {
                    chromeMediaSource: 'desktop',
                    maxWidth: window.screen.width,
                    maxHeight: window.screen.height
                }
            }
        })
    };

    /**
     *获取媒体源失败
     *
     * @memberof Recorder
     */
    getUserMediaError = (err) => {
        console.log('mediaError', err);
    };


    getUserAudioError = (err) => {
        console.log('audioError', err);

    };

    /**
     *开始视频录制
     *
     * @memberof Recorder
     */
    startRecorder = (stream) => {
        this.recorder = new MediaRecorder(stream);
        this.recorder.start();
        this.recorder.ondataavailable = event => {
            let blob = new Blob([event.data], {
                type: 'video/webm'
            });
            this.saveMedia(blob);
        };
    };


    formatLength = (str, length) => {
        str += '';
        if (str.length < length)
            return this.formatLength('0' + str, length)
        else
            return str
    };


    getnowstr = () => {
        var now = new Date();
        var year = now.getFullYear(); //得到年份
        var month = this.formatLength(now.getMonth(), 2);//得到月份
        var date = this.formatLength(now.getDate(), 2);//得到日期
        var hour = this.formatLength(now.getHours(), 2);//得到小时
        var minu = this.formatLength(now.getMinutes(), 2);//得到分钟
        var all_time = year + "-" + month + "-" + date + "_" + hour + "-" + minu;
        return all_time;
    };

    /**
     *数据转换并保存成MP4
     *
     * @memberof Recorder
     */
    saveMedia = (blob) => {
        let reader = new FileReader();
        let _t = this;
        var filename = this.mediaOutputPath + this.getnowstr() + "_vedio.webm";
        reader.onload = function () {
            let buffer = Buffer.from(reader.result);
            const fs = require('fs')
            fs.writeFile(filename, buffer, {}, (err, res) => {
                if (err) {
                    console.error(err);
                    return
                }
            })
        };
        reader.readAsArrayBuffer(blob);
    };


    /**
     *停止录制视频
     *
     * @memberof Recorder
     */
    stopRecord = () => {
        this.recorder.stop();
    }


}

exports.Recorder = Recorder;

调用方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const {Recorder} = require('./utils/Recorder');
let my_recorder = new Recorder("/Users/zhangjian/Downloads/");

// 开始录制
my_recorder.startRecord();
// 结束录制自动保存文件
if (my_recorder) {
  my_recorder.stopRecord();
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
强,不过。。。说好的MP4呢?结果输出的还是.webm啊
强,不过。。。说好的MP4呢?结果输出的还是.webm啊
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
「JS高级」构造函数和原型
请注意,本文编写于 2063 天前,最后修改于 173 天前,其中某些信息可能已经过时。
曼亚灿
2023/05/17
1.7K0
「JS高级」构造函数和原型
构造函数和原型
实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
梨涡浅笑
2020/10/27
4350
构造函数和原型
构造函数和原型
在典型的OOP的语言中(如Java) , 都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前, JS中并没用引入类的概念。 ES6,全称ECMAScript6.0 , 2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏 览器也支持ES6 ,不过只实现了ES6的部分特性和功能。 在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
星辰_大海
2020/10/23
5850
构造函数和原型
前端成神之路-JavaScript高级第02天
实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
海仔
2021/01/21
3270
js面向对象编程_JavaScript高级编程
面向过程即分析出解决问题所需要的步骤,然后用函数将这些步骤一步步实现,使用的时候再一个个的一次调用就可以了;
全栈程序员站长
2022/09/24
1.2K0
js面向对象编程_JavaScript高级编程
【说站】Javascript中对象原型 __proto__的介绍
1、所有对象都会有一个属性__proto__指向构造函数的prototype原型对象.
很酷的站长
2022/11/23
5340
【说站】Javascript中对象原型 __proto__的介绍
原型和原型链 prototype和proto的区别
原型是function对象下的属性,它定义了构造函数的共同祖先,也就是一个父子级的关系,子对象会继承父对象的方法和属性
小丞同学
2021/08/16
4630
这些js原型及原型链面试题你能做对几道
在面试过程中,频频被原型相关知识问住,每次回答都支支吾吾。后来有家非常心仪的公司,在二面时,果不其然,又问原型了!
loveX001
2022/10/02
5420
JS学习笔记
内部函数被返回到外部,函数本身保留了父函数的AO,即使父元素执行完了,取消对AO的引用,但依旧被子函数保留下来了,就形成了闭包。
小丞同学
2021/08/16
4490
【说站】JavaScript成员查找机制是什么
以上就是JavaScript成员查找机制的介绍,希望对大家有所帮助。更多Javascript学习指路:Javascript
很酷的站长
2022/11/23
2910
【说站】JavaScript成员查找机制是什么
JavaScript笔记(2) 构造函数和原型
昨天又独自把案例做了一遍,还是有点小错误出现的,但是大体上比较顺利,感觉还是逻辑比较重要,先把思路整理好再开始做会比较好.
y191024
2022/09/20
4450
JavaScript笔记(2) 构造函数和原型
【说站】javascript中原型对象this的原则
以上就是javascript中原型对象this的原则,希望对大家有所帮助。更多Javascript学习指路:Javascript
很酷的站长
2022/11/23
2440
【说站】javascript中原型对象this的原则
详解js原型,构造函数以及class之间的原型关系
含义: 是一个函数的属性,这个属性是一个指针,指向一个对象 作用: 构造函数调用 访问该构造函数所关联的原型对象
念念不忘
2019/03/29
1.8K0
详解js原型,构造函数以及class之间的原型关系
JS【中高级】部分的知识点总结第一篇
每一个知识点我都会尽可能的讲明白,写一些Demo给你们,就像前面写canvas的时候一样,尽量多写一些有说明性的代码,两个目的,第一个是总结一下,第二个是进行一个记录,也给学习js的过程中比较迷茫的一些提示,这篇文章是属于js中相对中高级的,所以初级的看起来会有一些困难,但是初级的可以直接百度或者看我之前的一些关于js的文章进行学习也是可以的!虽然上面的每一个点都是可以直接单独拿出来写一篇文章的,我也不是没这么计划,只是觉得这样会显的这个知识点好像很难一样,会劝退一部分人,所以就直接一篇文章直接搞定算了,篇幅会比较长,和前面的小程序和canvas一样,因为最近都是写一些总结性质的文章,可能会相对比较繁琐,读的时候可以直接收藏,后面慢慢看!我也会尽量的将每一个模块都分的比较清楚!
何处锦绣不灰堆
2022/11/30
6850
再谈构造函数、原型、原型链之间的关系
构造函数、原型、原型链作为ES5的内容,已经是老生常谈的问题了。首先说说为什么要再次拿起这个话题去说呢?这几天有空我会看一些源码,这些源码的底层实现考虑到兼容性还是来源于ES5,很多方法的封装以及实现(不管是按照模块封装还是统一实现)都是面向对象的思想,而且webpack以及rollup打包之后解析出来的代码利用@babel/core和@babel/preset-env转化之后也都是ES5的代码,所以有想再次谈起这个话题,回顾回顾旧知识,温故而知新。
小丑同学
2020/09/21
7810
第186天:js深入理解构造函数和原型对象
1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例。但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propotype chains)实现的。但在ES6中引入了类(class)这个概念,作为对象的模板,新的class写法知识让原型对象的写法更加清晰,这里不重点谈这个
半指温柔乐
2018/09/11
7610
第186天:js深入理解构造函数和原型对象
JS高级原型以及函数调用方式
Javascript 规定,每一个(构造)函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上,以便让同一类型的对象共享方法或其它成员
用户6256742
2024/06/13
5380
JS高级原型以及函数调用方式
JS【中高级】部分的知识点我帮你们总结好了
每一个知识点我都会尽可能的讲明白,写一些Demo给你们,就像前面写canvas的时候一样,尽量多写一些有说明性的代码,两个目的,第一个是总结一下,第二个是进行一个记录,也给学习js的过程中比较迷茫的一些提示,这篇文章是属于js中相对中高级的,所以初级的看起来会有一些困难,但是初级的可以直接百度或者看我之前的一些关于js的文章进行学习也是可以的!虽然上面的每一个点都是可以直接单独拿出来写一篇文章的,我也不是没这么计划,只是觉得这样会显的这个知识点好像很难一样,会劝退一部分人,所以就直接一篇文章直接搞定算了,篇幅会比较长,和前面的小程序和canvas一样,因为最近都是写一些总结性质的文章,可能会相对比较繁琐,读的时候可以直接收藏,后面慢慢看!我也会尽量的将每一个模块都分的比较清楚!
何处锦绣不灰堆
2022/06/13
7400
从零开始学 Web 之 JS 高级(二)原型链,原型的继承
原型链表示的是实例对象与原型对象之间的一种关系,这种关系是通过__proto__原型来联系的。
Daotin
2018/08/31
2.5K0
从零开始学 Web 之 JS 高级(二)原型链,原型的继承
【JS 构造|原型|原型链|继承(圣杯模式)|ES6类语法】上篇
❗️ ❗️ ❗️本篇系将带来JavaScript中的构造——原型——原型链——继承——ES6类语法系列知识完整讲解。 ❗️ ❗️ ❗️ ❕上篇涉及:构造——原型——原型链 ❕下篇涉及:继承——ES6类语法
好吃懒洋洋
2022/11/15
8450
【JS 构造|原型|原型链|继承(圣杯模式)|ES6类语法】上篇
推荐阅读
相关推荐
「JS高级」构造函数和原型
更多 >
LV.1
这个人很懒,什么都没有留下~
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档