ps-tcplayer
Version:
Tencent Cloud Player component with Vue2/Vue3 compatibility
434 lines (389 loc) • 10.6 kB
JavaScript
/**
* 错误上报组件
* 视频播放卡顿统计
*/
import Md5 from '../../utils/md5';
const ApiSign = `HRkJaX81cAbXdFjWe4Qg6HXxtCR7bJRr`;
// 错误类型
const ErrorTypeEnum = {
// 播放错误
ERROR: 'error',
// 连接卡顿
CONNECT_BUFFER: 'connectBuffer',
// 拖拽卡顿
SEEK_VIDEO_BUFFER: 'seekVideoBuffer',
// 正常播放卡顿
VIDEO_BUFFER: 'videoBuffer',
}
// 播放视频类型
const PlayTypeEnum = {
// M3U8点播
M3U8: 1,
// 短视频
VIDEO: 2
}
const LogStatus = {
NORMAL: 0,
// 卡顿时长超过阈值
BLOCK_TIME_OVERFLOW: -1,
// 下行速度不满足要求
DOWNLINK_TOO_LOW: -2,
}
// 视频卡顿阈值,上报
const VIDEO_BLOCK_LIMIT = 1000;
export default class ErrorReportComponent {
constructor(player, params, options) {
this.player = player;
// 错误上报参数
this.params = params;
// 当前播放位置时间,秒
this.currentTime = 0;
// 视频总时长,秒
this.ali_dt = "";
//清晰度
this.definition = "";
// error code, 如果属于系统播放错误
this.errorCode = 0;
// 卡顿时时长,毫秒
this.block_time = 0;
// 卡顿开始时间
this.blockStartTime = 0;
// 当前倍速
this.speed = 1;
this.errorType = '';
// 是否为首次播放
this.is_first = true;
// 卡顿时速度
this.net_speed = 0;
// 当前视频码率
this.bitrate = 0;
// 视频清晰度配置列表
this.videoResources = [];
// 是否发生过断网现象
this.hasOffline = false;
// 计算网速的定时timer
this.netTestTimer = null;
// 60s内不重复测速
this.netTestTimeout = 60000;
}
created() {
console.log('created----==--==-=--=--=--=-');
window.playerEl = this.player;
const player = this.player;
this.videoResources = player._urls
this.net_speed = 0;
this.is_first = true;
this.systemEventListen();
this.logger('created videoResources', this.videoResources);
}
logger(...args) {
const isProduction = window.location.host.startsWith('edu.51cto') || false;
if (isProduction) {
return;
}
console.log(...args);
}
getQuality() {
let Quality = '自动';
try {
const targetElement = document.querySelector(
'.tcp-video-quality-switcher .vjs-selected .vjs-menu-item-text'
);
if (targetElement) {
const textContent = targetElement.textContent.trim();
Quality = textContent;
} else {
console.warn('未找到目标元素,请检查选择器或页面状态');
Quality = '自动';
}
} catch (error) {
Quality = '未知';
}
this.fp = Quality;
}
getBitrate() {
//码率
let bitrate = '';
try {
// const targetElement = document.querySelector(
// '.tcp-video-quality-switcher .vjs-selected .vjs-menu-item-text'
// );
// if (targetElement) {
// const textContent = targetElement.textContent.trim();
// bitrate = textContent;
// }
} catch (error) {
bitrate = '未知';
}
this.bitrate = bitrate;
}
/**
* 视频开始播放
*/
async playing() {
this.logger('playing:', Date.now());
this.logger('playing-blockStartTime:', this.blockStartTime);
this.is_first = false;
this.ali_dt = this.player.getDuration();
// 播放前处于卡顿状态
if (this.blockStartTime > 0) {
const blockDuration = Date.now() - this.blockStartTime;
this.logger('缓冲了', `${this.block_time/1000}s`);
if (blockDuration > VIDEO_BLOCK_LIMIT) {
this.block_time = blockDuration;
const data = await this.generateRequestParams();
await this.errorRequest(data);
}
}
}
/**
* 视频播放结束
*/
ended() {
this.errorCode = 0;
this.resetBlockState();
}
resetBlockState() {
this.blockStartTime = 0;
this.block_time = 0;
this.errorType = '';
}
/**
* 视频暂停
*/
pause() {
this.resetBlockState();
}
/**
* 视频缓冲中, loading状态
*/
waiting() {
const player = this.player;
this.logger('开始缓冲:', Date.now());
this.logger('开始缓冲--seeking:', player._seeking);
this.currentTime = player.getCurrentTime();
this.blockStartTime = Date.now();
this.speed = localStorage.getItem("cto_video_Rate") ? localStorage.getItem("cto_video_Rate") : this.speed;
if (this.is_first) {
this.errorType = ErrorTypeEnum.CONNECT_BUFFER;
} else if (player._seeking) {
this.errorType = ErrorTypeEnum.SEEK_VIDEO_BUFFER;
} else {
this.errorType = ErrorTypeEnum.VIDEO_BUFFER;
}
}
timeupdate() {
// this.currentTime = player.getCurrentTime();
}
/**
* 视频播放错误
* @param {*} player
* @param {*} error
*/
async error(player, error) {
// 很多未知错误都在此,播放错误,请重试
this.logger('error', error);
this.errorCode = error.error_code;
this.resetBlockState();
this.errorType = ErrorTypeEnum.ERROR;
const data = await this.generateRequestParams();
await this.errorRequest(data);
}
destroyed() {
this.errorCode = 0;
this.resetBlockState();
}
getQuality() {
let Quality = '自动';
try {
const targetElement = document.querySelector(
'.tcp-video-quality-switcher .vjs-selected .vjs-menu-item-text'
);
if (targetElement) {
const textContent = targetElement.textContent.trim();
Quality = textContent;
} else {
console.warn('未找到目标元素,请检查选择器或页面状态');
Quality = '自动';
}
} catch (error) {
Quality = '未知';
}
this.definition = Quality;
}
getDownLink() {
if (window.navigator && window.navigator.connection) {
return window.navigator.connection.downlink || ''
}
}
getAccumulation() {
// 从其他插件获取累积播放时长
if (this.player) {
const ReportingComponent = this.player.getComponent('ReportingComponent');
this.logger('ReportingComponent', ReportingComponent);
if (ReportingComponent) {
return ReportingComponent.accumulation;
}
}
}
systemEventListen() {
window.addEventListener('online', this.onNetworkOnline.bind(this));
window.addEventListener('offline', this.onNetworkOffLine.bind(this));
window.addEventListener('visibleChange', this.onVisibleChange.bind(this));
}
onVisibleChange() {
// tab 显隐切换,丢弃卡顿数据
this.resetBlockState();
}
onNetworkOnline() {
this.logger('online');
this.resetBlockState();
}
onNetworkOffLine() {
this.logger('offline');
// 发生断网现象,丢弃卡顿数据
this.hasOffline = true;
this.resetBlockState();
}
/**
* 开始拖拽
*/
startSeek() {
this.resetBlockState();
}
async generateRequestParams() {
// 视频类型
const type = this.params.video_id.length === 32 ? PlayTypeEnum.M3U8 : PlayTypeEnum.VIDEO;
const pt = this.getAccumulation(); //本次累积播放时长
this.getBitrate();
this.getQuality();
var baseData = {
video_id: this.params.video_id,
user_id: this.params.user_id,
uuid: Md5("" + this.params.timestr + this.params.user_id + this.params.random).toString(),
type,
htime: this.currentTime, //当前播放时间
dt: this.ali_dt, //视频总时长
pt: pt,
ref: location.href,
sgin: Md5("" + this.params.timestr + ApiSign).toString(),
platform: 5,
ts_url: this.definition,
code: this.errorCode,
speed: this.speed,
eType: this.errorType,
bitrate: this.bitrate,
net_speed: 0,
downlink: this.getDownLink(),
has_offline: this.hasOffline,
log_status: LogStatus.NORMAL
};
baseData.log_status = this.calcLogStatus(baseData);
var data = {};
if ([ErrorTypeEnum.ERROR].includes(this.errorType)) {
data = Object.assign(baseData, {
time: new Date().getTime()
});
}
if ([ErrorTypeEnum.CONNECT_BUFFER, ErrorTypeEnum.SEEK_VIDEO_BUFFER, ErrorTypeEnum.VIDEO_BUFFER].includes(this.errorType)) {
data = Object.assign(baseData, {
time: this.params.timestr,
block_time: this.block_time,
});
}
return data;
}
/**
* 请求参数校验
* @param {*} data
*/
isParamInvalid(data) {
if (![ErrorTypeEnum.ERROR].includes(this.errorType) && data.block_time <= VIDEO_BLOCK_LIMIT) {
return true;
}
if (data.block_time > data.ali_dt) {
return true;
}
// 大于10分钟的数据没有意义
if (data.block_time > 10 * 60 * 1000) {
return true;
}
return false;
}
/**
* 错误上报
* @param {*} data 接口请求参数
*/
async errorRequest(data) {
this.logger('错误上报', data);
const that = this;
// 非法参数,直接丢弃
if (this.isParamInvalid(data)) {
return true;
}
return new Promise((resolve, reject) => {
$.ajax({
type: "post",
url: `${this.params.error_url}`,
data,
dataType: 'json',
async: false,
success: function (result) {},
complete: function (res) {
that.onReportComplete();
resolve();
}
});
});
}
onReportComplete() {
this.logger('onReportComplete');
this.resetBlockState();
this.errorCode = 0;
this.hasOffline = false;
}
/**
* 计算日志状态,特殊标记,数据库仅存储,不展示
* @param {*} baseData
* @returns
*/
calcLogStatus(baseData) {
const blockSecond = baseData.block_time ? baseData.block_time/1000 : 0;
// 连接卡顿时间大于数分钟,丢弃数据
if (baseData.eType === ErrorTypeEnum.CONNECT_BUFFER) {
if (blockSecond > 5 * 60) {
return LogStatus.BLOCK_TIME_OVERFLOW;
}
} else {
// 卡顿时间 大于 当前视频总时间
if (blockSecond > this.ali_dt) {
return LogStatus.BLOCK_TIME_OVERFLOW;
}
// 卡顿时间 大于 5分钟,丢弃上报,客户端不可用状态
if (blockSecond > 5 * 60) {
return LogStatus.BLOCK_TIME_OVERFLOW;
}
}
// 网速要求不达标
if (baseData.downlink > 0 && baseData.bitrate > 0) {
if (baseData.speed * (baseData.bitrate/1024) > baseData.downlink) {
return LogStatus.DOWNLINK_TOO_LOW;
}
}
return LogStatus.NORMAL;
}
/**
* 是否需要开启测速
* @param {*} baseData
*/
needTestSpeed(baseData) {
if (baseData.eType === ErrorTypeEnum.ERROR) {
return false;
}
// const blockSecond = baseData.block_time ? baseData.block_time/1000 : 0;
if (this.netTestTimer) {
return false;
}
return true;
}
}