一、项目背景:实现仿抖音短视频全屏视频播放、点赞、评论、上下切换视频、视频播放暂停、分页加载、上拉加载下一页、下拉加载上一页等功能。。。
二、前言:博主一开始一直想实现类似抖音进入页面自动播放当前视频,上下滑动切换之后播放当前视频,但最后在ios上出现声音播放,但画面卡顿的问题,估计是ios的浏览器对自动播放做了安全限制,导致自动播放失效,为了功能的可用性,最终放弃自动播放,实现手动点击视频正中心的播放按钮进行播放,再点击视频暂停,这个bug在安卓端暂时没出现,大概率是ios的安全性更高导致的浏览器策略拦截了,需要用户手动交互。
三、项目框架组件:uniapp h5项目、vue2、 swiper组件、video组件
四、效果:
仿抖音全屏视频切换播放暂停
五、布局:可根据自已的项目需求进行修改,博主这里的逻辑是数据由接口返回,如果没有视频,就展示图片,只有视频才进行播放,标题最多展示三行,超过三行显示‘展开’,点击展开谈起标题的底部弹窗,这里弹窗的代码就不展示了,有需要可私信
<view class="widget-video pos-r" :style="{height:`${videoHeight}px`}">
<swiper class="video-list" :current="current" :style="{height:`${videoHeight}px`}" :vertical="true"
@change="changeHandler" @transition="transitionHandler" @touchstart="touchStart" @touchend="touchEnd">
<swiper-item class="video-item" :style="{height:`${videoHeight}px`}" v-for="(item, index) in datas"
:key="index">
<video
v-if="!$util.validatenull(item.videourl) || !$util.validatenull(item.videourl_low) || !$util.validatenull(item.videourl_fhd) || !$util.validatenull(item.videourl_hd)"
class="thumb-img" :id="`video_${item.id}`" :src="item.videourl" :show-progress="false"
:show-fullscreen-btn="false" :show-play-btn="false" :loop="true" :show-center-play-btn="false"
enable-play-gesture :poster="item.thumb" preload="auto" x5-playsinline="" playsinline="true"
webkit-playsinline="true" x-webkit-airplay="allow" x5-video-player-type="h5"
x5-video-player-fullscreen="" x5-video-orientation="portraint" @click="playOrpauseFn">
</video>
<image v-else class="thumb-img" :src="item.thumb" mode="aspectFit"></image>
<template v-if="item.videourl || item.videourl_fhd || item.videourl_hd || item.videourl_low">
<image v-if="showPlayIcon" class="play-icon pos-a pos-tl-c"
:src="$util.isCandu()?'/static/h5AndWeixin/home/cd_video_play.png':'/static/h5AndWeixin/home/common_icon_item_video_play.png'"
mode="aspectFill" @tap="playOrpauseFn">
</image>
</template>
<view class="calcwidth pos-a pos-bottom padding-l padding-b">
<view class="wrapper" @click="openIntroducePop(item.title,item.description)">
<view :id="'video-title'+item.id" class="c-f video-title"
:style="{fontSize:$util.isElder()?'39rpx':'30rpx',maxHeight: titleMaxHeight}">
<text v-if="showExpand" class="expand">展开</text>
{{item.title}}
</view>
</view>
<from-time-view :item="item" :hideViews="true" :textColor="'#fff'"></from-time-view>
</view>
<view class="right-icon-wrap pos-a dflex col-s flex-d pos-right">
<view v-if="item.allow_comment === 1" class="pos-r tac mt30">
<image
:style="{width:$util.isElder()?'104rpx':'80rpx',height:$util.isElder()?'104rpx':'80rpx'}"
src="/sub-live/static/comment.png"
@click="openCommentPop(item.catid,item.contentid,item.id)" mode="scaleToFill">
</image>
<view v-if="commentCount> 0" class="zan-num tac pos-a pos-b-full"
:style="{fontSize:$util.isElder()?'32rpx':'20rpx',backgroundColor:$config.INFO.SUB_THEME_COLOR}">
{{$util.filterViews(commentCount)}}
</view>
</view>
<view v-if="item.islike === 1" class="pos-r tac mt30">
<image
:style="{width:$util.isElder()?'104rpx':'80rpx',height:$util.isElder()?'104rpx':'80rpx'}"
:src="likeObj.liked ? '/sub-live/static/zan-active.png' : '/sub-live/static/zan-inactive.png'"
mode="scaleToFill" @click="goZanFn(item.catid,item.id)">
</image>
<view v-if="likeObj.like_count > 0" class="zan-num tac pos-a pos-b-full"
:style="{fontSize:$util.isElder()?'32rpx':'20rpx',backgroundColor:$config.INFO.SUB_THEME_COLOR}">
{{$util.filterViews(likeObj.like_count)}}
</view>
</view>
</view>
</swiper-item>
</swiper>
<view class="nav-bar dflex padding-left-and-right pos-a pos-top">
<image :style="{width:$util.isElder()?'39rpx':'30rpx',height:$util.isElder()?'39rpx':'30rpx'}"
src="/static/h5AndWeixin/public/white-back.png" @click="goBack"></image>
</view>
</view>
六、js:主要展示视频代码
data() {
return {
videoHeight: uni.getWindowInfo().windowHeight,
current: 0,
datas: [],
page: 0, // 当前页0,上一页-1,下一页1
showPlayIcon: false,
pageStartY: 0,
pageEndY: 0,
titleMaxHeight: '',
showExpand: false,
videoCtx: null
};
},
onLoad() {
// 获取当前页数据
this.getvideolists();
},
methods: {
getvideolists() {
const _this = this;
// 请求数据,改成自已接口的路径和参数
_this.$api.getVerticalVideoList({
catid: _this.catid,
id: _this.id, // 请求上一页传第一条数据的id,请求下一页传最后一条数据的id
page: _this.page
}).then(res => {
if (res.data) {
// 判断是否有数据,有数据才进行操作
if (!_this.$util.validatenull(res.data.lists)) {
// 下拉加载上一页,将数据插入当前数据的头部,并且播放数据的最后一条
if (_this.current === 0 && _this.page === -1) {
_this.datas.unshift(...res.data.lists);
_this.current = res.data.lists.length - 1;
} else {
// 上拉加载下一页,将数据添加到当前数据的尾部
_this.datas.push(...res.data.lists);
}
const firstItem = _this.datas[0];
// 只创建当前视频的播放器,以免卡顿
_this.playOrpauseFn();
}
}
}).catch((err) => {
console.error(err);
})
},
// 上下切换视频
changeHandler(e) {
const _this = this;
if (e.detail.source == 'touch') {
const {
current
} = e.detail;
// 将播放按钮隐藏
_this.showPlayIcon = false;
// 设置当前视频
_this.current = current;
// 只创建当前视频播放器,播放当前视频,暂停其他视频
_this.playOrpauseFn();
}
},
transitionHandler(e) {
if (e.detail.dy === 0) {
// 最后一条数据上拉加载下一页
if (this.current === this.datas.length - 1) {
if (this.pageStartY > this.pageEndY) {
this.page = 1;
this.id = this.datas.at(-1).id;
this.getvideolists();
}
}
// 第一条数据下拉加载上一页
if (this.current === 0) {
if (this.pageStartY < this.pageEndY) {
this.page = -1;
this.id = this.datas.at(0).id;
this.getvideolists();
}
}
}
},
// 获取当前触发的纵坐标以此来判断是上拉还是下拉
// 记录开始滑动的手指的纵坐标
touchStart(res) {
if (this.current === this.datas.length - 1 || this.current === 0) {
this.pageStartY = res.changedTouches[0].pageY;
}
},
// 记录滑动结束的手指的纵坐标
touchEnd(res) {
if (this.current === this.datas.length - 1 || this.current === 0) {
this.pageEndY = res.changedTouches[0].pageY;
}
},
// 根据视频id创建播放器
playOrpauseFn() {
let video_id = this.datas[this.current].id;
this.videoCtx = uni.createVideoContext(`video_${video_id}`, this);
// 点击播放按钮视频播放,按钮隐藏,再点击视频暂停,按钮显示
if (this.showPlayIcon) {
this.videoCtx.seek(0);
this.videoCtx.play();
this.showPlayIcon = false;
} else {
this.videoCtx.pause();
this.showPlayIcon = true;
}
}
}
七、sass:
.widget-video {
width: 100%;
background-color: #000;
overflow: hidden;
.nav-bar {
width: 100%;
height: 88rpx;
}
}
.video-list {
width: 100%;
height: 100%;
.video-item {
width: 100%;
position: relative;
.play-icon {
width: 64rpx;
height: 64rpx;
}
.right-icon-wrap {
// width: 112rpx;
bottom: 208rpx;
right: 18rpx;
.mt30 {
margin-top: 60rpx;
}
.zan-num {
// width: 68rpx;
margin: auto;
border-radius: 4rpx;
font-weight: 600;
color: #FFFFFF;
transform: scale(0.8);
}
}
.calcwidth {
width: calc(100% - 130rpx);
}
.wrapper {
display: flex;
width: 100%;
overflow: hidden;
.video-title {
overflow: hidden;
text-overflow: ellipsis;
text-align: justify;
position: relative;
}
.video-title::before {
content: '';
height: calc(100% - 42rpx);
float: right;
}
.expand {
position: relative;
float: right;
clear: both;
margin-left: 40rpx;
color: #A9A9B8;
cursor: pointer;
border: 0;
}
.expand::before {
content: '...';
position: absolute;
left: -10rpx;
color: #fff;
transform: translateX(-100%);
}
}
}
}
**end:**如果出现画面卡顿,声音播放等问题,请一定要关闭视频自动播放功能。