feat: 更新视频播放器功能并修复多个问题

- 升级edu-core依赖至v1.0.8
- 新增测试页面路由配置
- 修复订单页面Android平台专属支付按钮逻辑
- 优化视频播放器组件,增加倍速播放配置和控件显示逻辑
- 修复iOS平台视频封面显示问题
- 改进全屏模式处理逻辑
- 优化进度条和控制栏交互体验
- 修复DOM元素查找延迟问题
- 移除课程详情页冗余刷新逻辑
This commit is contained in:
2026-03-03 16:01:58 +08:00
parent a67874754f
commit 5200c73bc5
14 changed files with 420 additions and 143 deletions

View File

@@ -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: {

View File

@@ -105,6 +105,11 @@
type: Number,
default: 1
},
//倍速列表配置
playbackRates: {
type: Array,
default: () => []
},
//循环播放
loop: {
type: Boolean,
@@ -219,8 +224,13 @@
return 'web'
}
}
}
},
// #endif
//是否一直显示控件
alwaysShowControls: {
type: Boolean,
default: false
}
},
computed: {
boxStyle () {
@@ -250,6 +260,7 @@
created() {
//获取当前运行的平台
this.updateHeight()
console.log('倍速列表是否是ios YINGBING' + JSON.stringify(this.playbackRates))
},
methods: {
//接收消息
@@ -513,36 +524,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 +621,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 +792,12 @@
playbackRate (newVal) {
this.setVideo('playbackRate', newVal)
},
//监听倍速列表
playbackRates (newVal) {
if (newVal && newVal.length > 0) {
this.reloadVideo()
}
},
//监听循环属性
loop (newVal) {
this.setVideo('loop', newVal)
@@ -791,9 +811,13 @@
this.updateConfig()
},
//监听controls
controls () {
this.updateConfig()
},
controls () {
this.updateConfig()
},
//监听alwaysShowControls
alwaysShowControls () {
this.updateConfig()
},
//深度监听custom
custom: {
handler(newVal, oldVal) {

View File

@@ -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;

View File

@@ -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,31 @@ var YbPlayer = /*#__PURE__*/function () {
this.loop = loop; //是否循环播放
this.muted = muted; //是否静音
this.playbackRate = playbackRate || 1; //默认播放倍速
console.log('倍速列表' + JSON.stringify(playbackRates))
this.playbackRates = playbackRates && playbackRates.length > 0 ? playbackRates : [{
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
}]; //倍速列表配置
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-默认值
@@ -111,7 +134,12 @@ var YbPlayer = /*#__PURE__*/function () {
}, {
key: "load",
value: function load() {
this.container.innerHTML = "\n\t\t\t<div class=\"yb-player-wrapper\" style=\"height: ".concat(this.height, "\">\n\t\t\t\t<div class=\"yb-player-header\"></div>\n\t\t\t\t<div class=\"yb-player-controls\">\n\t\t\t\t\t<div class=\"yb-player-progress\"></div>\n\t\t\t\t\t<div class=\"yb-player-controls-bottom yb-player-hide yb-player-full\"></div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"yb-player-locks yb-player-hide\">\n\t\t\t\t\t<div class=\"yb-player-lock yb-player-lock-left\">").concat(YbPlayer.lockIcon, "</div>\n\t\t\t\t\t<div class=\"yb-player-lock yb-player-lock-right\">").concat(YbPlayer.lockIcon, "</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"yb-player-danmu\"></div>\n\t\t\t\t<div class=\"yb-player-toast\"></div>\n\t\t\t\t<div class=\"yb-player-bottom-progress yb-player-hide\"></div>\n\t\t\t</div>\n\t\t");
var posterHtml = '';
// 针对 iOS 平台添加备用封面显示
if (this.poster && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
posterHtml = '<div class="yb-player-poster" style="position:absolute;top:0;left:0;width:100%;height:100%;background-image:url(' + this.poster + ');background-size:cover;background-position:center;"></div>';
}
this.container.innerHTML = posterHtml + "\n\t\t\t<div class=\"yb-player-wrapper\" style=\"height: ".concat(this.height, "\">\n\t\t\t\t<div class=\"yb-player-header\"></div>\n\t\t\t\t<div class=\"yb-player-controls\">\n\t\t\t\t\t<div class=\"yb-player-progress\"></div>\n\t\t\t\t\t<div class=\"yb-player-controls-bottom yb-player-hide yb-player-full\"></div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"yb-player-locks yb-player-hide\">\n\t\t\t\t\t<div class=\"yb-player-lock yb-player-lock-left\">").concat(YbPlayer.lockIcon, "</div>\n\t\t\t\t\t<div class=\"yb-player-lock yb-player-lock-right\">").concat(YbPlayer.lockIcon, "</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"yb-player-danmu\"></div>\n\t\t\t\t<div class=\"yb-player-toast\"></div>\n\t\t\t\t<div class=\"yb-player-bottom-progress yb-player-hide\"></div>\n\t\t\t</div>\n\t\t");
this._bindfullscreenerror = this._fullscreenerror.bind(this);
this._bindfullscreenchanged = this._fullscreenchanged.bind(this);
this.container.addEventListener('fullscreenerror', this._bindfullscreenerror);
@@ -857,12 +885,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 +1101,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,
@@ -1653,8 +1664,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 +1787,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 +1865,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 +1881,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 +1903,10 @@ var YbPlayer = /*#__PURE__*/function () {
}
//重新设置一次全屏状态
this._setFullscreenStatus();
// 如果设置了alwaysShowControls自动显示控件
if (this.alwaysShowControls) {
this.showControls();
}
}
//卸载自定义
}, {
@@ -2089,7 +2125,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();
}
}
});
}
//动态手势事件配置
@@ -2669,19 +2711,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 +2741,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 +2816,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');

View File

@@ -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: {