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…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...