navigator.mediaDevices.getUserMedia实现拍照功能,居然这么多坑

831 阅读2分钟

踩坑日常

最近公司需要做一个拍照扫描的功能,第一版使用input type=file,直接调用原生相机;然后测试大佬觉得链路太长,点击按钮->出现选择栏->选择相机->拍照点击使用->回到页面;作为卑微前端,改,选中了WebRTC的navigator.mediaDevices.getUserMedia,其他博客都有介绍,不多赘述;直接上干货。毕竟直接能拿来用的博客才是好代码。

  1. 参数设置
    用法其他博客都有介绍
    (tips: 在华为鸿蒙系统中,相机像素的宽高比是反过来的,类似电脑那种摄像头,还有近处的贼模糊,只能远景,踩坑+1)
const { clientWidth, clientHeight } = videoEl.value
const params = isHarmonyOS
      ? {
          width: { min: 1080, ideal: 1440, max: 2880, exact: clientWidth },
          height: { min: 2400, ideal: 3840, max: 6400, exact: clientHeight },
        }
      : {
          width: { min: 1280, ideal: 1440, max: 2560 },
          height: { min: 600, ideal: 720, max: 1440 },
        }
  const constraints = {
    video: {
      ...params,
      frameRate: { min: 30, ideal: 60, max: 120 },
      facingMode: 'environment', // 强制后置摄像头
    }
  }


  1. 打开摄像头之后模糊
    (tips:加上自动聚焦,好的测试才能出好的产品,测试很认真,手动认真脸,优化+1)
    videoEl.value.focus()
  1. 续1
    开始绘制canvas,不出意外的又要进坑了
    (tips:遥遥领先,调整完像素比,以为没事了,天真了,绘制完天塌了,图片直接变形,踩坑+1 )
  const { clientWidth, clientHeight, videoWidth, videoHeight } = videoEl.value
  const canvas = document.createElement('canvas')
  const devicePixelRatio = window.devicePixelRatio || 3
  const scaleNum = videoHeight / videoWidth
  const canvasHeight = clientWidth * devicePixelRatio * scaleNum
  canvas.width = isHarmonyOS ? clientHeight * devicePixelRatio : clientWidth * devicePixelRatio
  canvas.height = isHarmonyOS ? clientWidth * devicePixelRatio : canvasHeight
  1. 拍照之后绘制canvas
    (tips:又是遥遥领先,第一次拍出来的是空白透明图片,一度以为没拍出来,踩坑+1)
const checkIfImageIsTransparent = async (imageData: any): Promise<boolean> => {
  return new Promise((resolve) => {
    for (let i = 0; i < imageData.data.length; i += 4) {
      if (imageData.data[i + 3] !== 0) {
        resolve(false)
        return
      }
    }
    resolve(true)
  })
}
checkIfImageIsTransparent(imageData).then((res: boolean) => {
  if (res) {
    Message('请重新拍摄')
  } else {
    // 业务代码
  }
})
  1. 页面切出打开原生相机,切回来后黑屏
    (tips:这次是安卓,ios兼容性好点,优化+1)
    页面切回来后先关掉其他的视频流,然后再次获取navigator.mediaDevices.getUserMedia视频流

currentStream navigator.mediaDevices.getUserMedia获取到的视频流

if (currentStream.value) {
    const tracks = currentStream.value?.getTracks()
    tracks.forEach((track:any) => {
      track.stop()
    })
    currentStream.value = null
  }

每次打开页面都会询问下权限获取,产品觉得太麻烦,只想询问一次,(tips:请恕臣妾做不到啊,手动图)
大佬有研究过的可以留言py一波。
暂时就是这些,over!!!
(直接用,出bug哪有心思慢慢看文档)