相关介绍

车主认证项目背景

车主认证主体是以H5形式存在的,目前投放在多端,包括:哈啰App、车主App、货运车主App、支付宝小程序、微信小程序、H5外投页面,存在多端场景调用拍摄能力的需求。

存在问题:

  • 多平台适配
    确保拍摄功能在各个平台上有良好的适配,包括哈啰App、车主App、货运车主App、支付宝小程序、微信小程序和H5外投页面。
  • 小程序兼容性
    对于支付宝小程序和微信小程序,要确保拍摄功能在小程序环境下能够正常调用。支付宝小程序目前借助小程序本身的拍摄能力,但是微信未提供视频拍摄方案。
  • 外投页面兼容性
    对于H5外投页面,可能会面临不同浏览器和设备的兼容性挑战。确保在各种浏览器中都能够正常加载和运行。

WebRTC简介

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,是一组用于在Web浏览器和移动应用程序中实现实时通信的开放标准和协议。它允许浏览器和应用程序之间通过简单的API实现音频、视频和数据的实时传输。

WebRTC 的典型应用场景包括实时视频通话、视频会议、屏幕共享、音视频录制等。

WebRTC主要包含以下三个核心模块:

  • getUserMedia: 用于获取用户的音频和视频流。主要应用在视频和音频录制、视频通话和音频通话、在线会议和远程协作、人脸识别和图像处理等。
  • RTCPeerConnection: 用于建立点对点的连接,支持实时的音频和视频传输。主要应用在实时音视频通话、视频会议、屏幕共享等。
  • RTCDataChannel: 用于在两个对等体之间传输任意数据。主要应用在文件传输、实时游戏、即时消息、协同编辑、远程控制等。

由于其 API 的多样,针对不同的场景,其他贡献者们做了有效封装,recordRTC 就是其中一个。 其基于WebRTC的 getUserMedia API 实现媒体设备访问, 并对 WebRTC提供的视频流函数进行了封装, 使开发者可以简单函数调用就能实现视频录制。

本方案的实现借助了WebRTC和RecordRTC的图像采集以及媒体数据流(getUserMedia)的控制能力,WebRTC的核心还包括实时传输、安全传输等等,有兴趣的同学可以自行了解。

recordRTC简介

recordRTC 是一个 JavaScript 库,提供了一些用于录制媒体流(如音频、视频)的功能。 基于 WebRTC 的 getUserMedia API,利用这一API,它可以获取用户的音频和视频流。以下是 recordRTC 利用 getUserMedia 提供的主要能力:

  • 获取摄像头和麦克风的访问权限: 通过 getUserMedia,recordRTC 可以请求用户授予对摄像头和麦克风的访问权限。用户可以选择允许或拒绝这些权限。
  • 获取媒体流: getUserMedia 返回一个代表用户摄像头和麦克风的媒体流对象。这个媒体流包含实时的音频和视频数据。
  • 媒体流的配置: 通过 getUserMedia 的配置参数,recordRTC 可以指定获取的媒体流的特性,例如选择前置或后置摄像头、指定视频分辨率、选择音频输入设备等。
  • 实时预览: getUserMedia 允许在获取媒体流后进行实时的音视频预览。
  • 动态更新媒体流: getUserMedia 提供了一些方法,在运行时可以动态更新媒体流的配置,例如切换摄像头、更改分辨率等。

支持的浏览器:

图片

常用参数:

  • type: 接受 video or audio or canvas or gif
  • recorderType: 接受 MediaStreamRecorder or StereoAudioRecorder or WhammyRecorder or GifRecorder
  • timeSlice: 接受一个毫秒数; 用它来强制基于间隔的blob
  • ondataavailable: 将此函数与timeSlice一起传递以获取基于间隔的blob
  • bitsPerSecond: 每秒比特数; 适用于音频和视频的轨道
  • audioBitsPerSecond: 每秒比特数; 只适用于音频轨道
  • videoBitsPerSecond: 每秒比特数; 只适用于视频轨道
  • disableLogs: 接受 true or false; 用它禁用console的日志输出
  • frameInterval: 接受一个毫秒数
  • previewStream: 是 MultiStreamRecorder 的回调方法
  • video: 接受一个类似对象: {width: 320, height: 240}
  • canvas: 接受一个类似对象: {width: 320, height: 240}

方法:

  • startRecording(): 启动录制过程。调用此方法将开始捕获媒体流,并开始录制音频或视频。
  • stopRecording(callback): 停止录制过程。可以传递一个回调函数,用于在录制完成后处理录制的数据。
  • getBlob(): 获取录制数据的 Blob 对象。可以通过此方法获取录制的音频或视频数据。
  • pauseRecording(): 暂停录制。可以在录制过程中调用此方法以暂停录制。
  • resumeRecording(): 恢复录制。在暂停录制后,可以调用此方法以恢复录制过程。
  • clearRecordedData(): 清除录制的数据。
  • getDataURL(callback): 获取录制数据的 Data URL。通过回调函数获取录制的音频或视频数据的 Data URL。
  • setRecordingDuration(milliseconds): 设置录制的时长。可以通过此方法设置录制的最大时长,录制达到指定时长后会自动停止。

