This commit is contained in:
owenchen committed 2026-10-20 21:51:06 +08:00
1 parent 6417562c4f
commit f1e6e116c3
31 files changed
+2065 -309

No files matched your search

@@ -51,6 +51,46 @@ function setControls(v: boolean): void {
}
watch(() => props.controls, v => (localControls.value = v ?? true));
/**
* App 端 <video> 是原生控件层:DOM 节点、cover-view 都会被整层盖住,
* 只有 plus 原生视图能画在画面上,所以左上角返回改用它绘制(H5 走模板里的 vp-back)。
*/
let backView: any = null;
function mountNativeBack(): void {
const ViewCtor = plus.nativeObj.View as any;
if (!ViewCtor) {
return;
}
// 原生视图不受页面布局影响,而 webview 是沉浸式的(从屏幕顶部开始):
// 自己在 24rpx 边距上叠加系统状态栏高度,与 H5 的 .vp-back 保持一致
const btn = Math.round(uni.upx2px(72)) || 41;
const gap = Math.round(uni.upx2px(24)) || 14;
const statusH = uni.getSystemInfoSync().statusBarHeight || 0;
backView = new ViewCtor("vp-native-back", {
top: `${gap + statusH}px`,
left: `${gap}px`,
width: `${btn}px`,
height: `${btn}px`,
// View 默认白底,透明后只剩下面绘制的内容
backgroundColor: "rgba(0,0,0,0)",
});
// 半透明圆底 + 白色 "<":uview 图标字体在 App 端只有 woff2(原生字体不支持),用系统字形 "‹" 代替;
// 与 H5 的 .vp-back / 音乐厅 .vap-back 同款:rgba(0,0,0,0.22) 圆底、无描边
backView.drawRect(
{ color: "rgba(0,0,0,0.22)", radius: `${btn / 2}px` },
{ top: "0px", left: "0px", width: `${btn}px`, height: `${btn}px` },
"vp-back-bg",
);
backView.drawText(
"‹",
{ top: "0px", left: "0px", width: `${btn}px`, height: `${btn}px` },
{ color: "#FFFFFF", size: `${Math.round(btn * 0.82)}px`, align: "center" },
"vp-back-glyph",
);
backView.addEventListener("click", () => emit("close"));
backView.show();
}
const isPlaying = ref(false);
const buffering = ref(false);
const currentTime = ref(0);
@@ -268,6 +308,11 @@ function loadCurrent(autoplay: boolean): void {
}
function toggle(): void {
// 控制区收起时,点画面先把控制条叫回来(App 端画面上的恢复按钮会被原生层盖住)
if (!controlsVisible.value) {
setControls(true);
return;
}
ensureCtx();
if (isPlaying.value) {
ctx?.pause();
@@ -623,6 +668,9 @@ const progressPercent = computed(() => (duration.value > 0 ? Math.min(100, (curr
const bufferPercent = computed(() => Math.min(100, Math.max(0, bufferedRatio.value * 100)));
onMounted(() => {
// #ifdef APP-PLUS
mountNativeBack();
// #endif
// #ifdef H5
const el = getInnerVideo();
nativeHlsOk.value = !!(el?.canPlayType
@@ -634,6 +682,12 @@ onMounted(() => {
nextTick(() => measureBar());
});
onBeforeUnmount(() => {
// #ifdef APP-PLUS
if (backView) {
backView.close();
backView = null;
}
// #endif
destroyHls();
if (sleepTimer) {
clearTimeout(sleepTimer);
@@ -657,7 +711,7 @@ watch(controlsVisible, (v) => {
<template>
<view class="vp">
<!-- 顶部:仅返回,绝对定位浮在画面上不占布局高度(列表入口在底部控制区 ≡) -->
<!-- 左上角返回:浮在画面上不占布局(App 端该位置被视频原生层盖住,由 plus 原生视图绘制,见 mountNativeBack) -->
<view v-if="controlsVisible" class="vp-back" @click="emit('close')">
<u-icon name="arrow-left" size="40" color="#ffffff" />
</view>
@@ -686,7 +740,7 @@ watch(controlsVisible, (v) => {
<u-icon name="play-right-fill" size="80" color="#ffffff" />
</view>
<!-- 收起控制区后的恢复入口 -->
<!-- 收起控制区后的恢复入口(H5;App 端该位置被视频层盖住,改为点画面恢复) -->
<view v-if="!controlsVisible" class="vp-restore" @click="toggleControls">
<u-icon name="eye" size="34" color="#ffffff" />
</view>
@@ -825,8 +879,15 @@ watch(controlsVisible, (v) => {
.vp-back {
position: absolute;
top: calc(16rpx + var(--status-bar-height));
left: 16rpx;
/* 与音乐厅 .vap-back 完全一致(同尺寸/同底色/无描边) */
/* App 端用 uni 注入的状态栏高度避让;H5 用 safe-area */
/* #ifdef APP-PLUS */
top: calc(24rpx + var(--status-bar-height, 0px));
/* #endif */
/* #ifndef APP-PLUS */
top: calc(24rpx + env(safe-area-inset-top));
/* #endif */
left: 24rpx;
z-index: 20;
display: flex;
align-items: center;
@@ -834,7 +895,7 @@ watch(controlsVisible, (v) => {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.28);
background: rgba(0, 0, 0, 0.22);
}
.vp-stage {
@@ -844,7 +905,8 @@ watch(controlsVisible, (v) => {
display: flex;
align-items: center;
justify-content: center;
background: #000;
/* 空态不铺黑:透出 .vp 的青绿渐变,与音乐厅同底色,半透明圆钮才看得见 */
background: transparent;
overflow: hidden;
}
@@ -913,12 +975,12 @@ watch(controlsVisible, (v) => {
top: 24rpx;
right: 24rpx;
z-index: 9;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: rgba(6, 56, 62, 0.5);
border: 2rpx solid rgba(255, 255, 255, 0.45);
}