Skip to content

Instantly share code, notes, and snippets.

@cyio
Created January 26, 2021 02:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cyio/13ff0ff11de9d5507958aea3343f3611 to your computer and use it in GitHub Desktop.
Save cyio/13ff0ff11de9d5507958aea3343f3611 to your computer and use it in GitHub Desktop.
视频加载延迟、卡顿计算及上报
/* eslint no-underscore-dangle: 0 */
import * as Sentry from '@sentry/browser';
const videoObj = {
videoWaitingTimeout: null, // 会在 player.vue 卸载时清除
};
// 自定义上报,可选携带附加数据
function remoteLog(msg, level = 'info', extra = {}) {
Sentry.withScope(scope => {
scope.setTag('video', msg);
scope.setExtras({ customExtra: extra });
Sentry.captureMessage(msg, level);
});
}
// remoteLog('buffer', 'warn', {'a': 1})
// 卡顿缓冲时长
function videoTracking(video) {
latencyTracking(video);
bufferTracking(video);
}
function getDownLink() {
const connection =
navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection;
if (!connection) return null;
if (!navigator.onLine) return -1;
const { downlink } = connection;
return downlink;
}
// 首次加载延迟
function latencyTracking(video, limit = 10) {
// 加载延迟
const logLatency = () => {
if (
video._loadstart_time &&
video._loadedmetadata_time &&
video._loadeddata_time
) {
const loadedmetadataCost = (
video._loadedmetadata_time - video._loadstart_time
).toFixed(2); // 元信息
const loadeddataCost = (
video._loadeddata_time - video._loadstart_time
).toFixed(2); // 首真
const data = {
eType: 'latency',
loadedmetadataCost,
loadeddataCost,
downlink: getDownLink(),
};
if (loadeddataCost >= limit) {
remoteLog('latency', 'warn', data);
} else {
// logHelper.log(data);
}
}
};
['loadstart', 'loadedmetadata', 'loadeddata'].forEach(eventName => {
video.addEventListener(eventName, function callback() {
const key = `_${eventName}_time`;
video[key] = Date.now() / 1000;
// 在 loadeddata 时记录延迟
if (eventName === 'loadeddata') {
logLatency();
}
// 延迟只需记录一次,相关的监听器触发过后则可移除
video.removeEventListener(eventName, callback);
});
});
}
// 播放中卡顿
function bufferTracking(video, limit = 5) {
video._buffer_times = 0;
video._log_times = 0;
let waitingDuration = 0;
let hasError = false;
const maxWaitingDuration = 60;
// 视频卡顿
const logBuffer = () => {
video._buffer_times += 1;
// 初次缓冲为正常缓冲,不上报
if (video._buffer_times === 1) return;
// 上报次数超过一定次数,不上报
if (video._log_times >= 5) return;
if (video._waiting_time && video._playing_time) {
const diff = video._playing_time - video._waiting_time;
if (diff <= 0 || diff > 1000 * 60 * 5) {
console.log('buf', diff);
}
const bufferCost = +(video._playing_time - video._waiting_time).toFixed(
2
);
const data = {
eType: 'buffer',
limit,
bufferCost,
downlink: getDownLink(),
duration: parseInt(video.duration, 10),
currentTime: parseInt(video.currentTime, 10),
logTimes: video._log_times,
playbackRate: video.playbackRate,
};
if (bufferCost >= limit) {
remoteLog('buffer', 'warn', data);
video._log_times += 1;
} else {
// logHelper.log(data);
}
}
};
// 暂停需要重置
['waiting', 'playing', 'ended'].forEach(eventName => {
video.addEventListener(eventName, () => {
// console.log({ eventName })
const key = `_${eventName}_time`;
video[key] = Date.now() / 1000;
// 在 playing 时记录卡顿
if (eventName === 'playing') {
logBuffer();
if (videoObj.videoWaitingTimeout) {
clearInterval(videoObj.videoWaitingTimeout);
waitingDuration = 0;
}
}
// 播放结束重置卡顿计数器
if (eventName === 'ended') {
video._buffer_times = 0;
if (videoObj.videoWaitingTimeout) {
clearInterval(videoObj.videoWaitingTimeout);
waitingDuration = 0;
}
}
// 判断持续卡顿
if (eventName === 'waiting') {
if (videoObj.videoWaitingTimeout) {
clearInterval(videoObj.videoWaitingTimeout);
}
videoObj.videoWaitingTimeout = setInterval(() => {
// const { hash } = window.location
// console.log({ hash })
if (hasError) return; // 如果存在错误,只能关闭重试
waitingDuration += 1;
if (waitingDuration > maxWaitingDuration) {
video.pause();
// 显示刷新提示,确保当前为暂停状态
const { classList } = document.querySelector('.video-view');
if (!classList.contains('show-refresh') && video.paused) {
classList.add('show-refresh');
console.log('show refresh');
}
clearInterval(videoObj.videoWaitingTimeout);
waitingDuration = 0;
}
}, 1000);
}
});
video.addEventListener('error', () => {
hasError = true;
});
});
// 卡顿计算,有两种情况需要排除,1. waiting 时点击往前前跳触发 playing 2. 往后跳
// 解决办法:1. 有共性的 pause 事件 2. 也可记录事件发生时的播放相对时间,判断时间连续
// 这里采用方法 1,发生 pause 时重置计算
video.addEventListener('pause', () => {
if (video._waiting_time) {
video._waiting_time = null;
}
if (videoObj.videoWaitingTimeout) {
clearInterval(videoObj.videoWaitingTimeout);
waitingDuration = 0;
}
});
}
export { videoTracking, videoObj };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment