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

 现在有些手机更新的很激进,但是却没有很好的实现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[];
  // 检查下一个可用idx
  const 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.mfbz.cn/a/887480.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GAMES101(19节,相机)

相机 synthesis合成成像&#xff1a;比如光栅化&#xff0c;光线追踪&#xff0c;相机是capture捕捉成像&#xff0c; 但是在合成渲染时&#xff0c;有时也会模拟捕捉成像方式&#xff08;包括一些技术 动态模糊 / 景深等&#xff09;&#xff0c;这时会有涉及很多专有名词&a…

阿里云ACP认证考试题库

最近有好些同学&#xff0c;考完阿里云ACP了&#xff0c;再来跟我反馈&#xff1a;自己花700买的阿里云ACP题库&#xff0c;结果答案是错的&#xff01; 或者考完后发现&#xff0c;买的阿里云ACP题库覆盖率只有50%&#xff01; 为避免大家继续踩坑&#xff0c;给大家分享一个阿…

洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载

一、前言 【试用版软件下载可点击本文章最下方官网卡片】 洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载 洗车管理软件应用是洗车业务的得力助手&#xff0c;实现会员管理及数据统计一体化&#xff0c;助力店铺高效、有序运营。 洗车项…

CSS | 响应式布局之媒体查询(media-query)详解

media type(媒体类型)是CSS 2中的一个非常有用的属性&#xff0c;通过media type我们可以对不同的设备指定特定的样式&#xff0c;从而实现更丰富的界面。media query(媒体查询)是对media type的一种增强&#xff0c;是CSS 3的重要内容之一。随着移动互联网的发展&#xff0c;m…

【CSS Tricks】css动画详解

目录 引言一、动画关键帧序列二、动画各属性拆解1. animation-name2. animation-duration3. animation-delay3.1 设置delay为正值3.2 设置delay为负值 4. animation-direction5. animation-iteration-count6. animation-fill-mode7. animation-play-state8. animation-timing-f…

读数据湖仓08数据架构的演化

1. 数据目录 1.1. 需要将分析基础设施放置在数据目录(Data Catalogue)的结构中 1.1.1. 元数据 1.1.2. 数据模型 1.1.3. 本体 1.1.4. 分类标准 1.2. 数据目录类似于图书馆的图书检索目录 1.2.1. 先通过图书馆的图书检索目录进行查找&#xff0c;以便快速找到所需的图书 1…

【AGC005D】~K Perm Counting(计数抽象成图)

容斥原理。 求出f(m) &#xff0c;f(m)指代至少有m个位置不合法的方案数。 怎么求&#xff1f; 注意到位置为id&#xff0c;权值为v ,不合法的情况&#xff0c;当且仅当 v idk或 v id-k 因此&#xff0c;我们把每一个位置和权值抽象成点 &#xff0c;不合法的情况之间连一…

NASA:北极植被地块 ATLAS 项目 北坡和苏厄德半岛,明尼苏达州,1998-2000 年

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 Arctic Vegetation Plots ATLAS Project North Slope and Seward Peninsula, AK, 1998-2000 简介 文档修订日期&#xff1a;2018-12-31 数据集版本&#xff1a;1 本数据集提供了在北极陆地-大气系统…

基于auth2的单点登录原理理解

创作背景&#xff1a;基于auth2实现企业门户与业务系统的单点登录跳转。 架构组成&#xff1a;4A统一认证中心&#xff0c;门户系统&#xff0c;业务系统&#xff0c;用户&#xff1b; 实现目标&#xff1a;用户登录门户系统后&#xff0c;可通过点击业务系统菜单&#xff0c…

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)

1 准备工作 由于创建数据中心需要安装很多服务器&#xff0c;这些服务器要耗费很所物理物理计算资源、存储资源、网络资源和软件资源&#xff0c;作为穷学生只有几百块的n手笔记本&#xff0c;不可能买十几台服务器来搭建数据中心&#xff0c;也不愿意跑实验室&#xff0c;想躺…

云中红队系列 | 使用 Azure FrontDoor 混淆 C2 基础设施

