web获取媒体流
1. 下面例子演示了录屏和截图功能:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><!-- 录屏 --><video id="localVideo" autoplay playsinline muted></video><!-- 截图列表 --><div id="imgContainer"></div><button id="screenshotBtn" style="position: fixed;right: 0;top: 50%;">截图</button><script>const constraints = {audio: true,video: { width: 1280, height: 720 }}getLocalStream(constraints)// 获取本地音视频流async function getLocalStream(constraints) {// 获取媒体流const stream = await navigator.mediaDevices.getUserMedia(constraints)// 将媒体流设置到 video 标签上播放playLocalStream(stream)}// 播放本地视频流function playLocalStream(stream) {const videoEl = document.getElementById('localVideo')videoEl.srcObject = stream}</script><script>var imgList = []var screenshotBtn = document.getElementById('screenshotBtn')screenshotBtn.addEventListener('click', generatePhotos)// 拍照function generatePhotos () {// 添加滤镜var filterList = ['blur(5px)', // 模糊'brightness(0.5)', // 亮度'contrast(200%)', // 对比度'grayscale(100%)', // 灰度'hue-rotate(90deg)', // 色相旋转'invert(100%)', // 反色'opacity(90%)', // 透明度'saturate(200%)', // 饱和度'saturate(20%)', // 饱和度'sepia(100%)', // 褐色'drop-shadow(4px 4px 8px blue)', // 阴影]var videoEl = document.getElementById('localVideo')var canvas = document.createElement('canvas')canvas.width = videoEl.videoWidthcanvas.height = videoEl.videoHeightvar ctx = canvas.getContext('2d')// 原图ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)imgList.push(canvas.toDataURL('image/png'))for (let i = 0; i < filterList.length; i++) {ctx.filter = filterList[i]ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)imgList.push(canvas.toDataURL('image/png'))}insertImgs(imgList)}function insertImgs(list) {const container = document.createDocumentFragmentvar imgContainer = document.getElementById('imgContainer'); var fragment = document.createDocumentFragment();list.forEach((img) => {var imgEl = document.createElement('img');imgEl.src = img;fragment.appendChild(imgEl);});imgContainer.appendChild(fragment)}</script>
</body>
</html>
2. 下面的例子演示了录屏的功能
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><h2>录屏</h2><video id="screenVideo" autoplay playsinline></video><button id="recorderStartBtn" style="position: fixed;right: 0;top: 46%;">开始录制</button><button id="recorderStopBtn" style="position: fixed;right: 0;top: 48%;">停止录制</button><button id="recorderPauseBtn" style="position: fixed;right: 0;top: 52%;">暂停录制</button><button id="recorderResumeBtn" style="position: fixed;right: 0;top: 54%;">继续录制</button><script>// 录屏// 获取视频流var localStreamshareScreen()async function shareScreen() {localStream = await navigator.mediaDevices.getDisplayMedia({audio: true,video: true,})// 播放录屏视频流playStream(localStream)}// 在视频标签中播放视频流function playStream(stream) {const video = document.querySelector('#screenVideo')video.srcObject = stream}</script><script>// 录屏保存// 这里我们使用 MediaRecorder 来进行录制,它是一个用于录制媒体流的 API,它可以将媒体流中的数据进行录制,然后将录制的数据保存成一个文件。// 由于 MediaRecorder api 的对 mimeType 参数的支持是有限的。所以我们需要通过 MediaRecorder.isTypeSupported 来判断当前浏览器是否支持我们需要的 mimeTypefunction getSupportedMimeTypes() {const media = 'video'// 常用的视频格式const types = ['webm','mp4','ogg','mov','avi','wmv','flv','mkv','ts','x-matroska',]// 常用的视频编码const codecs = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h264']// 支持的媒体类型const supported = []const isSupported = MediaRecorder.isTypeSupported// 遍历判断所有的媒体类型types.forEach((type) => {const mimeType = `${media}/${type}`codecs.forEach((codec) =>[`${mimeType};codecs=${codec}`,`${mimeType};codecs=${codec.toUpperCase()}`,].forEach((variation) => {if (isSupported(variation)) supported.push(variation)}),)if (isSupported(mimeType)) supported.push(mimeType)})return supported}console.log(getSupportedMimeTypes())function getRecorder () {const kbps = 1024const Mbps = kbps * kbpsconst options = {audioBitsPerSecond: 128000,videoBitsPerSecond: 2500000,mimeType: 'video/webm; codecs="vp8,opus"',}// https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorderreturn new MediaRecorder(localStream, options)}var mediaRecorder = getRecorder()// 开始录制var recorderStartBtn = document.getElementById('recorderStartBtn')recorderStartBtn.addEventListener('click', handleRecorderStart)// 暂停录制var recorderPauseBtn = document.getElementById('recorderPauseBtn')recorderPauseBtn.addEventListener('click', handleRecorderPause)// 继续录制var recorderResumeBtn = document.getElementById('recorderResumeBtn')recorderResumeBtn.addEventListener('click', handleRecorderResume)// 停止录制var recorderStopBtn = document.getElementById('recorderStopBtn')recorderStopBtn.addEventListener('click', handleRecorderStop)// 录制媒体流function handleRecorderStart() {// 开始录制媒体,这个方法调用时可以通过给timeslice参数设置一个毫秒值,如果设置这个毫秒值,那么录制的媒体会按照你设置的值进行分割成一个个单独的区块,而不是以默认的方式录制一个非常大的整块内容。mediaRecorder.start()// 调用它用来处理 dataavailable 事件,该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象.)mediaRecorder.ondataavailable = (e) => {// 将录制的数据合并成一个 Blob 对象// const blob = new Blob([e.data], { type: e.data.type })// 🌸重点是这个地方,我们不要把获取到的 e.data.type设置成 blob 的 type,而是直接改成 mp4const blob = new Blob([e.data], { type: 'video/mp4' })downloadBlob(blob)}mediaRecorder.onstart = (e) => {console.log("开始录制", e)}mediaRecorder.onstop = (e) => {console.log("停止录制", e)}mediaRecorder.onpause = (e) => {console.log("暂停录制", e)}mediaRecorder.onresume = (e) => {console.log("继续录制", e)}}function handleRecorderPause() {mediaRecorder.pause()}function handleRecorderResume() {mediaRecorder.resume()}function handleRecorderStop() {mediaRecorder.stop()}// 下载 Blobfunction downloadBlob(blob) {// 将 Blob 对象转换成一个 URL 地址const url = URL.createObjectURL(blob)const a = document.createElement('a')// 设置 a 标签的 href 属性为刚刚生成的 URL 地址a.href = url// 设置 a 标签的 download 属性为文件名a.download = `${new Date().getTime()}.${blob.type.split('/')[1]}`// 模拟点击 a 标签a.click()// 释放 URL 地址URL.revokeObjectURL(url)}</script>
</body>
</html>
3. 下面的例子演示了保存(下载)录像流(录屏流也可)到本地
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><h1>录像</h1><video id="cameraVideo" autoplay playsinline muted></video><button id="recorderStartBtn" style="position: fixed;right: 0;top: 52%;">开始录制</button><button id="recorderPauseBtn" style="position: fixed;right: 0;top: 54%;">暂停录制</button><button id="recorderResumeBtn" style="position: fixed;right: 0;top: 56%;">继续录制</button><button id="recorderStopBtn" style="position: fixed;right: 0;top: 58%;">停止录制</button><script>// 录像const constraints = {audio: true,video: { width: 1280, height: 720 }}var cameraStreamgetLocalStream(constraints)// 获取本地音视频流async function getLocalStream(constraints) {// 获取媒体流cameraStream = await navigator.mediaDevices.getUserMedia(constraints)// 将媒体流设置到 video 标签上播放playLocalStream(cameraStream)}// 播放本地视频流function playLocalStream(stream) {const videoEl = document.getElementById('cameraVideo')videoEl.srcObject = stream}</script><script>// 录屏保存// 这里我们使用 MediaRecorder 来进行录制,它是一个用于录制媒体流的 API,它可以将媒体流中的数据进行录制,然后将录制的数据保存成一个文件。// 由于 MediaRecorder api 的对 mimeType 参数的支持是有限的。所以我们需要通过 MediaRecorder.isTypeSupported 来判断当前浏览器是否支持我们需要的 mimeTypefunction getSupportedMimeTypes() {const media = 'video'// 常用的视频格式const types = ['webm','mp4','ogg','mov','avi','wmv','flv','mkv','ts','x-matroska',]// 常用的视频编码const codecs = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h264']// 支持的媒体类型const supported = []const isSupported = MediaRecorder.isTypeSupported// 遍历判断所有的媒体类型types.forEach((type) => {const mimeType = `${media}/${type}`codecs.forEach((codec) =>[`${mimeType};codecs=${codec}`,`${mimeType};codecs=${codec.toUpperCase()}`,].forEach((variation) => {if (isSupported(variation)) supported.push(variation)}),)if (isSupported(mimeType)) supported.push(mimeType)})return supported}console.log(getSupportedMimeTypes())var mediaRecorderfunction getRecorder () {const kbps = 1024const Mbps = kbps * kbpsconst options = {audioBitsPerSecond: 128000,videoBitsPerSecond: 2500000,mimeType: 'video/webm; codecs="vp8,opus"',}// https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorderreturn new MediaRecorder(cameraStream, options)}// 开始录制var recorderStartBtn = document.getElementById('recorderStartBtn')recorderStartBtn.addEventListener('click', handleRecorderStart)// 暂停录制var recorderPauseBtn = document.getElementById('recorderPauseBtn')recorderPauseBtn.addEventListener('click', handleRecorderPause)// 继续录制var recorderResumeBtn = document.getElementById('recorderResumeBtn')recorderResumeBtn.addEventListener('click', handleRecorderResume)// 停止录制var recorderStopBtn = document.getElementById('recorderStopBtn')recorderStopBtn.addEventListener('click', handleRecorderStop)// 录制媒体流function handleRecorderStart() {mediaRecorder = getRecorder()// 开始录制媒体,这个方法调用时可以通过给timeslice参数设置一个毫秒值,如果设置这个毫秒值,那么录制的媒体会按照你设置的值进行分割成一个个单独的区块,而不是以默认的方式录制一个非常大的整块内容。mediaRecorder.start()// 调用它用来处理 dataavailable 事件,该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象.)mediaRecorder.ondataavailable = (e) => {// 将录制的数据合并成一个 Blob 对象// const blob = new Blob([e.data], { type: e.data.type })// 🌸重点是这个地方,我们不要把获取到的 e.data.type设置成 blob 的 type,而是直接改成 mp4const blob = new Blob([e.data], { type: 'video/mp4' })downloadBlob(blob)}mediaRecorder.onstart = (e) => {console.log("开始录制", e)}mediaRecorder.onstop = (e) => {console.log("停止录制", e)}mediaRecorder.onpause = (e) => {console.log("暂停录制", e)}mediaRecorder.onresume = (e) => {console.log("继续录制", e)}}function handleRecorderPause() {mediaRecorder.pause()}function handleRecorderResume() {mediaRecorder.resume()}function handleRecorderStop() {mediaRecorder.stop()}// 下载 Blobfunction downloadBlob(blob) {// 将 Blob 对象转换成一个 URL 地址const url = URL.createObjectURL(blob)const a = document.createElement('a')// 设置 a 标签的 href 属性为刚刚生成的 URL 地址a.href = url// 设置 a 标签的 download 属性为文件名a.download = `${new Date().getTime()}.${blob.type.split('/')[1]}`// 模拟点击 a 标签a.click()// 释放 URL 地址URL.revokeObjectURL(url)}</script>
</body>
</html>相关文章:
web获取媒体流
1. 下面例子演示了录屏和截图功能: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…...
代码随想录算法训练营第四十二天 | 01背包问题,你该了解这些、01背包问题,你该了解这些 滚动数组、 416. 分割等和子集
打卡第42天,搞搞01背包。 今日任务 01背包问题,你该了解这些!01背包问题,你该了解这些! 滚动数组416.分割等和子集 背包问题1.0 :0-1 背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weig…...
【Android】JNI静态与动态注册介绍
JNI的两种注册机制:静态注册和动态注册. 一、JNI介绍 JNI(Java Native Interface),即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C机型交互. 方式: 静态注册动态注册:需要提供Java中…...
【算法题解】22. 接雨水
这是一道 困难 题 题目来自: https://leetcode.cn/problems/trapping-rain-water/ 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,…...
集合详解之(四)集合的遍历
文章目录🐒个人主页🏅JavaSE系列专栏📖前言:🎀ArrayList集合forEach()方法遍历🎀for循环遍历(针对List集合)🪅增强for循环(也支持Set集合)&#x…...
【I2C】通用驱动i2c-dev分析
文章目录1. 前言2. i2c-dev驱动的注册过程3. open_i2c_dev函数分析4. set_slave_addr函数分析5. i2c_read_bytes函数分析1. 前言 前面分析i2c-tool测试工具就是基于drivers/i2c/i2c-dev.c驱动来实现的。i2c-dev驱动在加载时会遍历所有的I2C总线(i2c_bus_type)上所有注册的adap…...
用GPT-4写代码不用翻墙了?Cursor告诉你:可以~~
目录 一、介绍 二、使用方法 三、其他实例 1.正则表达式 2.自动化测试脚本 3.聊聊技术 一、介绍 Cursor主要功能是根据用户的描述写代码或者进行对话,对话的范围仅限技术方面。优点是不用翻墙、不需要账号。Cursor基于GPT模型,具体什么版本不祥&#…...
硬件语言Verilog HDL牛客刷题day03 时序逻辑部分
1.VL21 根据状态转移表实现时序电路 1.题目: 某同步时序电路转换表如下,请使用D触发器和必要的逻辑门实现此同步时序电路,用Verilog语言描述。 2.解题思路 2.1 首先同步时序电路 , 时钟上升沿触发, 复位信号rst 低电…...
day31 ● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和
● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和 在本次的题目中,我们使用了贪心算法来解决三个问题:分发饼干、摆动序列、最大子序和。这三个问题都可以使用贪心算法来解决,而且贪心算法的时间复杂度相对较低,能够在较短的…...
MobTech 秒验|本机号码一键登录会泄露隐私吗
本机号码一键登录是一种新型的应用登录方式,它可以利用运营商的数据网关认证能力,实现手机号免密登录,提高用户体验和转化率,降低验证成本和流失率。本机号码一键登录支持三大运营商号码认证,3秒内完成手机号验证&…...
2023年供销合作社研究报告
第一章 行业概况 1.1 供销合作社概述 中华全国供销合作总社,是中华人民共和国全国供销合作社的联合组织。中华全国供销合作总社的前身可以追溯到1949年11月成立的中央合作事业管理局。在新中国成立初期,供销合作社就基本形成了自上而下、覆盖全国的组织…...
【ansible】实施任务控制
目录 实施任务控制 一,循环(迭代)--- loop 1,利用loop----item循环迭代任务 2,item---loop循环案例 1,定义item循环列表 2,通过变量应用列表格式 3,字典列表(迭代嵌套子…...
49天精通Java,第11天,java接口和抽象类的异同,default关键字
目录一、什么是接口二、接口的特点三、接口和类的区别四、接口和抽象类的区别五、接口的声明方式六、default默认方法大家好,我是哪吒。 一、什么是接口 Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的…...
JAVA练习99-逆波兰表达式求值
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、题目-逆波兰表达式求值 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示:这里可以添加本文要记录的大概内容: 4月5…...
恶意软件、恶意软件反杀技术以及反病毒技术的详细介绍
1.恶意软件简单介绍恶意软件是指在计算机系统上执行恶意任务的病毒、蠕虫和特洛伊木马的程序,通过破坏软件进程来实施控制。腾讯移动安全实验室发布的数据显示,恶意软件由多种威胁组成,会不断弹出,所以需要采取多种方法和技术来进…...
【数据库运维】mysql备份恢复练习
目录 数据库备份,数据库为school,素材如下 1.创建student和score表 2.为student表和score表增加记录 3.备份数据库school到/backup目录 4.备份MySQL数据库为带删除表的格式,能够让该备份覆盖已有数据库而不需要手动删除原有数据库 5.直接将My…...
刷题30-对称的二叉树
对称的二叉树 思路:用递归,首先明白递归中止的条件是什么 搬用别人的看法: 做递归思考三步: 1.递归的函数要干什么? 函数的作用是判断传入的两个树是否镜像。 输入:TreeNode left, TreeNode right 输出…...
精选简历模板
1.应届生通用简历模板(.docx) 适用于应届生找工作的学生群体 https://download.csdn.net/download/weixin_43042683/87652099https://download.csdn.net/download/weixin_43042683/87652099 部分缩略图如下: 2.研究生通用简历模板(.docx)…...
蓝桥杯嵌入式第十三届客观题解析
文章目录 前言一、题目1二、题目2三、题目3四、题目4五、题目5六、题目6七、题目7八、题目8九、题目9十、题目10总结前言 本篇文章将带大家来学习蓝桥杯嵌入式的客观题了,蓝桥杯嵌入式的客观题涉及到模电,数电,单片机等知识,需要非常扎实的基础,客观题不能急于求成只能脚…...
【Redis】线程问题
文章目录单线程版本演化工作流程为什么逐渐又加入了多线程特性?影响Redis性能的主要因素->网络I/O多线程工作流程Unix网络编程中的五种I/O模型I/O多路复用工作原理:select、poll、epoll为什么Redis快单线程与多线程的比较配置文件开启多线程单线程 版本演化 Re…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
