Created
January 26, 2021 02:18
-
-
Save cyio/13ff0ff11de9d5507958aea3343f3611 to your computer and use it in GitHub Desktop.
视频加载延迟、卡顿计算及上报
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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