feat(yb-video): 添加倍速播放配置和错误恢复功能
- 新增 playbackRates 属性支持自定义倍速列表 - 增加错误自动恢复机制,处理视频播放错误 - 优化 DOM 查找逻辑,增加延迟和错误处理 - 调整播放器样式和版本号更新 - 修改 edu-core 依赖为本地引用
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
"name" : "心灵空间",
|
||||
"appid" : "__UNI__BBBDFD2",
|
||||
"description" : "心灵空间",
|
||||
"versionName" : "1.0.48",
|
||||
"versionName" : "1.0.49",
|
||||
"sassImplementationName" : "node-sass",
|
||||
"versionCode" : 1048,
|
||||
"versionCode" : 1049,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.6",
|
||||
"edu-core": "file:../edu-core",
|
||||
"jquery": "^3.7.1",
|
||||
"tcplayer.js": "^5.1.0"
|
||||
},
|
||||
@@ -19,7 +19,6 @@
|
||||
},
|
||||
"../edu-core": {
|
||||
"version": "1.0.6",
|
||||
"extraneous": true,
|
||||
"license": "ISC",
|
||||
"devDependencies": {}
|
||||
},
|
||||
@@ -74,9 +73,8 @@
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/edu-core": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#c1e7c25988e1b1ca91e8fd7a679df70339ef0fc3",
|
||||
"license": "ISC"
|
||||
"resolved": "../edu-core",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/es5-shim": {
|
||||
"version": "4.6.7",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
|
||||
"dependencies": {
|
||||
"edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.6",
|
||||
"edu-core": "file:../edu-core",
|
||||
"jquery": "^3.7.1",
|
||||
"tcplayer.js": "^5.1.0"
|
||||
},
|
||||
|
||||
@@ -254,9 +254,21 @@
|
||||
methods: {
|
||||
dataIdWatcher (newVal) {
|
||||
if ( newVal ) {
|
||||
this.dom = document.querySelector('.rvideo' + newVal)
|
||||
this.domSlot = document.querySelector('.rvideoslot' + newVal)
|
||||
this.init()
|
||||
// 延迟一下再查找 DOM 元素,确保 DOM 已经渲染完成
|
||||
setTimeout(() => {
|
||||
this.dom = document.querySelector('.rvideo' + newVal)
|
||||
this.domSlot = document.querySelector('.rvideoslot' + newVal)
|
||||
// 检查 DOM 元素是否存在
|
||||
if ( this.dom && this.domSlot ) {
|
||||
this.init()
|
||||
} else {
|
||||
console.error('yb-video: DOM 元素未找到,可能渲染失败', {
|
||||
dataId: newVal,
|
||||
dom: this.dom,
|
||||
domSlot: this.domSlot
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
async init () {
|
||||
@@ -348,8 +360,9 @@
|
||||
parseSrc (path) {
|
||||
// #ifdef H5
|
||||
const isHash = window.location.hash
|
||||
const route = this.dom.getAttribute('data-route')
|
||||
const pathName = isHash ? window.location.pathname : window.location.pathname.replace(route, '')
|
||||
// 安全检查 this.dom 是否存在
|
||||
const route = this.dom ? this.dom.getAttribute('data-route') : ''
|
||||
const pathName = isHash ? window.location.pathname : window.location.pathname.replace(route || '', '')
|
||||
return window.location.origin + pathName + path.substring(1)
|
||||
// #endif
|
||||
// #ifdef APP-VUE
|
||||
@@ -466,6 +479,7 @@
|
||||
workIndex: params.workIndex,
|
||||
subtitles: params.subtitles,
|
||||
subtitleIndex: params.subtitleIndex,
|
||||
playbackRates: params.playbackRates,
|
||||
custom,
|
||||
decoder: {
|
||||
hls: {
|
||||
|
||||
@@ -105,6 +105,16 @@
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//倍速列表配置
|
||||
playbackRates: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
//是否显示倍速提示
|
||||
showRateTips: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//循环播放
|
||||
loop: {
|
||||
type: Boolean,
|
||||
@@ -531,6 +541,7 @@
|
||||
muted: this.muted,
|
||||
loop: this.loop,
|
||||
playbackRate: this.playbackRate,
|
||||
playbackRates: this.playbackRates,
|
||||
isLive: this.isLive,
|
||||
header: this.header,
|
||||
controls: this.controls,
|
||||
@@ -785,6 +796,12 @@
|
||||
playbackRate (newVal) {
|
||||
this.setVideo('playbackRate', newVal)
|
||||
},
|
||||
//监听倍速列表
|
||||
playbackRates (newVal) {
|
||||
if (newVal && newVal.length > 0) {
|
||||
this.reloadVideo()
|
||||
}
|
||||
},
|
||||
//监听循环属性
|
||||
loop (newVal) {
|
||||
this.setVideo('loop', newVal)
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
.yb-player-time {
|
||||
min-width: 35px;
|
||||
min-width: 38px;
|
||||
}
|
||||
|
||||
/* .yb-player-duration {
|
||||
|
||||
@@ -51,7 +51,8 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
works = _ref.works,
|
||||
workIndex = _ref.workIndex,
|
||||
subtitles = _ref.subtitles,
|
||||
subtitleIndex = _ref.subtitleIndex;
|
||||
subtitleIndex = _ref.subtitleIndex,
|
||||
playbackRates = _ref.playbackRates;
|
||||
_classCallCheck(this, YbPlayer);
|
||||
this.container = typeof container == 'string' ? document.querySelector(container) : container;
|
||||
this.src = src; //播放链接
|
||||
@@ -64,6 +65,31 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
this.loop = loop; //是否循环播放
|
||||
this.muted = muted; //是否静音
|
||||
this.playbackRate = playbackRate || 1; //默认播放倍速
|
||||
this.playbackRates = playbackRates && playbackRates.length > 0 ? playbackRates : [{
|
||||
text: '0.5倍速',
|
||||
value: 0.5
|
||||
}, {
|
||||
text: '0.75倍速',
|
||||
value: 0.75
|
||||
}, {
|
||||
text: '正常倍速',
|
||||
value: 1
|
||||
}, {
|
||||
text: '1.25倍速',
|
||||
value: 1.25
|
||||
}, {
|
||||
text: '1.5倍速',
|
||||
value: 1.5
|
||||
}, {
|
||||
text: '1.75倍速',
|
||||
value: 1.75
|
||||
}, {
|
||||
text: '2倍速',
|
||||
value: 2
|
||||
}, {
|
||||
text: '3倍速',
|
||||
value: 3
|
||||
}]; //倍速列表配置
|
||||
this.preload = preload; //是否预加载
|
||||
this.isLive = isLive; //是否直播
|
||||
this.header = header; //显示头部控制栏
|
||||
@@ -99,6 +125,9 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
this._toastTimer = null; //消息隐藏定时器
|
||||
this._danmuTimer = null; //弹幕定时器
|
||||
this._seizingTimer = null; //卡死定时器(播放一些直播源的时候,可能会出现卡死无反应的情况,需要做出处理)
|
||||
this._rateTimer = null; //倍速显示定时器
|
||||
this._errorRetryCount = 0; //错误重试次数
|
||||
this._maxErrorRetry = 0; //最大重试次数
|
||||
this._event = {};
|
||||
}
|
||||
//开启全屏按钮
|
||||
@@ -136,6 +165,7 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
this._clearDanmuTimer();
|
||||
this._clearToastTimer();
|
||||
this._clearControlsTimer();
|
||||
this._clearRateTimer();
|
||||
this._removeBackbuttonListener();
|
||||
this._event = {}; //卸载所有监听事件
|
||||
if (this.container) {
|
||||
@@ -205,8 +235,8 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
_this2.emit('durationchange', _this2.getDuration());
|
||||
};
|
||||
this.video.onloadeddata = function () {
|
||||
//非直播时初始化播放时长
|
||||
if (_this2.initialTime && !_this2.isLive) _this2.seek(_this2.initialTime);
|
||||
//非直播时初始化播放时长(错误恢复时不执行)
|
||||
if (_this2.initialTime && !_this2.isLive && !_this2._isErrorRecovering) _this2.seek(_this2.initialTime);
|
||||
_this2.emit('loadeddata', {
|
||||
duration: _this2.getDuration(),
|
||||
videoWidth: _this2.video.videoWidth,
|
||||
@@ -234,12 +264,18 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
if (_this2.cm) _this2.cm.setConfig('playbackRate', playbackRate);
|
||||
_this2.emit('ratechange', playbackRate);
|
||||
_this2.setInnerHTML('yb-player-header-rate', '倍速x' + playbackRate);
|
||||
_this2._clearRateTimer();
|
||||
var rateEl = _this2.container.getElementsByClassName('yb-player-rate')[0] || document.createElement('DIV');
|
||||
if (![1, 1.0].includes(playbackRate)) {
|
||||
rateEl.setAttribute('class', 'yb-player-rate');
|
||||
rateEl.innerHTML = "\n\t\t\t\t\t<div class=\"yb-player-rate-icon\">\n\t\t\t\t\t\t<i></i><i></i><i></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"yb-player-rate-span\">".concat(playbackRate + '倍速播放中', "</span>\n\t\t\t\t");
|
||||
var wrapperEl = _this2.container.getElementsByClassName('yb-player-wrapper')[0];
|
||||
if (wrapperEl) wrapperEl.appendChild(rateEl);
|
||||
_this2._rateTimer = window.setTimeout(function () {
|
||||
if (rateEl && rateEl.parentNode) {
|
||||
rateEl.parentNode.removeChild(rateEl);
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
if (rateEl) rateEl.remove();
|
||||
}
|
||||
@@ -332,7 +368,6 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
};
|
||||
this.video.onerror = function (e) {
|
||||
if (e && e.target.error) {
|
||||
// 网络问题或其他不可恢复的错误
|
||||
var code = e.target.error.code;
|
||||
var errorMsg = '';
|
||||
switch (code) {
|
||||
@@ -349,10 +384,26 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
errorMsg = '视频源不支持或地址无效';
|
||||
break;
|
||||
}
|
||||
_this2.showError(errorMsg);
|
||||
_this2.unloadVideo();
|
||||
console.log('[video.onerror] 检测到视频错误,错误码:' + code + ', 错误信息:' + errorMsg);
|
||||
|
||||
// 错误码 3 尝试跳过问题区间后继续播放
|
||||
if (code === 3) {
|
||||
_this2._skipErrorAndRetry(errorMsg);
|
||||
} else {
|
||||
// 其他错误直接提示错误信息
|
||||
_this2.showError(errorMsg);
|
||||
_this2.unloadVideo();
|
||||
}
|
||||
|
||||
// emit 错误事件,传递详细的错误信息
|
||||
_this2.emit('error', {
|
||||
code: code,
|
||||
message: errorMsg,
|
||||
error: e
|
||||
});
|
||||
} else {
|
||||
_this2.emit('error', e);
|
||||
}
|
||||
_this2.emit('error', e);
|
||||
};
|
||||
//视频长按菜单取消
|
||||
this.video.oncontextmenu = function (e) {
|
||||
@@ -389,6 +440,7 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
key: "unloadVideo",
|
||||
value: function unloadVideo() {
|
||||
this._clearSeizingTimer();
|
||||
this._errorRetryCount = 0; // 重置错误计数
|
||||
this.unloadCustom();
|
||||
this.unloadDecoder();
|
||||
this.unloadPano();
|
||||
@@ -1080,25 +1132,7 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
key: "showPlaybackRate",
|
||||
value: function showPlaybackRate() {
|
||||
var _this10 = this;
|
||||
var arr = [{
|
||||
text: '0.5倍速',
|
||||
value: 0.5
|
||||
}, {
|
||||
text: '正常倍速',
|
||||
value: 1
|
||||
}, {
|
||||
text: '1.5倍速',
|
||||
value: 1.5
|
||||
}, {
|
||||
text: '1.75倍速',
|
||||
value: 1.75
|
||||
}, {
|
||||
text: '2倍速',
|
||||
value: 2
|
||||
}, {
|
||||
text: '3倍速',
|
||||
value: 3
|
||||
}];
|
||||
var arr = this.playbackRates;
|
||||
var list = arr.map(function (a, k) {
|
||||
return {
|
||||
text: a.text,
|
||||
@@ -1427,7 +1461,7 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
if (['timeDiffrence', 'disableScroll', 'disableTop', 'disableBottom', 'fontScale'].includes(key)) _this.cm.reset();
|
||||
}
|
||||
_this.emit('danmuconfigchange', config); //派发弹幕配置更改事件,以便开发者外部记录
|
||||
}, 500);
|
||||
}, 2000);
|
||||
}
|
||||
this.showPopup('弹幕设置', div);
|
||||
}
|
||||
@@ -2448,7 +2482,14 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
value: function hideLoading() {
|
||||
var div = this.container.getElementsByClassName('yb-player-loading')[0];
|
||||
if (div) div.remove();
|
||||
if (this.video.paused) this.showCenterPlay();else this.hideCenter();
|
||||
// if (this.video.paused) this.showCenterPlay();else this.hideCenter();
|
||||
if (this._isErrorRecovering) {
|
||||
this.hideCenter();
|
||||
} else if (this.video.paused) {
|
||||
this.showCenterPlay();
|
||||
} else {
|
||||
this.hideCenter();
|
||||
}
|
||||
}
|
||||
//展示中间播放按钮
|
||||
}, {
|
||||
@@ -2558,6 +2599,152 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
value: function seek(time) {
|
||||
if (this.video) this.video.currentTime = time;
|
||||
}
|
||||
/**
|
||||
* 跳过错误区间并重试播放
|
||||
* @param {String} errorMsg 错误信息
|
||||
*/
|
||||
}, {
|
||||
key: "_skipErrorAndRetry",
|
||||
value: function _skipErrorAndRetry(errorMsg) {
|
||||
var _this = this;
|
||||
|
||||
// 检查 video 对象是否存在
|
||||
if (!this.video) {
|
||||
console.log('[错误恢复] video 对象不存在,无法恢复');
|
||||
this.showError(errorMsg);
|
||||
this.unloadVideo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 超过最大重试次数,显示错误并卸载视频
|
||||
if (this._errorRetryCount >= this._maxErrorRetry) {
|
||||
this.showError(errorMsg);
|
||||
this.unloadVideo();
|
||||
return;
|
||||
}
|
||||
|
||||
this._errorRetryCount++;
|
||||
|
||||
// 第 1 次错误静默处理,显示 loading
|
||||
if (this._errorRetryCount === 1) {
|
||||
this.showLoading();
|
||||
}
|
||||
|
||||
// 获取当前播放时间,向后跳过 1 秒(跳过损坏的 GOP 区间)
|
||||
var currentTime = this.video.currentTime || 0;
|
||||
// 确保 currentTime 是有效数字
|
||||
if (isNaN(currentTime) || currentTime < 0) {
|
||||
currentTime = 0;
|
||||
}
|
||||
var duration = this.getDuration() || 0;
|
||||
var skipTime = Math.min(currentTime + 1, duration);
|
||||
|
||||
console.log('[错误恢复] 开始处理,当前时间:' + currentTime + 's, 跳转到:' + skipTime + 's');
|
||||
|
||||
// 关键:保存当前 src,然后重新加载视频来恢复状态
|
||||
// var currentSrc = this.video.currentSrc || this.video.src;
|
||||
console.log('[错误恢复] 重新加载视频...');
|
||||
|
||||
// 保存当前倍速设置
|
||||
var currentPlaybackRate = this.video.playbackRate || 1;
|
||||
console.log('[错误恢复] 保存倍速:' + currentPlaybackRate);
|
||||
|
||||
// 设置标志,告诉 loadeddata 不要执行 seek
|
||||
this._isErrorRecovering = true;
|
||||
|
||||
// 重新设置 src 并加载
|
||||
// this.video.src = currentSrc;
|
||||
this.video.load();
|
||||
|
||||
// 清除之前的定时器(如果有)
|
||||
if (this._errorRecoveryTimer) {
|
||||
clearTimeout(this._errorRecoveryTimer);
|
||||
}
|
||||
|
||||
// 等待元数据加载完成
|
||||
var onLoadedMetadata = function() {
|
||||
// 检查 video 对象是否仍然存在
|
||||
if (!_this.video) {
|
||||
console.log('[错误恢复] loadedmetadata 触发,但 video 对象已不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[错误恢复] loadedmetadata 触发,设置 currentTime = ' + skipTime);
|
||||
_this.video.removeEventListener('loadedmetadata', onLoadedMetadata);
|
||||
|
||||
// 清除超时定时器
|
||||
if (_this._errorRecoveryTimer) {
|
||||
clearTimeout(_this._errorRecoveryTimer);
|
||||
}
|
||||
|
||||
// 恢复倍速设置
|
||||
_this.video.playbackRate = currentPlaybackRate;
|
||||
console.log('[错误恢复] 恢复倍速:' + currentPlaybackRate);
|
||||
|
||||
// 设置跳转位置
|
||||
_this.video.currentTime = skipTime;
|
||||
|
||||
// 等待 seek 完成后立即播放
|
||||
var onSeeked = function() {
|
||||
// 检查 video 对象是否仍然存在
|
||||
if (!_this2.video) {
|
||||
console.log('[错误恢复] seeked 触发,但 video 对象已不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[错误恢复] seeked 触发,实际位置:' + _this2.video.currentTime);
|
||||
_this2.video.removeEventListener('seeked', onSeeked);
|
||||
_this2.tryPlayAfterSeek(errorMsg);
|
||||
};
|
||||
|
||||
_this.video.addEventListener('seeked', onSeeked);
|
||||
};
|
||||
|
||||
this.video.addEventListener('loadedmetadata', onLoadedMetadata);
|
||||
|
||||
// loadedmetadata 超时处理
|
||||
this._errorRecoveryTimer = setTimeout(function() {
|
||||
console.log('[错误恢复] loadedmetadata 超时,直接尝试播放');
|
||||
if (_this.video) {
|
||||
_this.video.removeEventListener('loadedmetadata', onLoadedMetadata);
|
||||
}
|
||||
_this.tryPlayAfterSeek(errorMsg);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 尝试播放
|
||||
}, {
|
||||
key: "tryPlayAfterSeek",
|
||||
value: function tryPlayAfterSeek(errorMsg) {
|
||||
var _this2 = this;
|
||||
|
||||
console.log('[错误恢复] 准备播放,当前状态:paused=' + this.video.paused + ', readyState=' + this.video.readyState + ', currentTime=' + this.video.currentTime);
|
||||
|
||||
// 直接调用 play(),Promise 会处理缓冲
|
||||
var playPromise = this.video.play();
|
||||
console.log('[错误恢复] play() 已调用');
|
||||
|
||||
if (playPromise && typeof playPromise.then === 'function') {
|
||||
playPromise.then(function() {
|
||||
console.log('[错误恢复] 播放成功!');
|
||||
_this2._errorRetryCount = 0;
|
||||
_this2._isErrorRecovering = false; // 清除错误恢复标志
|
||||
// 清除所有定时器
|
||||
if (_this2._errorRecoveryTimer) {
|
||||
clearTimeout(_this2._errorRecoveryTimer);
|
||||
_this2._errorRecoveryTimer = null;
|
||||
}
|
||||
_this2.hideLoading();
|
||||
}).catch(function(err) {
|
||||
console.log('[错误恢复] 播放失败:', err);
|
||||
_this2._skipErrorAndRetry(errorMsg);
|
||||
});
|
||||
} else {
|
||||
_this2._errorRetryCount = 0;
|
||||
_this2._isErrorRecovering = false;
|
||||
_this2.hideLoading();
|
||||
}
|
||||
}
|
||||
//播放上一个视频
|
||||
}, {
|
||||
key: "prev",
|
||||
@@ -2959,6 +3146,14 @@ var YbPlayer = /*#__PURE__*/function () {
|
||||
this._seizingTimer = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "_clearRateTimer",
|
||||
value: function _clearRateTimer() {
|
||||
if (this._rateTimer) {
|
||||
window.clearTimeout(this._rateTimer);
|
||||
this._rateTimer = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "_removeBackbuttonListener",
|
||||
value: function _removeBackbuttonListener() {
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
workIndex: params.workIndex,
|
||||
subtitles: params.subtitles,
|
||||
subtitleIndex: params.subtitleIndex,
|
||||
playbackRates: params.playbackRates,
|
||||
custom: params.custom,
|
||||
decoder: {
|
||||
hls: {
|
||||
|
||||
Reference in New Issue
Block a user