diff --git a/manifest.json b/manifest.json index 05381bd..18efcfb 100644 --- a/manifest.json +++ b/manifest.json @@ -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" : { diff --git a/package-lock.json b/package-lock.json index ec9f34e..71abbcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 14a646d..30779ea 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/uni_modules/yingbing-video/components/modules/video.vue b/uni_modules/yingbing-video/components/modules/video.vue index c5e3b7e..ab221a1 100644 --- a/uni_modules/yingbing-video/components/modules/video.vue +++ b/uni_modules/yingbing-video/components/modules/video.vue @@ -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: { diff --git a/uni_modules/yingbing-video/components/yb-video/yb-video.vue b/uni_modules/yingbing-video/components/yb-video/yb-video.vue index cb49c0b..47ddf7d 100644 --- a/uni_modules/yingbing-video/components/yb-video/yb-video.vue +++ b/uni_modules/yingbing-video/components/yb-video/yb-video.vue @@ -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) diff --git a/uni_modules/yingbing-video/static/html/css/yb-player.css b/uni_modules/yingbing-video/static/html/css/yb-player.css index 08c97ce..5b7f75b 100644 --- a/uni_modules/yingbing-video/static/html/css/yb-player.css +++ b/uni_modules/yingbing-video/static/html/css/yb-player.css @@ -54,7 +54,7 @@ } .yb-player-time { - min-width: 35px; + min-width: 38px; } /* .yb-player-duration { diff --git a/uni_modules/yingbing-video/static/html/dist/yb-player.js b/uni_modules/yingbing-video/static/html/dist/yb-player.js index c3b01a8..13481dc 100644 --- a/uni_modules/yingbing-video/static/html/dist/yb-player.js +++ b/uni_modules/yingbing-video/static/html/dist/yb-player.js @@ -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
\n\t\t\t\t\t".concat(playbackRate + '倍速播放中', "\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() { diff --git a/uni_modules/yingbing-video/static/html/video.html b/uni_modules/yingbing-video/static/html/video.html index 12f9f00..e27cd1d 100644 --- a/uni_modules/yingbing-video/static/html/video.html +++ b/uni_modules/yingbing-video/static/html/video.html @@ -80,6 +80,7 @@ workIndex: params.workIndex, subtitles: params.subtitles, subtitleIndex: params.subtitleIndex, + playbackRates: params.playbackRates, custom: params.custom, decoder: { hls: {