WebRTC记录音视频流(web技术分享)


Posted in Javascript onFebruary 24, 2022

一、监听开始事件

  • EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,DocumentWindow或者任何其他支持事件的对象 (比如 XMLHttpRequest)。
  • addEventListener()的工作原理是将实现EventListener的函数或对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。
document.querySelector('button#start').addEventListener('click', async () => {

    document.querySelector('button#start').disabled = true;

    const constraints = {

        audio: {},

        video: {

            width: 1280, height: 720

        }

    };

    await init(constraints);

});

二、获取音视频轨道

  • MediaDevices.getUserMedia() 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。
  • 它返回一个 Promise 对象,成功后会resolve回调一个 MediaStream 对象。若用户拒绝了使用权限,或者需要的媒体源不可用,promise会reject回调一个 PermissionDeniedError 或者 NotFoundError
async function init(constraints) {

    try {

        const stream = await navigator.mediaDevices.getUserMedia(constraints);

        handleSuccess(stream);

    } catch (e) {

        console.error('navigator.getUserMedia error:', e);

    }

}
  • HTMLMediaElement 接口的 srcObject 属性设定或返回一个对象,这个对象提供了一个与HTMLMediaElement关联的媒体源,这个对象通常是 MediaStream ,但根据规范可以是 MediaSource, Blob 或者 File。
function handleSuccess(stream) {

    recordButton.disabled = false;

    window.stream = stream;

    const gumVideo = document.querySelector('video#gum');

    gumVideo.srcObject = stream;

}

三、录制媒体流

  • MediaRecorder() 构造函数会创建一个对指定的 MediaStream 进行录制的 MediaRecorder 对象
  • MediaRecorder.ondataavailable 事件处理程序API处理dataavailable事件,在响应运行代码Blob数据被提供使用。
  • dataavailableMediaRecorder将媒体数据传递到您的应用程序以供使用时,将触发该事件。数据在包含数据的Blob对象中提供。

这在四种情况下发生:

  • 媒体流结束时,所有尚未传递到ondataavailable处理程序的媒体数据都将在单个Blob中传递。
  • 当调用MediaRecorder.stop() (en-US)时,自记录开始或dataavailable事件最后一次发生以来已捕 获的所有媒体数据都将传递到Blob中;此后,捕获结束。
  • 调用MediaRecorder.requestData() (en-US) dataavailable时,将传递自记录开始或事件最后一次发生以来捕获的所有媒体数据;然后Blob创建一个新文件,并将媒体捕获继续到该blob中。
  • 如果将timeslice属性传递到开始媒体捕获的MediaRecorder.start() (en-US)方法中,dataavailable则每timeslice毫秒触发一次事件。这意味着每个Blob都有特定的持续时间(最后一个Blob除外,后者可能更短,因为它将是自上次事件以来剩下的所有东西)。
let mediaRecorder;

const recordButton = document.querySelector('button#record');



recordButton.addEventListener('click', () => {

    if (recordButton.textContent === '开始记录') {

        startRecording();

    } else {

        stopRecording();

        recordButton.textContent = '开始记录';

        playButton.disabled = false;

    }

});



function startRecording() {

    recordedBlobs = [];

    try {

        mediaRecorder = new MediaRecorder(window.stream);

    } catch (e) {

        console.error('创建MediaRecorder时异常:', e);

    }

    recordButton.textContent = '停止记录';

    playButton.disabled = true;

    mediaRecorder.ondataavailable = handleDataAvailable;

    mediaRecorder.start();

}



function stopRecording() {

    mediaRecorder.stop();

}



function handleDataAvailable(event) {

    if (event.data && event.data.size > 0) {

        recordedBlobs.push(event.data);

    }

}

四、播放媒体流

  • URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
let recordedBlobs;

const recordedVideo = document.querySelector('video#recorded');

const playButton = document.querySelector('button#play');



playButton.addEventListener('click', () => {

    const superBuffer = new Blob(recordedBlobs, { type: 'video/webm' });

    recordedVideo.src = null;

    recordedVideo.srcObject = null;

    recordedVideo.src = window.URL.createObjectURL(superBuffer);

    recordedVideo.controls = true;

    recordedVideo.play();

});

HTML:

<link rel="stylesheet" href="./index.css">



<video id="gum" autoplay></video>

<video id="recorded"></video>

<div>

    <button id="start">开始</button>

    <button id="record" disabled>开始记录</button>

    <button id="play" disabled>Play</button>

</div>



<script src="./index.js"></script>

CSS:

button {

    margin: 0 3px 10px 0;

    padding-left: 2px;

    padding-right: 2px;

    width: 99px;

}

  

button:last-of-type {

    margin: 0;

}

  

video {

    vertical-align: top;

    --width: 25vw;

    width: var(--width);

    height: calc(var(--width) * 0.5625);

}

  

video:last-of-type {

    margin: 0 0 20px 0;

}

  

video#gumVideo {

    margin: 0 20px 20px 0;

}

JavaScript:

let mediaRecorder;

let recordedBlobs;



const recordedVideo = document.querySelector('video#recorded');

const recordButton = document.querySelector('button#record');

recordButton.addEventListener('click', () => {

    if (recordButton.textContent === '开始记录') {

        startRecording();

    } else {

        stopRecording();

        recordButton.textContent = '开始记录';

        playButton.disabled = false;

    }

});



const playButton = document.querySelector('button#play');

playButton.addEventListener('click', () => {

    const superBuffer = new Blob(recordedBlobs, { type: 'video/webm' });

    recordedVideo.src = null;

    recordedVideo.srcObject = null;

    recordedVideo.src = window.URL.createObjectURL(superBuffer);

    recordedVideo.controls = true;

    recordedVideo.play();

});



