一、问题背景
目前,在我们的项目中通常会使用各种各样的埋点和监控来收集页面访问的信息,例如点击埋点、PV埋点等,这些埋点数据能够反应绝大部分的用户行为,但是对于一些关注上下文的使用场景而言这些埋点是不够的。
-
对于产品而言,通过点击或PV埋点来判断功能的使用情况有时候不够的,通常需要知道用户的真实使用路径以判断用户使用是否与功能设计所预想的保持一致,从而能够更好地分析用户使用情况并进一步优化和推广。
-
对于开发而言,当我们收到系统异常通知的时候,监控系统只能告诉你系统出现了错误,但是不能给出错误的复现路径,对于稳定复现的错误而言还好,但对于偶发错误或复现路径隐藏较深的场景我们就较难去解决问题。
-
对于测试而言,用户反馈线上bug的时候,首先需要知道的就是通过什么操作触发了这个问题,有时候用户自己可能也无法二次复现,这样的错误我们也无法通过埋点的数据去推导上下文,因此就会产生较大的沟通成本,在执行测试计划反馈 bug 时同理。
-
......
因此,我们需要一种手段来获取用户某一时段连续的操作行为,也就是录制用户行为,包括整个会话中的每一个点击、滑动、输入等行为,同时支持回放录制的操作行为,完整且真实地重现用户行为以帮助我们回溯或分析某些使用场景。
二、技术方案
2.1 视频录制
录制用户行为最容易想到的就是将屏幕操作通过视频的方式录制下来,目前浏览器本身已经提供了一套基于音视轨的实时数据流传输方案 WebRTC(Web Real-Time Communications),在我们的录屏使用场景主要关注以下几个 API:
-
getDisplayMedia() - 提示用户给予使用媒体输入的许可从而获取屏幕的流;
-
MediaRecorder() - 生成对指定的媒体流进行录制的 MediaRecorder 对象;
-
ondataavailable - 当 MediaRecorder 将媒体数据传递到应用程序以供使用时将触发该事件;
整体录制流程如下:
-
调用
mediaDevices.getDisplayMedia()
由用户授权选择屏幕进行录制,获取到数据流; -
生成一个
new MediaRecorder()
对象录制获取的屏幕的数据流; -
在 MediaRecorder 对象上设置
ondataavailable
监听事件用于获取录制的 Blob 数据。
<template>
<video ref="playerRef"></video>
<button @click="handleStart">开启录制</button>
<button @click="handlePause">暂停录制</button>
<button @click="handleResume">继续录制</button>
<button @click="handleStop">结束录制</button>
<button @click="handleReplay">播放录制</button>
<button @click="handleReset">重置内容</button>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
const playerRef = ref();
const state = reactive({
mediaRecorder: null as null | MediaRecorder,
blobs: [] as Blob[],
});
// 开始录制
const handleStart = async () => {
const stream = await navigator.mediaDevices.getDisplayMedia();
state.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm',
});
state.mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
state.blobs.push(e.data);
});
state.mediaRecorder?.start();
};
// canvas录制(特殊处理)
const handleCanvasRecord = () => {
const stream = canvas.captureStream(60); // 60