拿下奇怪的前端报错:某些多摄手机拉取部分摄像头视频流会导致应用崩溃,该如何改善呢?

 现在有些手机更新的很激进,但是却没有很好的实现web规范,不支持facingMode配置来控制前后摄像头,只能根据序号切换,但拉取到某些设备的流会导致应用崩溃,这里就教一招如何尽可能的改善用户体验

至少不至于次次都崩溃,最多崩溃三次后就不崩了(╮(╯▽╰)╭)

1 问题上下文

  • 四摄手机:UA携带ELS-AN00标识
  • 混合应用:基于webview开发
  • 不支持facingMode切换前后摄像头
    • 支持的话就不要用这种方法了,如果做的是面向大众用户,需要多测试不同型号

2. 问题描述 - 非主摄拉流则崩溃

因为不支持facingMode配置来控制前后摄像头,所以只能采用枚举设备id,切换序号的方式来实现相机切换,但一旦拉流时选择的id是非主摄(后摄的子镜头),则会导致应用崩溃退出

3. 解决过程

这个问题比较棘手,触发崩溃需要重启而且导致切换相机功能不可用。崩溃也是比较严重的问题,而且切换必会触发崩溃,又由于是设备不支持对应api,于是只能想出一个临时的解决办法

优选一对index,经测试是 序号 0,4的摄像头--但有测试出某多摄并非这两个序号,只能是最大程度保证体验:不让应用在会导致崩溃的相机上崩溃2次

3.1 方案设计

本地缓存两个数组:可用indexs、失败indexs,每次拉流前标记选择的index,拉流成功删除这个标记并加入到可用列表,开机时检测有无这个标记,有的话加入失败列表。

3.2 初始化UA匹配

需要使用设备调试找出UA的关键字,并测试出不会崩溃的序号作为备选项

const fallbackMap = new Map<string, number[]>();
// 加入需要执行回退方案的map
fallbackMap.set("ELS-AN00", [0, 4]);
let preferFallback = false;
let preferIndexs = [0, 4];
{const ua = navigator.userAgent;for (const key of fallbackMap.keys()) {if (ua.includes(key)) {preferFallback = true;preferIndexs = fallbackMap.get(key)!;console.info(`hite ${key}`);break;}}
}
// 如果ua匹配上,查看是否有需要标记失败的
if (shouldCameraFallback()) {markDeviceIdTested();
}

3.3 更新测试完的设备标记

  • 场景一,应用启动时,curId为空,通过读取缓存是否存在正在检测的id,如果有则表明这个index的摄像头导致了崩溃(当然还有一种情况,就是用户拉流过程手动退出应用了)
  • 场景二,拉流成功,调用这个方法标记成功的设备id
export const markDeviceIdTested = async (curId?: string) => {const vlist = await getVideoDevices();if (!vlist) {return;}const checkingDevice = localStorage.getItem("capture_checking_device");const sucList = getSavedList("capture_success_list",preferIndexs) as number[];const cidx = vlist.findIndex((v) => v.deviceId === curId);if ((checkingDevice && checkingDevice === curId) ||(!checkingDevice && curId)) {if (!sucList.includes(cidx)) {sucList.push(cidx);localStorage.setItem("capture_success_list", JSON.stringify(sucList));}localStorage.removeItem("capture_checking_device");return;}if (!checkingDevice) {return;}const failList = getSavedList("capture_fail_list", []) as number[];failList.push(cidx);localStorage.setItem("capture_fail_list", JSON.stringify(failList));
};

3.4 一些辅助方法

// 检查是否需要兼容模式 - 不然可用facingMode控制相机切换
export const shouldCameraFallback = () => {return !!localStorage.getItem("camera_fallback_used") || preferFallback;
}
// 根据限制获取设备id
export const getDeviceId = (v?: ConstrainDOMString): string | undefined => {return Array.isArray(v)? v[0]: typeof v === "object"? getDeviceId(v.exact): v;
}
// 拉流前标记正在检测的设备id
export const markDeviceIdTest = (deviceId: string) => {localStorage.setItem("capture_checking_device", deviceId);
}// 从localstorage读取列表数据
function getSavedList (key: string, def: any) {const str = localStorage.getItem(key);try {const e = JSON.parse(str || "");return e || def;} catch (error) {return def;}
}
// 获取摄像头列表
async function getVideoDevices ()  {// **************下面的内容需要自己调整,设备枚举前要先获取权限不然获取不到设备列表await permission.checkVideoPermission();const list = await navigator.mediaDevices.enumerateDevices();return list.filter((e) => e.kind === "videoinput").sort();
}

3.5 获取下一个设备id - 切换相机的实现

