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…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...