当前位置: 首页 > 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…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...