UNPKG

ps-tcplayer

Version:

Tencent Cloud Player component with Vue2/Vue3 compatibility

434 lines (389 loc) 10.6 kB
/** * 错误上报组件 * 视频播放卡顿统计 */ 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; } }