function handleDataAvailable(event) {

    if (event.data && event.data.size > 0) {

        recordedBlobs.push(event.data);

    }

}



function startRecording() {

    recordedBlobs = [];

    try {

        mediaRecorder = new MediaRecorder(window.stream);

    } catch (e) {

        console.error('创建MediaRecorder时异常:', e);

    }

    recordButton.textContent = '停止记录';

    playButton.disabled = true;

    mediaRecorder.ondataavailable = handleDataAvailable;

    mediaRecorder.start();

}



function stopRecording() {

    mediaRecorder.stop();

}



function handleSuccess(stream) {

    recordButton.disabled = false;

    window.stream = stream;

    const gumVideo = document.querySelector('video#gum');

    gumVideo.srcObject = stream;

}



async function init(constraints) {

    try {

        const stream = await navigator.mediaDevices.getUserMedia(constraints);

        handleSuccess(stream);

    } catch (e) {

        console.error('navigator.getUserMedia error:', e);

    }

}



document.querySelector('button#start').addEventListener('click', async () => {

    document.querySelector('button#start').disabled = true;

    const constraints = {

        audio: {},

        video: {

            width: 1280, height: 720

        }

    };

    await init(constraints);

});

到此这篇关于WebRTC记录音视频流(web技术分享)的文章就介绍到这了,更多相关WebRTC记录音视频流内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章,希望大家以后多多支持三水点靠木!

 
Javascript 相关文章推荐
解析javascript 浏览器关闭事件
Jul 08 Javascript
js 3秒后跳转页面的实现代码
Mar 10 Javascript
jquery获取radio值实例
Oct 16 Javascript
深入分析原生JavaScript事件
Dec 29 Javascript
jQuery Html控件基本操作(日常收集整理)
Mar 11 Javascript
Node.js和Express简单入门介绍
Mar 24 Javascript
JS时间控制实现动态效果的实例讲解
Jul 31 Javascript
vue-router beforeEach跳转路由验证用户登录状态
Dec 26 Javascript
使用Layer组件弹出多个对话框(非嵌套)与关闭及刷新的例子
Sep 25 Javascript
解决vue打包 npm run build-test突然不动了的问题
Nov 13 Javascript
vue3使用vue-count-to组件的实现
Dec 25 Vue.js
JS创建或填充任意长度数组的小技巧汇总
Oct 24 Javascript
Vue3如何理解ref toRef和toRefs的区别
Feb 18 #Vue.js
JavaScript实现酷炫的鼠标拖尾特效
Vue h函数的使用详解
Feb 18 #Vue.js
详解Vue中$props、$attrs和$listeners的使用方法
Feb 18 #Vue.js
详解JSON.parse和JSON.stringify用法
Feb 18 #Javascript
前端vue+express实现文件的上传下载示例
详解JavaScript的计时器和按钮效果设置
You might like
大师制作的中短波矿石收音机
2020/04/02 无线电
PHP在不同页面间传递Json数据示例代码
2013/06/08 PHP
WordPress中获取所使用的模板的页面ID的简单方法
2015/12/31 PHP
php+redis实现注册、删除、编辑、分页、登录、关注等功能示例
2017/02/15 PHP
Win10 下安装配置IIS + MySQL + nginx + php7.1.7
2017/08/04 PHP
利用php + Laravel如何实现部署自动化详解
2017/10/11 PHP
CSS(js)限制页面显示的文本字符长度
2012/12/27 Javascript
node中socket.io的事件使用详解
2014/12/15 Javascript
关于动态生成dom绑定事件失效的原因及解决方法
2016/08/06 Javascript
HTML Table 空白单元格补全的简单实现
2016/10/13 Javascript
Angular.JS判断复选框checkbox是否选中并实时显示
2016/11/30 Javascript
js实现文字选中分享功能
2017/01/25 Javascript
vue解决使用webpack打包后keep-alive不生效的方法
2018/09/01 Javascript
Vue.js 父子组件通信的十种方式
2018/10/30 Javascript
微信小程序实现页面下拉刷新和上拉加载功能详解
2018/12/03 Javascript
VueX模块的具体使用(小白教程)
2020/06/05 Javascript
原生js实现自定义难度的扫雷游戏
2021/01/22 Javascript
[06:42]DOTA2每周TOP10 精彩击杀集锦vol.1
2014/06/25 DOTA
详解Python读取配置文件模块ConfigParser
2017/05/11 Python
python计算auc指标实例
2017/07/13 Python
PIL包中Image模块的convert()函数的具体使用
2020/02/26 Python
python+requests接口自动化框架的实现
2020/08/31 Python
澳大利亚波西米亚风情网上商店:Czarina
2019/03/18 全球购物
德国药房apodiscounter中文官网:德国排名前三的网上药店
2019/06/03 全球购物
Etam俄罗斯:法国女士内衣和家居服网上商店
2019/10/30 全球购物
为什么Runtime.exec(“ls”)没有任何输出?
2014/10/03 面试题
优秀毕业生推荐信
2013/11/02 职场文书
中专生职业生涯规划书范文
2013/12/29 职场文书
大学生未来职业生涯规划书
2014/02/15 职场文书
优秀本科毕业生自荐信
2014/07/04 职场文书
2014年房地产销售工作总结
2014/12/01 职场文书
医院合作意向书范本
2015/05/08 职场文书
企业战略合作意向书
2015/05/08 职场文书
2015秋季开学典礼主持词
2015/07/16 职场文书
《弟子规》读后感:知廉耻、明是非、懂荣辱、辨善恶
2019/12/03 职场文书
python函数的两种嵌套方法使用
2022/04/02 Python