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备忘录:文本编辑框不再隐藏,又增加了哔哩哔哩搜索和必应搜索. <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"…...
二极管分类及用途
二极管分类及用途 通用开关二极管 特点:电流小,工作频率高 选型依据:正向电流、正向压降、功耗,反向最大电压,反向恢复时间,封装等 类型:BAS316 ; IN4148WS 应用电路: 说明:应用…...
文献阅读:Viv:在 web 上多尺度可视化高分辨率多重生物成像数据
文献介绍 「文献题目」 Viv: multiscale visualization of high-resolution multiplexed bioimaging data on the web 「研究团队」 Nils Gehlenborg(美国哈佛医学院) 「发表时间」 2022-05-11 「发表期刊」 Nature Methods 「影响因子」 47.9 「DOI…...
SpringBoot整合Logback日志框架
Logback 是一个灵活而高效的日志框架,它是由 Ceki Glc 开发的,也是 Log4j 的创建者之一。Logback 旨在成为 Log4j 的替代品,并提供了一系列强大的功能和性能改进。 以下是 Logback 的一些主要特点和功能: 模块化结构:…...
知识图谱与人工智能:携手共进
知识图谱与人工智能:携手共进 一、引言:知识图谱与人工智能的融合 在这个数据驱动的时代,知识图谱与人工智能(AI)之间的融合不仅是技术发展的必然趋势,也是推动各行各业创新的关键。知识图谱,作…...
全栈的自我修养 ———— react实现滑动验证
实现滑动验证 展示依赖实现不借助create-puzzle借助create-puzzle 展示 依赖 npm install rc-slider-captcha npm install create-puzzleapi地址 实现 不借助create-puzzle 需要准备两张图片一个是核验图形,一个是原图------> 这个方法小编试了后感觉比较麻烦…...
<<、>>和>>>
1.左移操作符(<<): 左移操作符将数字的二进制表示向左移动指定的位数。右侧空出的位用0填充。左移操作相当于乘以2的幂。 例如: int num 4; // 二进制表示为 0100 int shifted num << 1; // 结果为 8,二进制表示为 10002.带…...
【C++进阶】RAII思想&智能指针
智能指针 一,为什么要用智能指针(内存泄漏问题)内存泄漏 二,智能指针的原理2.1 RAII思想2.2 C智能指针发展历史 三,更靠谱的shared_ptr3.1 引用计数3.2 循环引用3.3 定制删除器 四,总结 上一节我们在讲抛异…...
探索量子计算:打开未来技术的大门
在科技领域,每一次技术革命都能开启新的可能性,推动人类社会进入一个新的时代。当前,量子计算作为一种前沿技术,正引领着下一轮科技革命的浪潮。本文将深入探索量子计算的奥秘,解析其工作原理,并通过一个简…...
C++11 设计模式2. 简单工厂模式
简单工厂(Simple Factory)模式 我们从实际例子出发,来看在什么情况下,应用简单工厂模式。 还是以一个游戏举例 //策划:亡灵类怪物,元素类怪物,机械类怪物:都有生命值࿰…...
RabbitMQ-死信队列常见用法
目录 一、什么是死信 二、什么是死信队列 编辑 三、第一种情景:消息被拒绝时 四、第二种场景:. 消费者发生异常,超过重试次数 。 其实spring框架调用的就是 basicNack 五、第三种场景: 消息的Expiration 过期时长或队列TTL…...
2024/4/14周报
文章目录 摘要Abstract文献阅读题目创新点CROSSFORMER架构跨尺度嵌入层(CEL)CROSSFORMER BLOCK长短距离注意(LSDA)动态位置偏置(DPB) 实验 深度学习CrossFormer背景维度分段嵌入(DSW)…...
MySQL 社区版 安装总结
很早就安装过MySQL,没有遇到过什么问题,直接next就行了,这次在新电脑上安装却遇到了一些问题,记录一下。 安装的是MySQL社区版,下载地址是www.mysql.com,进入后选择DOWNLOAD页面,选择MySQL Com…...
二叉排序树的增删改查(java版)
文章目录 1. 基本节点2. 二叉排序树2.1 增加节点2.2 查找(就是遍历)就一起写了吧2.3 广度优先遍历2.4 删除(这个有点意思)2.5 测试样例 最后的删除,目前我测试的是正确的 1. 基本节点 TreeNode: class TreeNode{pri…...
linux下coredump问题的定位分析方法
(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 参考:https://blog.csdn.net/m0_73698480/article/details/130077852 最近定位了一段时间linux下的崩溃问题,又收集了一些思路,特整理记录一下。 常见coredump定位方法是:…...
第十届蓝桥杯省赛真题(C/C++大学B组)
目录 试题 A: 组队 试题 B: 年号字串 试题 C: 数列求值 试题 D: 数的分解 试题 E: 迷宫 试题 F: 特别数的和 试题 G:完全二叉树的权值 试题 H:等差数列 试题 I:后缀表达式(不一定对) 试题 J:灵能…...
Scrapy 爬取m3u8视频
Scrapy 爬取m3u8视频 【一】效果展示 爬取ts文件样式 合成的MP4文件 【二】分析m3u8文件路径 视频地址:[在线播放我独自升级 第03集 - 高清资源](https://www.physkan.com/ph/175552-8-3.html) 【1】找到m3u8文件 这里任务目标很明确 就是找m3u8文件 打开浏览器…...
LVGL简单记录
1、 vs中代码旁边有个小锁删除git 2、Visual Studio 试图编译已删除的文件, 如果这个文件也是你不再需要编译的文件,且已经从文件系统中删除,你需要从 .vcxproj 文件中移除或者注释掉这一行,以停止Visual Studio尝试去编译一个不…...
计算机网络——ARP协议
前言 本博客是博主用于复习计算机网络的博客,如果疏忽出现错误,还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结,讲的非常好。 可以先去看一篇视频,再来参考这篇笔记(或者说直接偷走)。 …...
【C++]C/C++的内存管理
这篇博客将会带着大家解决以下几个问题 1. C/C内存分布 2. C语言中动态内存管理方式 3. C中动态内存管理 4. operator new与operator delete函数 5. new和delete的实现原理 6. 定位new表达式(placement-new) 1. C/C内存分布 我们先来看下面的一段代码和相关问题 int global…...
深入理解计算机网络分层结构
一、 为什么要分层? 计算机网络分层的主要目的是将复杂的网络通信过程分解为多个相互独立的层次,每个层次负责特定的功能。这样做有以下几个好处: 模块化设计:每个层次都有清晰定义的功能和接口,使得网络系统更易于设…...
亚马逊云科技CTO带你学习云计算降本增效秘诀
2023亚马逊云科技一年一度的重磅春晚--Re:invent上有诸多不同话题的主题Keynote,这次小李哥带大家复盘来自亚马逊CTO: Wener博士的主题演讲: 云架构节俭之道1️⃣节俭对于云计算为什么重要? ▶️企业基础设施投入大,利用好降本策略可以减少巨…...
快速做网站的软件/市场seo是什么意思
双向链表的插入与删除 双向链表的结点定义 #define ElemType int //双向链表的存储结构 typedef struct DuLNode {ElemType data;DuLNode *prior;DuLNode *next; }DuLNode, *DuLinkList;双向链表的结点插入 画图表示,并在上述双向链表中一个已知结点p之后插入一…...
wordpress 中文客户端/客服外包
Docker三大编排工具: Docker Compose:是用来组装多容器应用的工具,可以在 Swarm集群中部署分布式应用。 Docker Machine:是支持多平台安装Docker的工具,使用 Docker Machine,可以很方便地在笔记本、云平台…...
企业门户网站开发代码/湘潭关键词优化服务
美国国家健康与营养调查( NHANES, National Health and Nutrition Examination Survey)是一项基于人群的横断面调查,旨在收集有关美国家庭人口健康和营养的信息。 地址为:https://wwwn.cdc.gov/nchs/nhanes/Default.aspx 既往我们…...
商丘网站制作费用/平台优化是指什么
品味电视,品味人生!很少看电视剧的我,最近看了一步名为“闪婚”的电视剧,其情节让人深思。爱情没钱就没幸福吗,一个穷光蛋就找不到爱情吗。现实的社会,现实的电视剧。是谁造就了这个社会风气,很…...
合肥网站开发培训/推广商
『TensorFlow』读书笔记_ResNet_V2 对比之前的复杂版本,这次的torch实现其实简单了不少,不过这和上面的代码实现逻辑过于复杂也有关系。 一、PyTorch实现 # Author : hellcat # Time : 18-3-2""" import os os.environ["CUDA_VISI…...
ASPnet动态网站开发教程试卷/株洲企业seo优化
第一次见到iRobot创始人Colin Angle,看到的是他很有礼貌的微笑,以及经过前几轮采访后的浅浅倦意。此次来到中国,Colin Angle的任务是将此前在美国已经亮相的iRobot扫地机器人新品Roomba i7带到中国。在全程一个多小时的交流过程中,…...