文件
OmniCard/mpuniapp/pages/template/vertical-video-feed/vertical-video-feed.uvue

383 行
9.8 KiB
Plaintext

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="page">
<uni-back-arrow-circle class="back" arrow-class="arrow"></uni-back-arrow-circle>
<swiper class="swiper" :current="current" :circular="index != 0" :vertical="true" @change="onSwiperChange"
@transition="onTransition">
<swiper-item class="swiper-item" v-for="(item,i) in visibleList" :key="i">
<video @click="changeState(i)" ref="video" class="video-box" :id="'video-'+i"
@loadstart="onLoadstart(i)" :src="item.src" :poster="item.poster_src" :autoplay="false"
:show-progress="false" :show-fullscreen-btn="false" :show-play-btn="false"
:show-center-play-btn="false" :loop="true" @play="onPlay(i)" @pause="onPause(i)" :http-cache="true"></video>
<view class="video-cover" @click="changeState(i)">
<image v-if="stateList[i] === 'pause'" class="play-btn" src="/static/template/swiper-vertical-video/play.png"
mode="widthFix"></image>
</view>
<view class="video-info" v-if="showDebugInfo">
<text class="video-info-text">容器:第 {{i}} 个</text>
<text class="video-info-text">内容:{{item.content}}</text>
</view>
<!-- 评论按钮 -->
<view class="comment-btn" @click="openCommentPanel">
<view class="comment-icon">
<text class="comment-icon-text">💬</text>
</view>
<text class="comment-count">{{commentCount}}</text>
</view>
</swiper-item>
</swiper>
<view class="debug-info" v-if="showDebugInfo">
<text class="status-text">debug-info 播放状态:</text>
<text class="status-text" v-for="(value,index) in stateList">第{{index+1}}个:{{ value }}</text>
</view>
<!-- 评论面板 -->
<comment-panel :show="showCommentPanel" @close="closeCommentPanel" @countChange="onCommentCountChange"></comment-panel>
</view>
</template>
<script setup lang="uts">
/**
* @description: 支持上下滑动切换短视频内容,并在当前视频上叠加评论入口的竖滑视频流页面。
* @logic: 页面只维护当前可见的 3 个视频容器,用户上下滑动 swiper 时更新可见数据和播放状态,点击评论按钮打开评论面板。
* @optimization: 通过只保留 3 个可见视频容器并复用数据窗口,降低连续视频流场景下的组件创建和视频上下文数量。
*/
type ListItem = { _id : string, content : string, src : string, poster_src : string }
let page : number = 0,
currentPageIsShow : boolean = true;
import { state, setNetless } from '@/store/index.uts'
import CommentPanel from './comment-panel.uvue'
const $videoContextMap = new Map<string, VideoContext>()
const list = ref([] as ListItem[])
const visibleList = ref([] as ListItem[])// 提高性能 可见的只有3个
const current = ref(0 as number)
const index = ref(0)
const stateList = ref(["pause", "unPlay", "unPlay"] as string[])
const showDebugInfo = ref(false as boolean)
// 评论相关
const showCommentPanel = ref(false as boolean)
const commentCount = ref(0 as number)
function openCommentPanel() {
showCommentPanel.value = true
}
function closeCommentPanel() {
showCommentPanel.value = false
}
function onCommentCountChange(count : number) {
commentCount.value = count
}
function getVideoContext(idx : number) : VideoContext {
let videoContext : VideoContext | null = $videoContextMap.get('video-' + idx)
if (videoContext == null) {
videoContext = uni.createVideoContext('video-' + idx) as VideoContext
$videoContextMap.set('video-' + idx, videoContext)
}
return videoContext
}
function doPlay(idx : number) {
console.log("doPlay video" + idx);
getVideoContext(idx).play()
}
function doStop(idx : number) {
console.log("doStop video-" + idx);
getVideoContext(idx).stop();
// TODO 临时方案:解决.stop()时触发了doPause的问题
setTimeout(() => {
stateList.value[idx] = 'unPlay'
}, 1000)
}
function doPause(idx : number) {
getVideoContext(idx).pause()
console.log("doPause video-" + idx);
}
function changeState(idx : number) {
if (stateList.value[idx] === 'play') {
doPause(idx)
} else {
doPlay(current.value)
}
}
function onLoadstart(idx : number) {
console.error("onLoadstart video" + idx);
}
function onPause(idx : number) {
stateList.value[idx] = 'pause'
console.log('onPause', idx);
}
function onPlay(idx : number) {
if (current.value != idx || !currentPageIsShow) {
onPause(idx)
} else {
stateList.value[idx] = 'play'
console.log('onPlay', idx);
}
}
function getData() : ListItem[] {
let videoUrlList = state.netless
? [
'/static/test-video/10second-demo.mp4',
'/static/test-video/10second-demo.mp4',
'/static/test-video/10second-demo.mp4'
]
: [
'https://web-ext-storage.dcloud.net.cn/uni-app-x/video/uts-5-16.mp4',
'https://web-ext-storage.dcloud.net.cn/uni-app-x/video/uni-ai-5-16.mp4',
'https://web-ext-storage.dcloud.net.cn/uni-app-x/video/uni-verify-5-16.mp4'
] as string[]
let posterSrcList = state.netless
? [
'/static/shuijiao.jpg',
'/static/logo.jpg',
'/static/shuijiao.jpg'
]
: [
'https://web-ext-storage.dcloud.net.cn/uni-app-x/dark-uni-uts-01.png',
'https://web-ext-storage.dcloud.net.cn/uni-app-x/dark-uni-ai-01.png',
'https://web-ext-storage.dcloud.net.cn/uni-app-x/dark-uni-verify-01.jpg'
] as string[]
let listData = [] as ListItem[];
for (let i = 0; i < 6; i++) {
let idx = list.value.length + i;
let listItem : ListItem = {
"_id": "a00" + idx,
"content": "这是第" + idx + "条数据,url地址" + videoUrlList[i % 3],
"src": videoUrlList[i % 3],
"poster_src": posterSrcList[i % 3]
}
listData.push(listItem)
}
return listData
}
function onSwiperChange(e : SwiperChangeEvent) {
// console.error('SwiperChangeEvent',e.detail.current);
current.value = e.detail.current;
}
function onTransition(/*e : SwiperTransitionEvent*/) {
// console.log('onTransition e.detail.dx', e.detail.dx);
}
watch(current, (curr : number, oldCurrent : number) => {
let changeNumber = curr - oldCurrent
if (changeNumber == 1 || changeNumber == -2) {
// console.error('向右');
index.value++
} else {
// console.error('向左');
index.value--
}
// //翻页(3项为一页)
if (Math.abs(changeNumber) == 2) {
// console.log('翻页');
page = Math.floor(index.value / 3);
// console.log(index.value);
// console.log('page',page);
// console.log('slice',3*page,3*page+3);
if (list.value.length < 3 * page + 3) {
let listData : ListItem[] = getData()
list.value.push(...listData)
}
let visible = list.value.slice(3 * page, 3 * page + 3)
// 换数据
visibleList.value = visible
}
stateList.value.forEach((_ : string, idx : number) => {
if (idx === curr) {
doPlay(curr)
} else {
// 除了选中的其他已经播放的都需要停止
doStop(idx)
console.log('index:' + idx + '已被执行停止');
}
})
})
// #ifdef APP-HARMONY
uni.getNetworkType({
success: (res) => {
setNetless(res.networkType === 'none')
// #endif
list.value = getData()
visibleList.value = list.value.slice(0, 3)
// #ifdef APP || MP
// 非web端,一启动完成,就播放第一个。因为 web端浏览器限制不支持自动播放
// 鸿蒙 vapor 模式下,v-for 后 nextTick 后 nativeView init 事件还没有触发,因此延时 30ms
nextTick(() => {
setTimeout(() => doPlay(0), 30)
})
// #endif
// #ifdef APP-HARMONY
}
})
// #endif
onPageShow(() => {
currentPageIsShow = true
})
onHide(() => {
currentPageIsShow = false
console.log('pages-onHide');
doPause(current.value)
})
onBackPress((): boolean => {
if (showCommentPanel.value) {
closeCommentPanel()
return true
}
return false
})
onUnload(() => {
doPause(current.value)
})
function jest_getStateList() : string[] {
return stateList.value
}
function jest_getCurrent() : number {
return current.value
}
function jest_isCommentPanelShown() : boolean {
return showCommentPanel.value
}
function jest_switchTo(idx : number) {
current.value = idx
}
defineExpose({
jest_getStateList,
jest_getCurrent,
jest_isCommentPanelShown,
jest_switchTo
})
</script>
<style>
.page {
flex: 1;
}
.swiper,
.swiper-item,
.video-box,
.video-cover {
width: 100%;
height: 100%;
}
/* .swiper-item {
position: relative;
} */
.video-box {
width: 100%;
}
.video-cover {
position: absolute;
justify-content: center;
align-items: center;
align-content: center;
}
.play-btn {
width: 40px;
height: 40px;
}
.video-info {
position: absolute;
bottom: 0;
padding: 15px;
}
.video-info-text {
font-size: 14px;
color: red;
line-height: 20px;
}
.debug-info {
position: fixed;
top: 15px;
width: 100%;
background-color: rgba(255, 255, 255, 0.3);
}
.status-text {
color: red;
padding: 15px;
}
.back{
position: absolute;
/* #ifdef APP || MP */
top: var(--status-bar-height);
/* #endif */
/* #ifdef WEB */
top: 10px;
/* #endif */
z-index: 10;
}
/* uni-back-arrow-circle组件 自定义样式 */
.arrow {
border-left-color: #FFFFFF !important;
border-bottom-color: #FFFFFF !important;
}
/* 评论按钮 */
.comment-btn {
position: absolute;
right: 15px;
bottom: 80px;
flex-direction: column;
align-items: center;
z-index: 10;
}
.comment-icon {
width: 40px;
height: 40px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
}
.comment-icon-text {
font-size: 20px;
}
.comment-count {
font-size: 12px;
color: #ffffff;
text-align: center;
}
</style>