当前位置: 首页 > news >正文

webRtc生产环境实用方法

最近做了几个项目发现多个项目反反复复会出现几个高频的需求, 都依赖于获取系统采集设备和指定设备id获取媒体流;为了不在反复书写总结2个公用方法;

检索当前系统所有可用设备
/*** 检索当前系统所有可用设备* @returns {*  audioInputOptions: Array [{value: string, label: string}]; 说明: 音频输入设备*  videoInputOptions: Array [{value: string, label: string}]; 说明: 视频输入设备*  audiooutputOptions: Array [{value: string, label: string}]; 说明: 音频输出设备*  isAudioInput: Boolean 是否有音频输入设备;*  isVideoInput: Boolean 是否视频输入设备;*  isAudioOutput: Boolean 是否有音频输出设备;*  audioDeviceIds: [string] // 音频DevIds;*  videoDeviceIds: [string] // 视频DevIds;* }
*/
export function GetSystemEnumerateDevices() {return navigator.mediaDevices.enumerateDevices().then(function(devices) {const audioInputOptions = [];const videoInputOptions = [];const audiooutputOptions = [];let isAudioInput = false;let isVideoInput = false;let isAudioOutput = false;const audioDeviceIds = [];const videoDeviceIds = [];let tmp = devices.filter(function(device) {/* *  device.kind: 目前有3个值;* 'audioinput': 表示麦克风;* 'videoinput': 表示摄像头,没有安装摄像头时部分品牌机({label: 'screen-capture-recorder'},这也就是为啥没摄像头就会传会桌面画面);* 'audiooutput': 扬声器/听筒;*/// 判断是否有摄像头设备if (device.kind === 'videoinput') {if (!isVideoInput) {isVideoInput = true;}// 针对个别电脑优先选择摄像头设备if (device.label === 'screen-capture-recorder') {videoDeviceIds.push(device.deviceId);videoInputOptions.push({ value: device.deviceId, label: device.label });} else {videoDeviceIds.unshift(device.deviceId);videoInputOptions.unshift({ value: device.deviceId, label: device.label });}};// 判断是否有麦克风设备if (device.kind === 'audioinput') {if (!isAudioInput) {isAudioInput = true;}audioInputOptions.push({ value: device.deviceId, label: device.label });audioDeviceIds.push(device.deviceId);};// 音频输出设备if (device.kind === 'audiooutput') {if (!isAudioOutput) { isAudioOutput = true };audiooutputOptions.push({ value: device.deviceId, label: device.label });};return false;});tmp = null;// console.log('音频输入设备', audioInputOptions);// console.log('视频输入设备', videoInputOptions);// console.log('音频输出设备', audiooutputOptions);// console.log('是否有音频输入设备', isAudioInput);// console.log('是否视频输入设备', isVideoInput);// console.log('是否有音频输出设备', isAudioOutput);// console.log('音频DevIds', audioDeviceIds);// console.log('视频DevIds', videoDeviceIds);return {audioInputOptions, videoInputOptions, audiooutputOptions, isAudioInput, isVideoInput, isAudioOutput, audioDeviceIds, videoDeviceIds}});
}
设备id获取MediaStream
export function DeviceidToMediaStream(devId: string) {return new Promise((resolve, reject) => {if (typeof devId != 'string') {return reject({code: 0, message: '设备id必须是字符串'});}GetSystemEnumerateDevices().then((devInfo) => {const {audioDeviceIds, videoDeviceIds} = devInfo;const constraints = {audio: false,video: false,}let mediaType = 1;if (audioDeviceIds.includes(devId)) {constraints.audio = true;delete constraints.video;}if (videoDeviceIds.includes(devInfo)) {mediaType = 2;constraints.video = true;delete constraints.audio;}let isHas = Object.values(constraints).filter((item) => {return item;})if (isHas.length === 0) {return reject({code: 0, message: '没有找到采集设备'});}navigator.mediaDevices.getUserMedia(constraints).then((locStream) => {return resolve(locStream);}).catch((error) => {const cause = error.message;let message = mediaType === 1 ? '麦克风启动失败' : '摄像头启动失败';if (cause === 'Permission denied') {message = '媒体采集权限被拒绝';} else if (cause === 'Requested device not found') {message = mediaType === 1 ? '未检测到麦克风' : '未检测到采集设备';} else if (cause === 'Could not start video source') {// QQ浏览器如果摄像头被其它应用占用会报错: Could not start video sourcemessage = '摄像头启动失败,可能被其它应用占用或被拔出';} else if (cause === 'Device in use') {// 谷歌浏览器摄像头被其它占用用会报错 'Device in use'message = '设备被另一个应用或进程占用'} else if (cause === 'Starting videoinput failed') {// 火狐摄像头被占用会报错 'Starting videoinput failed'message = '设备启动失败,可能被被另一个应用或进程占用'};return reject({code: 0, message});})})})
}
MediaStream获取视频的宽度和高度
export function GetMediaStreamVideoWidthHeight(streams: MediaStream) {return new Promise((resolve, reject) => {const videoTrackArr = streams.getVideoTracks();if (videoTrackArr.length === 0) {reject({code:0, message:'没有视频轨道'});return;}const tmp = document.createElement('video');tmp.srcObject = streams;tmp.onloadedmetadata = function() {resolve({width: tmp.videoWidth, height: tmp.videoHeight});tmp.srcObject = null;tmp.remove();  };tmp.onerror = function() {  reject({ code: 1, message: '视频加载失败' });  tmp.srcObject = null;  tmp.remove();  };  })
}
判断音频录入设备是否存在
export function JudgeAudioInputDevIdIsExist(devId: string) {return new Promise((resolve, reject) => {if (typeof devId != 'string') {return reject({code: 0, message: '设备id必须是字符串'});};GetSystemEnumerateDevices().then((devInfo) => {const {audioDeviceIds} = devInfo;if (!audioDeviceIds.includes(devId)) {return reject({code: 0, message: '设备id不存在'});};resolve({code: 1, message: devId});});})
}
判断视频录入设备是否存在
export function JudgeVideoInputDevIdIsExist(devId: string) {return new Promise((resolve, reject) => {if (typeof devId != 'string') {return reject({code: 0, message: '设备id必须是字符串'});};GetSystemEnumerateDevices().then((devInfo) => {const {videoDeviceIds} = devInfo;if (!videoDeviceIds.includes(devId)) {return reject({code: 0, message: '设备id不存在'});}resolve({code: 1, message: devId});});})
}
拦截音视频权限可能会报错的问题
/*** 处理音视频权限错误*  mediaType: 1_音频; 2_视频;
*/
export function BackGetUserMediaError(cause: string, mediaType: number) {let message = '';let mediaName= mediaType === 1 ? '麦克风' : '摄像头'if (cause === 'Permission denied') {message = `${mediaName}获取权限被拒绝,请检查浏览器是否开启使用权限`;} else if (cause === 'Requested device not found') {message = `未检测到${mediaName}设备`;} else if (cause === 'Could not start video source') {/* 1.QQ浏览器_设备被占用会走这里; 2.QQ浏览器_系统设置关闭摄像头应用使用权限; 3.谷歌浏览器_win7或低版本谷歌会存在临时拔掉摄像头但枚举可以拿到设备调用后会报错*/message = '摄像头启动失败,可能被其它应用占用或被拔出或系统关闭了摄像头调用权限';} else if (cause === 'Device in use') {// 谷歌浏览,假如设备被占用的时候会报错message = `${mediaName}设备被另一个应用或进程占用`;} else if (cause === 'Starting videoinput failed') {// 火狐浏览器, 假如设备被占用的时候会报错message = '摄像头启动失败,可能被被另一个应用或进程占用';} else if (cause === 'Failed to allocate videosource') {// 火狐-win10系统设置关闭摄像头应用使用权限message = '摄像头启动失败,可能系统关闭了摄像头使用权限';} else if (cause === 'Permission denied by system') {/*  存在麦克风摄像头混用报错1.谷歌浏览器_win10系统设置关闭摄像头应用使用权限;2.谷歌浏览器_win10系统设置关闭麦克风应用使用权限;3.QQ浏览器_系统win10系统设置关闭麦克风使用权限*/message = `${mediaName}启动失败,可能系统关闭了设备使用权限`;} else {message = `${mediaName}启动失败`};return {code: 0, cause, message};
}
解除video占用
clearVideoSrcObject(id:string) {const videoElement = document.getElementById('mettingVideoRef');if (videoElement) {if (videoElement.srcObject) {videoElement.srcObject.getTracks().forEach((track: any) => track.stop());videoElement.srcObject = null;}}    
}

相关文章:

webRtc生产环境实用方法

最近做了几个项目发现多个项目反反复复会出现几个高频的需求, 都依赖于获取系统采集设备和指定设备id获取媒体流;为了不在反复书写总结2个公用方法; 检索当前系统所有可用设备 /*** 检索当前系统所有可用设备* returns {* audioInputOption…...

Java String、StringBuffer

构造方法 通过字符数组构造,结果abc 通过字节数组构造,结果abc //把字符串转化为字节数组 当前代码编译环境为UTF-8,出现异常时,直接抛出异常即可。mainthrows UnsupportedEncodingException 编译环境为UTF-8,但是运用gb2312这个…...

LangChain调用tool集的原理剖析(包懂)

一、需求背景 在聊天场景中,针对用户的问题我们希望把问题逐一分解,每一步用一个工具得到分步答案,然后根据这个中间答案继续思考,再使用下一个工具得到另一个分步答案,直到最终得到想要的结果。 这个场景非常匹配la…...

如何正确使用数字化仪前端信号调理?(一)

一、前言 板卡式的数字转换器和类似测量仪器,比如图1所示的德思特TS-M4i系列,都需要为各种各样的特性信号与内部模数转换器(ADC)的固定输入范围做匹配。 图1:德思特TS-M4i系列高速数字化仪,包括2或4通道版…...

实验5 流程图和盒图ns图

一、实验目的 通过绘制流程图和盒图,熟练掌握流程图和盒图的基本原理。 能对简单问题进行流程图和盒图的分析,独立地完成流程图和盒图设计。 二、实验项目内容(实验题目) 1、用Microsoft Visio绘制下列程序的程序流程图。 若…...

[Java、Android面试]_18_详解Handler机制 常见handler面试题(非常重要,非常高频!!)

本人今年参加了很多面试,也有幸拿到了一些大厂的offer,整理了众多面试资料,后续还会分享众多面试资料。 整理成了面试系列,由于时间有限,每天整理一点,后续会陆续分享出来,感兴趣的朋友可关注收…...

国内开通gpt会员方法

ChatGPT镜像 今天在知乎看到一个问题:“平民不参与内测的话没有账号还有机会使用ChatGPT吗?” 从去年GPT大火到现在,关于GPT的消息铺天盖地,真要有心想要去用,途径很多,别的不说,国内GPT的镜像…...

使用 Meltano 将数据从 Snowflake 导入到 Elasticsearch:开发者之旅

作者:来自 Elastic Dmitrii Burlutskii 在 Elastic 的搜索团队中,我们一直在探索不同的 ETL 工具以及如何利用它们将数据传输到 Elasticsearch,并在传输的数据上实现 AI 助力搜索。今天,我想与大家分享我们与 Meltano 生态系统以及…...

第24次修改了可删除可持久保存的前端html备忘录:文本编辑框不再隐藏,又增加了哔哩哔哩搜索和必应搜索

第24次修改了可删除可持久保存的前端html备忘录:文本编辑框不再隐藏&#xff0c;又增加了哔哩哔哩搜索和必应搜索. <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"…...

二极管分类及用途

二极管分类及用途 通用开关二极管 特点&#xff1a;电流小&#xff0c;工作频率高 选型依据&#xff1a;正向电流、正向压降、功耗&#xff0c;反向最大电压&#xff0c;反向恢复时间&#xff0c;封装等 类型&#xff1a;BAS316 ; IN4148WS 应用电路: 说明&#xff1a;应用…...

文献阅读:Viv:在 web 上多尺度可视化高分辨率多重生物成像数据

文献介绍 「文献题目」 Viv: multiscale visualization of high-resolution multiplexed bioimaging data on the web 「研究团队」 Nils Gehlenborg&#xff08;美国哈佛医学院&#xff09; 「发表时间」 2022-05-11 「发表期刊」 Nature Methods 「影响因子」 47.9 「DOI…...

SpringBoot整合Logback日志框架

Logback 是一个灵活而高效的日志框架&#xff0c;它是由 Ceki Glc 开发的&#xff0c;也是 Log4j 的创建者之一。Logback 旨在成为 Log4j 的替代品&#xff0c;并提供了一系列强大的功能和性能改进。 以下是 Logback 的一些主要特点和功能&#xff1a; 模块化结构&#xff1a;…...

知识图谱与人工智能:携手共进

知识图谱与人工智能&#xff1a;携手共进 一、引言&#xff1a;知识图谱与人工智能的融合 在这个数据驱动的时代&#xff0c;知识图谱与人工智能&#xff08;AI&#xff09;之间的融合不仅是技术发展的必然趋势&#xff0c;也是推动各行各业创新的关键。知识图谱&#xff0c;作…...

全栈的自我修养 ———— react实现滑动验证

实现滑动验证 展示依赖实现不借助create-puzzle借助create-puzzle 展示 依赖 npm install rc-slider-captcha npm install create-puzzleapi地址 实现 不借助create-puzzle 需要准备两张图片一个是核验图形&#xff0c;一个是原图------> 这个方法小编试了后感觉比较麻烦…...

<<、>>和>>>

1.左移操作符(<<&#xff09;: 左移操作符将数字的二进制表示向左移动指定的位数。右侧空出的位用0填充。左移操作相当于乘以2的幂。 例如&#xff1a; int num 4; // 二进制表示为 0100 int shifted num << 1; // 结果为 8&#xff0c;二进制表示为 10002.带…...

【C++进阶】RAII思想&智能指针

智能指针 一&#xff0c;为什么要用智能指针&#xff08;内存泄漏问题&#xff09;内存泄漏 二&#xff0c;智能指针的原理2.1 RAII思想2.2 C智能指针发展历史 三&#xff0c;更靠谱的shared_ptr3.1 引用计数3.2 循环引用3.3 定制删除器 四&#xff0c;总结 上一节我们在讲抛异…...

探索量子计算:打开未来技术的大门

在科技领域&#xff0c;每一次技术革命都能开启新的可能性&#xff0c;推动人类社会进入一个新的时代。当前&#xff0c;量子计算作为一种前沿技术&#xff0c;正引领着下一轮科技革命的浪潮。本文将深入探索量子计算的奥秘&#xff0c;解析其工作原理&#xff0c;并通过一个简…...

C++11 设计模式2. 简单工厂模式

简单工厂&#xff08;Simple Factory&#xff09;模式 我们从实际例子出发&#xff0c;来看在什么情况下&#xff0c;应用简单工厂模式。 还是以一个游戏举例 //策划&#xff1a;亡灵类怪物&#xff0c;元素类怪物&#xff0c;机械类怪物&#xff1a;都有生命值&#xff0…...

RabbitMQ-死信队列常见用法

目录 一、什么是死信 二、什么是死信队列 ​编辑 三、第一种情景&#xff1a;消息被拒绝时 四、第二种场景&#xff1a;. 消费者发生异常&#xff0c;超过重试次数 。 其实spring框架调用的就是 basicNack 五、第三种场景&#xff1a; 消息的Expiration 过期时长或队列TTL…...

2024/4/14周报

文章目录 摘要Abstract文献阅读题目创新点CROSSFORMER架构跨尺度嵌入层&#xff08;CEL&#xff09;CROSSFORMER BLOCK长短距离注意&#xff08;LSDA&#xff09;动态位置偏置&#xff08;DPB&#xff09; 实验 深度学习CrossFormer背景维度分段嵌入&#xff08;DSW&#xff09…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

DiscuzX3.5发帖json api

参考文章&#xff1a;PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下&#xff0c;适配我自己的需求 有一个站点存在多个采集站&#xff0c;我想通过主站拿标题&#xff0c;采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

Cursor AI 账号纯净度维护与高效注册指南

Cursor AI 账号纯净度维护与高效注册指南&#xff1a;解决限制问题的实战方案 风车无限免费邮箱系统网页端使用说明|快速获取邮箱|cursor|windsurf|augment 问题背景 在成功解决 Cursor 环境配置问题后&#xff0c;许多开发者仍面临账号纯净度不足导致的限制问题。无论使用 16…...

Qt学习及使用_第1部分_认识Qt---Qt开发基本流程

前言 学以致用,通过QT框架的学习,一边实践,一边探索编程的方方面面. 参考书:<Qt 6 C开发指南>(以下称"本书") 标识说明:概念用粗体倾斜.重点内容用(加粗黑体)---重点内容(红字)---重点内容(加粗红字), 本书原话内容用深蓝色标识,比较重要的内容用加粗倾…...

python打卡day47

昨天代码中注意力热图的部分顺移至今天 知识点回顾&#xff1a; 热力图 作业&#xff1a;对比不同卷积层热图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import D…...