重定向器是充当 C2 服务器和目标网络之间中间人的服务器。其主要功能是重定向 C2 和受感染目标之间的所有通信。重定向器通常用于隐藏 C2 服务器流量的来源&#xff0c;使防御者更难以检测和阻止 C2 基础设施。 基于云的重定向器提供了一个很好的机会&#xff0c;通过内容分发…

Map: 地图

对全国2023年各省市的人口分布情况&#xff0c;做出地图展示效果 参考&#xff1a;Map - Map_base - Document (pyecharts.org) 1、模板 # -*- coding: gbk -*- from pyecharts import options as opts from pyecharts.charts import Map from pyecharts.faker import Faker…

SQL Inject-基于报错的信息获取

常用的用来报错的函数 updatexml() : 函数是MYSQL对XML文档数据进行查询和修改的XPATH函数。 extractvalue(): 函数也是MYSQL对XML文档数据进行查询的XPATH函数。 floor(): MYSQL中用来取整的函数。 思路&#xff1a; 在MySQL中使用一些指定的函数来制造报错&am…

【Python】Hypercorn:轻量级的异步ASGI/WSGI服务器

Hypercorn 是一个支持异步 ASGI 和同步 WSGI 应用的高效 Python 服务器。它结合了现代协议支持&#xff08;包括 HTTP/1、HTTP/2 和 HTTP/3&#xff09;&#xff0c;并且为异步 Web 框架&#xff08;如 FastAPI 和 Quart&#xff09;提供了卓越的性能和灵活性。通过 Hypercorn&…

MySQL联合索引、索引下推Demo

1.联合索引 测试SQL语句如下&#xff1a;表test中共有4个字段(id, a, b, c)&#xff0c;id为主键 drop table test;#建表 create table test(id bigint primary key auto_increment,a int,b int,c int )#表中插入数据 insert into test(a, b, c) values(1,2,3),(2,3,4),(4,5,…

云服务器部署k8s需要什么配置?

云服务器部署k8s需要什么配置&#xff1f;云服务器部署K8s需要至少2核CPU、4GB内存、50GBSSD存储的主节点用于管理集群&#xff0c;工作节点建议至少2核CPU、2GB内存、20GBSSD。还需安装Docker&#xff0c;选择兼容的Kubernetes版本&#xff0c;配置网络插件&#xff0c;以及确…

【黑马点评】 使用RabbitMQ实现消息队列——1.Docker与RabbitMQ环境安装

黑马点评中&#xff0c;使用基于Redis的Stream实现消息队列&#xff0c;但是Strema已经不太常用。在此修改为使用RabbitMQ实现消息队列。主要包括RabbitMQ的环境准备&#xff08;Docker的下载与安装&#xff09;以及如何修改黑马点评中的代码。 【黑马点评】使用RabbitMQ实现消…

《Linux从小白到高手》理论篇:Linux的资源监控管理

本篇介绍Linux的资源监控管理。 1、CPU 资源管理 进程调度&#xff1a; Linux 采用公平的进程调度算法&#xff0c;确保每个进程都能获得合理的 CPU 时间。调度算法会根据进程的优先级、等待时间等因素来决定哪个进程获得 CPU 使用权。 可以通过调整进程的优先级来影响其获得…

基于SpringBoot+Vue+MySQL的校园二手物品交易系统

系统展示 用户前台界面 管理员后台界面 系统背景 校园二手物品交易系统开发的背景与重要性随着高等教育的蓬勃发展&#xff0c;大学生群体的规模持续扩大&#xff0c;随之而来的是物品更新换代速度的显著加快。学生们在追求新潮、高品质生活的同时&#xff0c;往往会产生大量闲…

多文件并发多线程MD5工具(相对快速的MD5一批文件),适配自定义MD5 Hash I/O缓存。

自己写的多文件 MD5校验工具&#xff0c;一个文件开一个线程&#xff0c;有最大I/O 缓存设置&#xff0c;兼容读写MD5后缀文件。 共计91个文件&#xff0c;合计180G左右 12分钟左右&#xff0c;UI基本卡废&#xff0c;但程序没蹦&#xff0c;属于正常。 卡的原因是基本是用 I/O…