-
@@ -49,10 +50,15 @@
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { courseApi } from '@/api/modules/course'
-import AliPlayer from '@/components/ali-video/index.vue'
import type { IChapterDetail } from '@/types/course'
import type { IVideoInfo } from '@/types/video'
+import { mainClient } from '@/api/clients'
+
+import CourseVideo from '@/components/course/chapter-video.vue'
+import { useSafeBack } from '@/components/nav-bar/use-safe-back'
+useSafeBack()
+
// 页面参数
const chapterId = ref(0)
const courseTitle = ref('')
diff --git a/uni_modules/yingbing-video/components/modules/video.vue b/uni_modules/yingbing-video/components/modules/video.vue
index 949e8c6..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
@@ -455,6 +468,7 @@
isLive: params.isLive,
header: params.header,
controls: params.controls,
+ alwaysShowControls: params.alwaysShowControls,
height: '100%',
objectFit: params.objectFit,
crossOrigin: params.crossOrigin,
@@ -465,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 6366904..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,
@@ -219,8 +229,13 @@
return 'web'
}
}
- }
+ },
// #endif
+ //是否一直显示控件
+ alwaysShowControls: {
+ type: Boolean,
+ default: false
+ }
},
computed: {
boxStyle () {
@@ -513,36 +528,38 @@
//重加载视频
async reloadVideo () {
const arg = {
- src: this.parseSrc(this.src),
- segments: this.parseSegments(this.segments),
- title: this.title,
- poster: this.poster,
- type: this.type,
- three: this.three,
- initialTime: this.initialTime,
- duration: this.duration,
- autoplay: this.autoplay,
- preload: this.preload,
- muted: this.muted,
- loop: this.loop,
- playbackRate: this.playbackRate,
- isLive: this.isLive,
- header: this.header,
- controls: this.controls,
- objectFit: this.objectFit,
- crossOrigin: this.crossOrigin,
- openDirection: this.openDirection,
- exitDirection: this.exitDirection,
- quality: this.parseList(this.quality),
- works: this.works,
- workIndex: this.workIndex,
- subtitles: this.parseList(this.subtitles),
- subtitleIndex: this.subtitleIndex,
- custom: await this.parseCustom(),
- flvConfig: this.flvConfig,
- hlsConfig: this.hlsConfig,
- jsmpegConfig: this.jsmpegConfig
- }
+ src: this.parseSrc(this.src),
+ segments: this.parseSegments(this.segments),
+ title: this.title,
+ poster: this.poster,
+ type: this.type,
+ three: this.three,
+ initialTime: this.initialTime,
+ duration: this.duration,
+ autoplay: this.autoplay,
+ preload: this.preload,
+ muted: this.muted,
+ loop: this.loop,
+ playbackRate: this.playbackRate,
+ playbackRates: this.playbackRates,
+ isLive: this.isLive,
+ header: this.header,
+ controls: this.controls,
+ alwaysShowControls: this.alwaysShowControls,
+ objectFit: this.objectFit,
+ crossOrigin: this.crossOrigin,
+ openDirection: this.openDirection,
+ exitDirection: this.exitDirection,
+ quality: this.parseList(this.quality),
+ works: this.works,
+ workIndex: this.workIndex,
+ subtitles: this.parseList(this.subtitles),
+ subtitleIndex: this.subtitleIndex,
+ custom: await this.parseCustom(),
+ flvConfig: this.flvConfig,
+ hlsConfig: this.hlsConfig,
+ jsmpegConfig: this.jsmpegConfig
+ }
this.evalJS('reloadVideo', arg)
},
//卸载视频
@@ -608,7 +625,8 @@
this.updateTimer = setTimeout(() => {
const arg = {
header: this.header,
- controls: this.controls
+ controls: this.controls,
+ alwaysShowControls: this.alwaysShowControls
}
this.evalJS('updateConfig', arg)
}, 200)
@@ -778,6 +796,12 @@
playbackRate (newVal) {
this.setVideo('playbackRate', newVal)
},
+ //监听倍速列表
+ playbackRates (newVal) {
+ if (newVal && newVal.length > 0) {
+ this.reloadVideo()
+ }
+ },
//监听循环属性
loop (newVal) {
this.setVideo('loop', newVal)
@@ -791,9 +815,13 @@
this.updateConfig()
},
//监听controls
- controls () {
- this.updateConfig()
- },
+ controls () {
+ this.updateConfig()
+ },
+ //监听alwaysShowControls
+ alwaysShowControls () {
+ this.updateConfig()
+ },
//深度监听custom
custom: {
handler(newVal, oldVal) {
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 5577246..5b7f75b 100644
--- a/uni_modules/yingbing-video/static/html/css/yb-player.css
+++ b/uni_modules/yingbing-video/static/html/css/yb-player.css
@@ -53,6 +53,14 @@
pointer-events: none;
}
+.yb-player-time {
+ min-width: 38px;
+}
+
+/* .yb-player-duration {
+ width: 35px;
+} */
+
.yb-player-bottom-progress {
position: absolute;
bottom: 0;
@@ -120,22 +128,22 @@
width: 0;
flex: 1;
position: relative;
- height: 2px;
+ height: 30px;
margin: 0 10px;
}
.yb-player-range-track {
position: absolute;
- top: 0;
+ top: 14px;
left: 0;
right: 0;
- bottom: 0;
+ bottom: 14px;
background-color: #999;
}
.yb-player-range-focus, .yb-player-range-preload {
position: absolute;
- top: 0;
+ top: 14px;
left: 0;
- bottom: 0;
+ bottom: 14px;
background-color: #fff;
width: 0;
}
@@ -162,7 +170,7 @@
background: none;
outline: none;
width: 100%;
- height: 2px;
+ height: 30px;
margin: 0;
position: absolute;
top: 0;
@@ -501,6 +509,11 @@
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
+ background-color: rgba(0, 0, 0, 0.3);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.yb-player-center svg {
width: 50px;
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 88fcede..13481dc 100644
--- a/uni_modules/yingbing-video/static/html/dist/yb-player.js
+++ b/uni_modules/yingbing-video/static/html/dist/yb-player.js
@@ -36,6 +36,7 @@ var YbPlayer = /*#__PURE__*/function () {
isLive = _ref.isLive,
header = _ref.header,
controls = _ref.controls,
+ alwaysShowControls = _ref.alwaysShowControls,
height = _ref.height,
crossOrigin = _ref.crossOrigin,
objectFit = _ref.objectFit,
@@ -50,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; //播放链接
@@ -63,10 +65,36 @@ 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; //显示头部控制栏
this.controls = controls; //显示底部控制栏
+ this.alwaysShowControls = alwaysShowControls; //是否一直显示控件
this.height = height || 'auto'; //视频高度
this.crossOrigin = crossOrigin; //跨域属性 anonymous-它有一个默认值。它定义了将在不传递凭据信息的情况下发送CORS请求 use-credentials-将发送带有凭据、cookie 和证书的 cross-origin 请求
this.objectFit = objectFit; //当视频宽高超出容器时的表现形式 fill-内容拉伸填充 contain-保持比例内容缩放 cover-保持比例内容可能被剪切 none-内容不重置 scale-down-保持比例从none或contain选一个 initial-默认值
@@ -97,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 = {};
}
//开启全屏按钮
@@ -111,7 +142,12 @@ var YbPlayer = /*#__PURE__*/function () {
}, {
key: "load",
value: function load() {
- this.container.innerHTML = "\n\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
").concat(YbPlayer.lockIcon, "
\n\t\t\t\t\t
").concat(YbPlayer.lockIcon, "
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t");
+ var posterHtml = '';
+ // 针对 iOS 平台添加备用封面显示
+ if (this.poster && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
+ posterHtml = '';
+ }
+ this.container.innerHTML = posterHtml + "\n\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
").concat(YbPlayer.lockIcon, "
\n\t\t\t\t\t
").concat(YbPlayer.lockIcon, "
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t");
this._bindfullscreenerror = this._fullscreenerror.bind(this);
this._bindfullscreenchanged = this._fullscreenchanged.bind(this);
this.container.addEventListener('fullscreenerror', this._bindfullscreenerror);
@@ -129,6 +165,7 @@ var YbPlayer = /*#__PURE__*/function () {
this._clearDanmuTimer();
this._clearToastTimer();
this._clearControlsTimer();
+ this._clearRateTimer();
this._removeBackbuttonListener();
this._event = {}; //卸载所有监听事件
if (this.container) {
@@ -198,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,
@@ -227,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\t\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();
}
@@ -325,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) {
@@ -342,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) {
@@ -382,6 +440,7 @@ var YbPlayer = /*#__PURE__*/function () {
key: "unloadVideo",
value: function unloadVideo() {
this._clearSeizingTimer();
+ this._errorRetryCount = 0; // 重置错误计数
this.unloadCustom();
this.unloadDecoder();
this.unloadPano();
@@ -857,12 +916,13 @@ var YbPlayer = /*#__PURE__*/function () {
wrapperEl.appendChild(toolbarEl); //先插入不显示的工具栏,因为需要计算工具栏尺寸
var element = this.container.querySelector(selector); //获取点击选择器
var rect = element.getBoundingClientRect(); //获取点击元素的尺寸布局信息
+ var containerRect = this.container.getBoundingClientRect(); //获取容器相对于视口的位置
var boxWidth = this.container.offsetWidth;
var boxHeight = this.container.offsetHeight;
- var top = rect.top + rect.height; //计算顶部定位
- var left = rect.left; //计算左边定位
- var right = boxWidth - rect.right; //计算右边定位
- var bottom = boxHeight - rect.bottom + rect.height; //计算底部定位
+ var top = rect.top - containerRect.top + rect.height; //计算顶部定位(相对于容器)
+ var left = rect.left - containerRect.left; //计算左边定位(相对于容器)
+ var right = containerRect.right - rect.right; //计算右边定位
+ var bottom = containerRect.bottom - rect.bottom + rect.height; //计算底部定位
var isTop = true; //是否定位顶部 true-定位顶部 false-定位底部
var isLeft = true; //是否定位左边 true-定位左边 false-定位右边
if (top > boxHeight / 2) isTop = false; //判断点击元素是否在盒子上半部
@@ -1072,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,
@@ -1419,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);
}
@@ -1653,8 +1695,13 @@ var YbPlayer = /*#__PURE__*/function () {
});
var children = progressEl.children;
for (var i = 0; i < children.length; i++) {
- // children[i].onclick = arr[i].click
- YbPlayer.tap(children[i], arr[i].click);
+ (function(index) {
+ var originalClick = arr[index].click;
+ YbPlayer.tap(children[index], function() {
+ if (originalClick) originalClick();
+ _this17.showControls();
+ });
+ })(i);
//如果标记了非全屏元素,则在全屏时需要隐藏
if (children[i].classList.contains('yb-player-unfull') && this.getFullscreen()) {
children[i].classList.add('yb-player-hide');
@@ -1771,8 +1818,13 @@ var YbPlayer = /*#__PURE__*/function () {
});
var children = controlsBottomEl.children;
for (var _i = 0; _i < children.length; _i++) {
- // children[i].onclick = arr[i].click
- YbPlayer.tap(children[_i], arr[_i].click);
+ (function(index) {
+ var originalClick = arr[index].click;
+ YbPlayer.tap(children[index], function() {
+ if (originalClick) originalClick();
+ _this17.showControls();
+ });
+ })(_i);
}
}
@@ -1844,8 +1896,13 @@ var YbPlayer = /*#__PURE__*/function () {
});
var children = heightEl.children;
for (var _i2 = 0; _i2 < children.length; _i2++) {
- // children[i].onclick = arr[i].click
- YbPlayer.tap(children[_i2], arr[_i2].click);
+ (function(index) {
+ var originalClick = arr[index].click;
+ YbPlayer.tap(children[index], function() {
+ if (originalClick) originalClick();
+ _this17.showControls();
+ });
+ })(_i2);
}
}
@@ -1855,13 +1912,19 @@ var YbPlayer = /*#__PURE__*/function () {
if (slots.length > 0 && wrapperEl) {
var slotEl = document.createElement('DIV');
slotEl.setAttribute('class', 'yb-player-slot');
+ var _this = this;
slots.forEach(function (slot, key) {
slotEl.innerHTML += slot.innerHTML;
});
var children = slotEl.children;
for (var _i3 = 0; _i3 < children.length; _i3++) {
- // children[i].onclick = slots[i].click
- YbPlayer.tap(children[_i3], slots[_i3].click);
+ (function(index) {
+ var originalClick = slots[index].click;
+ YbPlayer.tap(children[index], function() {
+ if (originalClick) originalClick();
+ _this.showControls();
+ });
+ })(_i3);
if (slots[_i3].followControls) {
children[_i3].setAttribute('data-controls', 1);
if (!this.getControls()) children[_i3].classList.add('yb-player-hide');
@@ -1871,6 +1934,10 @@ var YbPlayer = /*#__PURE__*/function () {
}
//重新设置一次全屏状态
this._setFullscreenStatus();
+ // 如果设置了alwaysShowControls,自动显示控件
+ if (this.alwaysShowControls) {
+ this.showControls();
+ }
}
//卸载自定义
}, {
@@ -2089,7 +2156,13 @@ var YbPlayer = /*#__PURE__*/function () {
value: function setConfig(key, value) {
var _this20 = this;
Object.keys(this).forEach(function (k) {
- if (k == key) _this20[k] = value;
+ if (k == key) {
+ _this20[k] = value;
+ //如果更新的是alwaysShowControls属性,需要重新显示控件
+ if (k == 'alwaysShowControls' && value) {
+ _this20.showControls();
+ }
+ }
});
}
//动态手势事件配置
@@ -2409,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();
+ }
}
//展示中间播放按钮
}, {
@@ -2519,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",
@@ -2669,19 +2895,27 @@ var YbPlayer = /*#__PURE__*/function () {
value: function exitFullscreen() {
var _this26 = this;
this.setDirection(false).then(function () {
- var cfs = document.exitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen || document.webkitExitFullscreen;
- if (typeof cfs != 'undefined' && cfs) {
- cfs.call(document);
- } else if (typeof window.ActiveXObject !== "undefined") {
- //IE浏览器,模拟按下F11键退出全屏
- var wscript = new ActiveXObject("WScript.Shell");
- if (wscript != null) {
- wscript.SendKeys("{F11}");
- }
- } else {
- _this26.container.classList.remove('yb-player-openfull');
- _this26._fullscreenchanged();
- }
+ // #ifdef APP-PLUS
+ // 不使用浏览器的退出全屏了,全部通过css模拟退出全屏
+ _this26.container.classList.remove('yb-player-openfull');
+ _this26._fullscreenchanged();
+ // #endif
+
+ // #ifndef APP-PLUS
+ // var cfs = document.exitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen || document.webkitExitFullscreen;
+ // if (typeof cfs != 'undefined' && cfs) {
+ // cfs.call(document);
+ // } else if (typeof window.ActiveXObject !== "undefined") {
+ // //IE浏览器,模拟按下F11键退出全屏
+ // var wscript = new ActiveXObject("WScript.Shell");
+ // if (wscript != null) {
+ // wscript.SendKeys("{F11}");
+ // }
+ // } else {
+ // _this26.container.classList.remove('yb-player-openfull');
+ // _this26._fullscreenchanged();
+ // }
+ // #endif
});
}
//开启全屏
@@ -2691,18 +2925,25 @@ var YbPlayer = /*#__PURE__*/function () {
var _this27 = this;
this.openDirection = direction || this.openDirection;
this.setDirection(true).then(function () {
- var rfs = document.documentElement.requestFullscreen || document.documentElement.webkitRequestFullscreen || document.documentElement.mozRequestFullscreen || document.documentElement.requestFullScreen || document.documentElement.webkitRequestFullScreen || document.documentElement.mozRequestFullScreen;
- if (typeof rfs != 'undefined' && rfs) {
- rfs.call(_this27.container);
- } else if (typeof window.ActiveXObject !== "undefined") {
- //IE浏览器,模拟按下F11全屏
- var wscript = new ActiveXObject("WScript.Shell");
- if (wscript != null) {
- wscript.SendKeys("{F11}");
- }
- } else {
- _this27._fullscreenerror();
- }
+ // #ifdef APP-PLUS
+ // 不使用浏览器的全屏了,全部通过css模拟全屏
+ _this27._fullscreenerror();
+ // #endif
+
+ // #ifndef APP-PLUS
+ // var rfs = document.documentElement.requestFullscreen || document.documentElement.webkitRequestFullscreen || document.documentElement.mozRequestFullscreen || document.documentElement.requestFullScreen || document.documentElement.webkitRequestFullScreen || document.documentElement.mozRequestFullScreen;
+ // if (typeof rfs != 'undefined' && rfs) {
+ // rfs.call(_this27.container);
+ // } else if (typeof window.ActiveXObject !== "undefined") {
+ // //IE浏览器,模拟按下F11全屏
+ // var wscript = new ActiveXObject("WScript.Shell");
+ // if (wscript != null) {
+ // wscript.SendKeys("{F11}");
+ // }
+ // } else {
+ // _this27._fullscreenerror();
+ // }
+ // #endif
});
}
//是否支持全屏API
@@ -2759,14 +3000,17 @@ var YbPlayer = /*#__PURE__*/function () {
this.emit('controlschange', {
show: true
});
- this._controlsTimer = window.setTimeout(function () {
- _this28.hideControls();
- }, 5000);
+ if (!this.alwaysShowControls) {
+ this._controlsTimer = window.setTimeout(function () {
+ _this28.hideControls();
+ }, 5000);
+ }
}
//关闭控制栏
}, {
key: "hideControls",
value: function hideControls(item, timer) {
+ if (this.alwaysShowControls) return;
this._clearControlsTimer();
this.container.getElementsByClassName('yb-player-controls')[0].classList.remove('yb-player-controls-show');
this.container.getElementsByClassName('yb-player-header')[0].classList.remove('yb-player-header-show');
@@ -2902,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 5c22c7b..e27cd1d 100644
--- a/uni_modules/yingbing-video/static/html/video.html
+++ b/uni_modules/yingbing-video/static/html/video.html
@@ -69,6 +69,7 @@
isLive: params.isLive,
header: params.header,
controls: params.controls,
+ alwaysShowControls: params.alwaysShowControls,
height: '100%',
objectFit: params.objectFit,
crossOrigin: params.crossOrigin,
@@ -79,6 +80,7 @@
workIndex: params.workIndex,
subtitles: params.subtitles,
subtitleIndex: params.subtitleIndex,
+ playbackRates: params.playbackRates,
custom: params.custom,
decoder: {
hls: {