拿下奇怪的前端报错(六):多摄手机webrtc拉取视频流会导致应用崩溃,从而无法进行人像扫描

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 本文介绍了一种解决手机摄像头切换导致应用崩溃的问题的方法。针对不支持facingMode配置的四摄手机,通过缓存和序号切换的方式,确保应用在特定设备上不会频繁崩溃,提升用户体验。

现在有些手机更新的很激进,但是却没有很好的实现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();
}

image.gif

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));
};

image.gif

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();
}

image.gif

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;
};

image.gif

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;
  }
};

image.gif

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,
  };
}

image.gif

4.3 拉流

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

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

image.gif

5 小结

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


相关文章
|
26天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
43 3
|
10天前
|
前端开发
结合具体案例分析Gitflow分支策略在大型前端项目中的应用优势
通过这个具体案例可以看出,Gitflow 分支策略在大型前端项目中能够提供有条不紊的开发环境,保障项目的稳定性和持续发展。
|
27天前
|
Rust 前端开发 JavaScript
前端性能革命:WebAssembly在高性能计算中的应用探索
【10月更文挑战第26天】随着Web应用功能的日益复杂,传统JavaScript解释执行模式逐渐成为性能瓶颈。WebAssembly(Wasm)应运而生,作为一种二进制代码格式,支持C/C++、Rust等语言编写的代码在浏览器中高效运行。Wasm不仅提升了应用的执行速度,还具备跨平台兼容性和安全性,显著改善了Web应用的响应速度和用户体验。
40 4
|
26天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
43 2
|
27天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
26 3
|
27天前
|
Web App开发 定位技术 iOS开发
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
28 1
|
27天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
32 2
|
27天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
38 2
|
28天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
32 2
|
29天前
|
前端开发 API UED
深入理解微前端架构:构建灵活、高效的前端应用
【10月更文挑战第23天】微前端架构是一种将前端应用分解为多个小型、独立、可复用的服务的方法。每个服务独立开发和部署,但共同提供一致的用户体验。本文探讨了微前端架构的核心概念、优势及实施方法,包括定义服务边界、建立通信机制、共享UI组件库和版本控制等。通过实际案例和职业心得,帮助读者更好地理解和应用微前端架构。

热门文章

最新文章