export const getNextDeviceId = async (curId: string) => {// ***** 需要自己调整下面的相机权限获取方法await permission.checkVideoPermission();const list = await navigator.mediaDevices.enumerateDevices();const vlist = list.filter((e) => e.kind === "videoinput");let sucList = getSavedList("capture_success_list", preferIndexs) as number[];let cidx = vlist.findIndex((v) => v.deviceId === curId);if (cidx === -1) {cidx = 0;localStorage.setItem("capture_success_list", JSON.stringify(preferIndexs));sucList = preferIndexs;}if (sucList.includes(cidx)) {// 如果成功列表有两个摄像头了,就用这俩,就不会发生崩溃啦!!if (sucList.length > 1) {const lidx = sucList.indexOf(cidx);const nSucIdx = lidx === sucList.length - 1 ? 0 : lidx + 1;const nIdx = sucList[nSucIdx];const dev = vlist[nIdx];console.log(`use sucList ${nSucIdx} - realIndex ${nIdx} `);return dev.deviceId;}}const failList = getSavedList("capture_fail_list", []) as number[];// 检查下一个可用idxconst ne = vlist.find((e, idx) => !failList.includes(idx) && e.deviceId !== curId);return ne?.deviceId;
};

 4 使用示例

4.1 切换相机参数更新

核心就是兼容模式则使用deviceId,否则使用facingMode

const updateSwitchParams = async () => {if (!shouldCameraFallback()) {facingMode = facingMode === "user" ? "environment" : "user";return;}await markDeviceIdTested(deviceId);deviceId = (await getNextDeviceId(deviceId)) || "";if (!deviceId) {d.canSwitch = false;}
};

 4.2 抽离拉流配置方法

兼容模式时用deviceId控制拉流的摄像头,其它则使用facingMode

const getMediaConfig = () => {const video = {width: { min: 640, ideal: 1280 },height: { min: 480, ideal: 720 },} as any;const audio = mode === 2;if (shouldCameraFallback()) {if (deviceId) {video.deviceId = deviceId;}markDeviceIdTest(deviceId);return {video,audio,};}if (facingMode) {video.facingMode = facingMode;}if (deviceId && deviceIdFaceMode === facingMode) {video.deviceId = deviceId;}return {video,audio,};
}

4.3 拉流

就是简单的使用getUserMedia,不过参数是动态获取的,当使用特定设备时会使用回退设置。切换摄像头,只需要调用updateSwitchParams 即可

const config = getMediaConfig();console.log("camera", config);
return navigator.mediaDevices.getUserMedia(config)

 5 小结

本文介绍的是一个很特殊的场景的一个解决办法,因为现在有些手机挺激进的,三摄像头四摄像头,但是却没有很好的实现web规范,导致会出一些问题,若是都根据规范来实现接口,我们的工作会轻松挺多!

因为心里想着下班放假,就不补充太多细节了,有疑问的可以留言~

YU.H

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/55543.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

普通人怎么才能实现时间自由和财富自由?

在快节奏的现代生活中&#xff0c;每个人都怀揣着对时间自由和财富自由的向往。尤其是对于普通人而言&#xff0c;这不仅仅是一种梦想&#xff0c;更是一种可以通过智慧和努力实现的生活方式。副业&#xff0c;作为主业之外的另一片天地&#xff0c;正逐渐成为实现这一梦想的重…

npx create-react-app react-basic 创建react 项目报错

npx create-react-app创建react 项目报错 npm error code ERESOLVE npm error ERESOLVE could not resolve npm error npm error While resolving: react-basic0.1.0 npm error Found: reactundefined npm error node_modules/react npm error react"*" from the …

63.5 注意力提示_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录注意力提示生物学中的注意力提示查询、键和值注意力的可视化使用 show_heatmaps 显示注意力权重代码示例 代码解析结果 小结练习 注意力提示 &#x1f3f7;sec_attention-cues 感谢读者对本书的关注&#xff0c;因为读者的注意力是一种稀缺…

Angular ng-state script 元素的生成机制介绍

ng-state 的生成过程是在 Angular SSR 中非常关键的部分。为了让客户端能够接管服务器渲染的页面状态&#xff0c;Angular 在服务器端需要将应用的当前状态保存下来&#xff0c;并将其嵌入到返回的 HTML 中。这样&#xff0c;客户端在接管时就可以直接使用这些状态&#xff0c;…

动手学深度学习(李沐)PyTorch 第 7 章 现代卷积神经网络

7.1 深度卷积神经网络&#xff08;AlexNet&#xff09; 在计算机视觉中&#xff0c;直接将神经网络与其他机器学习方法进行比较也许不公平。这是因为&#xff0c;卷积神经网络的输入是由原始像素值或是经过简单预处理&#xff08;例如居中、缩放&#xff09;的像素值组成的。但…

深圳楼市国庆“狂欢”:从“冷清”到“火爆”,谁导演了这场大戏

你知道吗&#xff1f;深圳那楼市&#xff0c;前面冷清得跟个大森林似的&#xff0c;楼里的灯都像是寂寞的眼睛&#xff0c;眨巴眨巴的&#xff0c;没人搭理。比如八月份那会儿&#xff0c;售楼中心冷清得能听见针掉地上的声音&#xff0c;工作人员闲得跟啥似的&#xff0c;大眼…