WebRTC拍摄具体实现

拍摄流程

图片

具体实现

安装:

安装 recordrtc 库,引入 RecordRTCPromisesHandler 类,用于处理WebRTC的视频录制。

npm install recordrtc
import { RecordRTCPromisesHandler } from 'recordrtc';

使用:

在车主认证项目中,将操作js拍摄化封装为一个 video-recorder 组件,在组件内部处理方法调用。

具体实现步骤大概分为3部分:

  • 初始化:获取拍摄设备和配置信息;
  • 拍摄:使用 RecordRTCPromisesHandler 的实例化对象提供的方法;
  • 上传:视频上传到阿里云OSS,并且进行回显。
初始化:

图片

因为目前手机存在多个后置摄像头场景,如果获取到的是广角或者桌面视角摄像头,则会有体验问题,所以在初始化时,将所有后置摄像头全部获取,可以让用户通过 Picker 进行选择。

图片

图片

getVideoConstraints方法,获取后置拍摄设备配置列表。

async getVideoConstraints() {
  let deviceId = '';
  // 只有第一次时需要遍历镜头列表
  if (!this.activeCamera) {
    // 获取所有设备列表
    const deviceList = await navigator.mediaDevices.enumerateDevices();
    // 过滤出视频输入设备列表
    const videoDeviceList = deviceList.filter((deviceInfo) => deviceInfo.kind === 'videoinput').reverse();
    // 发送视频设备列表到父组件
    this.$emit('output-list', videoDeviceList);
    // 遍历视频输入设备列表
    for (const device of videoDeviceList) {
      // 获取特定设备的视频流
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          deviceId: device.deviceId,
        },
        audio: false,
      });
      // 检查摄像头是否为环境(后置)摄像头
      const isEnvironment = stream.getVideoTracks()[0].getSettings().facingMode === 'environment';
      // 停止获取的视频流上的所有轨道,释放资源
      stream.getTracks().forEach((track) => {
        track.stop();
      });
      // 如果是环境(后置)摄像头,则记录设备ID,并跳出循环
      if (isEnvironment) {
        deviceId = device.deviceId;
        break;
      }
    }
  }

  // 设置视频约束
  const result: MediaTrackConstraints = {
    frameRate: { ideal: 6, max: 10 },
    width: this.env.isAndroid ? { ideal: 960, min: 480, max: 960 } : { ideal: 480, min: 480, max: 960 },
    height: this.env.isAndroid ? { ideal: 1280, min: 640, max: 1280 } : { ideal: 640, min: 640, max: 1280 },
    facingMode: 'environment',
    deviceId: this.activeCamera ? this.activeCamera.deviceId : deviceId,
    aspectRatio: 3 / 4,
  };

  if (!deviceId && !this.activeCamera) {
    delete result.deviceId;
  }

  // 返回视频约束
  return result;
}
拍摄:

点击录制按钮,通过调用 recorder 对象的 startRecording 方法来开启视频录制。

async record() {
  if (this.recorder) {
    await this.recorder.startRecording();
    this.isRecording = true;
  }
}

在开启录制后,倒计时5s,停止录制,调用 recorder 对象的 stopRecording 停止拍摄,通过 getBlob() 方法获取录制的 Blob对象,一定要在停止录制之后获取 Blob 对象,否则可能获取的Blob数据有问题。

// 开始倒计时
   startTimer() {
    if (this.timerText > 1) {
      this.recording = true;
      this.timerText -= 1;
      setTimeout(() => {
        this.startTimer();
      }, 1000);
    } else {
      this.resetTimer();
    }
  }

// 倒计时结束后重制
  resetTimer() {
    if (this.$refs.videoRecorder) {
      this.$refs.videoRecorder.stop();
    }
    this.recording = false;
    this.btnImgUrl = btnImgUrlMapper.DEFALUT;
    this.timerText = 6;
  }

// 停止拍摄并且上传文件
  async stop() {
    if (this.recorder) {
      await this.recorder.stopRecording();
      this.isRecording = false;
      this.uploadFile();
    }
  }

// 获取视频流
  async uploadFile() {
    const video = await this.recorder.getBlob();
    this.$emit('recorded', {
      video,
    });
  }
上传:

视频上传是使用 aliyun 的 oss,在获取到 上传视频的 Blob 对象之后,上传到 aliyun 进行存储,通过返回的文件名 videoRes.name 获取视频的预览Url,跳转到Ocr识别页,进行Ocr识别。

图片

(本文作者:佟健)

图片


哈啰技术
89 声望51 粉丝

哈啰官方技术号,不定期分享哈啰的相关技术产出。