| | |
| | | <wd-watermark v-if="videoWatermarkText" class="video-watermark" :content="videoWatermarkText" |
| | | :full-screen="false" :width="160" :height="110" :gutter-x="36" :gutter-y="36" :size="15" |
| | | color="rgba(255, 255, 255, 0.24)" :rotate="-18" :opacity="1" :z-index="2" /> |
| | | <view v-if="showPlaybackRetry" class="video-retry-mask" @tap.stop="retryPlayback"> |
| | | <view v-if="showPlaybackRetry" class="video-retry-mask" @tap.stop="retryPlayback(true)"> |
| | | <text class="video-retry-text">点击开始播放</text> |
| | | </view> |
| | | </view> |
| | |
| | | // return |
| | | // } |
| | | if (canPlayVideo(item)) { |
| | | playSelectedVideo(item) |
| | | // 点击章节属于用户手势,需透传给播放逻辑,否则 H5 会被浏览器自动播放策略拦截 |
| | | playSelectedVideo(item, true) |
| | | return |
| | | } |
| | | handlePaidVideoTap(item) |
| | |
| | | |
| | | /** |
| | | * 切换顶部播放器到指定小节,并在可自动播放的环境中立即开始播放。 |
| | | * @param fromUserGesture 是否由用户点击章节触发,H5 需要借助该手势放行自动播放 |
| | | */ |
| | | async function playSelectedVideo(item : CourseVideo) { |
| | | async function playSelectedVideo(item : CourseVideo, fromUserGesture = false) { |
| | | if (!canPlayVideo(item)) { |
| | | return |
| | | } |
| | | if (activeVideoId.value === item.id && showVideoPlayer.value) { |
| | | retryPlayback() |
| | | retryPlayback(fromUserGesture) |
| | | return |
| | | } |
| | | activeVideoId.value = item.id |
| | |
| | | resetVideoPlaybackFlags() |
| | | await nextTick() |
| | | initVideoContext() |
| | | retryPlayback() |
| | | retryPlayback(fromUserGesture) |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 对当前播放器执行播放重试,避免首次切换章节时无感失败。 |
| | | * H5 下取出播放器内部的原生 video 节点。 |
| | | * uni 的 videoContext.play() 在 H5 下拿不到 Promise,无法感知“被浏览器拦截”的情况,这里直接操作原生节点。 |
| | | */ |
| | | function retryPlayback() { |
| | | if (isReadonlyDisplayMode) { |
| | | function resolveNativeVideoElement() { |
| | | // #ifdef H5 |
| | | const container = document.getElementById(VIDEO_PLAYER_ID) |
| | | if (!container) { |
| | | return null |
| | | } |
| | | // id 可能挂在播放器外层节点上,也可能直接落在原生 video 节点上,两种结构都兼容 |
| | | if (container instanceof HTMLVideoElement) { |
| | | return container |
| | | } |
| | | return container.querySelector('video') |
| | | // #endif |
| | | return null |
| | | } |
| | | |
| | | /** |
| | | * 对当前播放器执行播放重试,避免首次切换章节时无感失败。 |
| | | * @param fromUserGesture 是否由用户点击触发:H5 有自动播放限制,只有用户手势内调用 play 才可靠 |
| | | */ |
| | | function retryPlayback(fromUserGesture = false) { |
| | | // H5 下非用户手势触发的自动播放会被浏览器拦截,这里直接跳过;点击章节或遮罩属于用户手势,需要真正执行 |
| | | if (isReadonlyDisplayMode && !fromUserGesture) { |
| | | return |
| | | } |
| | | if (!videoContext || !currentVideoUrl.value) { |
| | | if (!currentVideoUrl.value) { |
| | | return |
| | | } |
| | | if (isReadonlyDisplayMode) { |
| | | const nativeVideo = resolveNativeVideoElement() |
| | | if (!nativeVideo) { |
| | | playbackFailed.value = true |
| | | return |
| | | } |
| | | const playPromise = nativeVideo.play() |
| | | if (playPromise) { |
| | | // 播放被拦截(如非用户手势)时展示“点击开始播放”遮罩兜底 |
| | | playPromise.catch(() => { |
| | | playbackFailed.value = true |
| | | }) |
| | | } |
| | | return |
| | | } |
| | | if (!videoContext) { |
| | | return |
| | | } |
| | | try { |
| | |
| | | } |
| | | const openinstallOpened = await wakeupOrInstallByOpeninstall({ |
| | | page: 'courseDetail', |
| | | courseId: String(resolveCurrentCourseId() || '').trim() |
| | | courseId: String(resolveShareCourseId() || '').trim() |
| | | }) |
| | | if (openinstallOpened) { |
| | | return |