【VUE】案例:商场会员管理系统

编写vuedfr实现对会员进行基本增删改查 1. drf项目初始化 请求&#xff1a; POST http://127/0.0.0.1:8000/api/auth/ {"username":"cqn", "password":"123"}返回&#xff1a; {"username":"cqn", "token&q…

Ubuntu2404安装

Ubuntu是一款非常优秀的发行版本&#xff0c;起初她的优势主要在于桌面版&#xff0c;但是随着Centos 从服务版的支持的退出&#xff0c;Ubuntu server也在迅猛的成长&#xff0c;并且不断收获了用户&#xff0c;拥有了一大批忠实的粉丝。好了&#xff0c;废话不多说&#xff0…

鸿蒙harmonyos next flutter通信之BasicMessageChannel获取app版本号

本文将通过BasicMessageChannel获取app版本号&#xff0c;以此来演练BasicMessageChannel用法。 建立channel flutter代码&#xff1a; //建立通道 BasicMessageChannel basicMessageChannel BasicMessageChannel("com.xmg.basicMessageChannel",StringCodec());…

微智启yolo数据集划分工具免费分享下载

微智启yolo数据集划分工具&#xff0c;是由微智启软件工作室开发&#xff0c;版权所有&#xff0c;用于划分yolo数据集的工具&#xff0c;免费分享不收费&#xff0c;禁止用于一切商业用途&#xff0c;如有发现欢迎举报投诉并退款&#xff01; 使用指南&#xff1a; 打开软件&…

Linux忘记root用户密码怎么重设密码

直接说步骤&#xff1a; 1.重启客户机 2.在选择内核页面快速按e键&#xff0c;进入编辑模式 进入后应该是这个样子 在这里只能按上下键切换行 找到Linux16这里 3.按右方向键切换到行尾&#xff0c;也就是UTF-8处&#xff0c;在后面添加一个空格&#xff0c;然后加上这段话 …

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

【Web】portswigger 服务端原型污染 labs 全解

目录 服务端原型污染 为什么服务器端原型污染更难检测&#xff1f; 通过受污染的属性反射检测服务器端原型污染 lab1:通过服务器端原型污染进行权限提升 无需污染属性反射即可检测服务器端原型污染 状态代码覆盖 JSON 空格覆盖 字符集覆盖 lab2:检测没有污染属性反射的…

初识Linux · 进程替换

目录 前言&#xff1a; 1 直接看代码和现象 2 解释原理 3 将代码改成多进程版本 4 认识所有函数并使用 前言&#xff1a; 由前面的章节学习&#xff0c;我们已经了解了进程状态&#xff0c;进程终止以及进程等待&#xff0c;今天&#xff0c;我们学习进程替换。进程替换我…

汽车追尾为什么是后车的责任?

简单点说&#xff1a;因为人后面没有长眼睛。 结论 在汽车追尾事故中&#xff0c;通常情况下后车被认为是责任方的原因在于交通法规对驾驶安全标准的约定和实践中的责任识别原则。虽然追尾事故常见地被归责于后车&#xff0c;但具体判断并不是绝对的&#xff0c;仍需综合多种…

ubuntu图形界面右上角网络图标找回解决办法

问题现象&#xff1a; ubuntu图形界面右上角网络图标消失了&#xff0c;不方便联网&#xff1a; 正常应该是下图&#xff1a; 网络寻找解决方案&#xff0c;问题未解决&#xff0c;对于某些场景可能有用&#xff0c;引用过来&#xff1a; 参考方案 Ubuntu虚拟机没有网络图标或…

(C语言贪吃蛇)13.实现贪吃蛇四方向的移动

目录 前言 原代码预览 解决方法⚠️ 运行效果 总结 前言 我们上节通过Linux线程实现了两个while(1)同时运行&#xff0c;这样就可以一边控制方向一遍刷新出贪吃蛇的身体节点了。本节我们就来实现贪吃蛇四方向的移动。 (此图片为最终效果) 原代码预览 我们之前的代码是通过…

Unity实战案例全解析:RTS游戏的框选和阵型功能(1) 基础要素

本案例来源于unity唐老狮&#xff0c;有兴趣的小伙伴可以去泰克在线观看该课程 【唐老狮】Unity实现 即时战略游戏 阵型功能 - 泰课在线 -- 志存高远&#xff0c;稳如泰山 - 国内专业的在线学习平台|Unity3d培训|Unity教程|Unity教程 Unreal 虚幻 AR|移动开发|美术CG - Powered…

植物种类识别系统源码分享

植物种类识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

网络编程(12)——完善粘包处理操作(id字段)

十二、day12 之前的粘包处理是基于消息头包含的消息体长度进行对应的切包操作&#xff0c;但并不完整。一般来说&#xff0c;消息头仅包含数据域的长度&#xff0c;但是如果要进行逻辑处理&#xff0c;就需要传递一个id字段表示要处理的消息id&#xff0c;当然可以不在包头传i…