uniapp 上下切换视频,优化版
防抖音模块的写法,自己重构了下,更贴合自己项目,借鉴了uniapp插件市场(平台兼容性:app-nvue。
·
借鉴了uniapp插件市场(https://ext.dcloud.net.cn/plugin?id=5656)防抖音模块的写法,自己重构了下,更贴合自己项目,
平台兼容性:app-nvue
videoPlayer.nvue
<template>
<view class="videoPlayer">
<video v-if="showVideo" id="myVideo" class="video" :src="src" ref="myVideo" :muted="isMuted"
object-fit="contain" :loop="true" :autoplay="false" :show-play-btn="false" :enable-progress-gesture="false"
:page-gesture="false" :controls="false" :http-cache="true" :show-loading="false"
:show-fullscreen-btn="false" :show-center-play-btn="false" :style="boxStyle"
@timeupdate="timeupdate($event)"></video>
<image class="snapshot" :class="!playing?'':'snapshot_hide'" :src="src+snapshot" mode="aspectFit"
:style="boxStyle">
</image>
<view class="videoHover" :style="boxStyle" @click="click">
<image v-if="state=='pause'" class="playState" src="@/static/video/play.png"></image>
</view>
</view>
</template>
<script>
/*
* @property src 视频地址
* @property state 视频状态(play:播放,continue:暂停后继续播放,pause:暂停 ,stop:暂停并归零,preplay:预加载)
* @property index 列表下标(用于进度条判断是否为当前视频)
* @property boxStyle 视频宽高(取值与列表页面的宽高)
* @property showVideo 是否显示视频(列表Math.abs(currentIndex-index)<=1的boolen值,避免视频元素过多占内存)
* @property seektime 跳转进度
*/
var timer = null
export default {
props: ['src', 'state', 'index', 'boxStyle', 'showVideo', 'seektime'],
watch: {
state: {
immediate: true,
handler() {
this.stateChange();
}
},
seektime: {
immediate: true,
handler() {
let videos = this.getVideoContext();
if (!!videos) {
let videoContext = videos;
this.isMuted = false
this.playing = true
if (this.seektime > 0) {
videoContext.seek(this.seektime);
videoContext.play()
}
}
}
}
},
data() {
return {
snapshot: '?x-oss-process=video/snapshot,t_0000,f_jpg', //阿里云OSS媒体视频截帧 https://help.aliyun.com/document_detail/64555.html
//snapshot:'?ci-process=snapshot&time=1',//腾讯云COS媒体视频截帧 https://cloud.tencent.com/document/product/436/55671
playing: false, //是否播放中,控制图片(视频首帧)显隐
isMuted: false, //是否静音
dblClick: false,
initplay: true,
};
},
mounted() {
},
methods: {
getVideoContext(){
//也可以使用 uni.createVideoContext('myVideo',this)
return this.$refs['myVideo'];
},
timeupdate(e) {
if (this.initplay && e.detail.currentTime > 0) { //大于0说明视频有图像了,可以隐藏首帧图片了,并将进度重新归为0
this.initplay = false;
let videos = this.getVideoContext();
if (!!videos) {
let videoContext = videos;
videoContext.seek(0); //因为视频首帧用的1s,所以跳到这里页面可以无缝切换
videoContext.play()
this.playing = true
this.isMuted = false
}
} else {
//通知进度条进行更新
this.$emit('timeupdate', e, this.index);
}
},
async stateChange() {
let videos = this.getVideoContext();
if (!!videos) {
let videoContext = videos;
switch (this.state) {
case 'play': //视频播放
if (!this.playing) {
//timeupdate方法在uniapp这个版本的bug,只有暂停再播放,timeupdate的值才能刷新
videoContext.pause();
this.initplay = true
this.isMuted = true
this.playing = false
videoContext.play()
}
break;
case 'continue': //暂停后的播放,不需要用图片盖住
this.isMuted = false
this.playing = true
videoContext.play()
break;
case 'pause': //视频暂停
videoContext.pause()
this.playing = true //暂停后的播放,不需要用图片盖住
break;
case 'stop': //上下切换停止视频
videoContext.pause(); //视频停止,从0开始
videoContext.seek(0);
this.playing = false
break;
case 'preplay': //预加载视频,让视频静音播放1.5秒时间
this.isMuted = true
this.playing = false
videoContext.play();
await setTimeout(() => {
videoContext.seek(0);
videoContext.pause(); //视频停止,从0开始
this.playing = false
}, 1500)
break;
default:
break;
}
}
},
click() {
clearTimeout(timer)
this.dblClick = !this.dblClick
timer = setTimeout(() => {
if (this.dblClick) { //判断是单击 即为true
//单击
if (this.state != "continue" && this.state != "play") {
this.state = "continue"
} else {
this.state = "pause"
}
} else {
//双击
this.$emit('dblClick') //向父组件传递一个事件
}
this.dblClick = false //点击后重置状态 重置为false
}, 300)
},
}
}
</script>
<style>
.videoPlayer {
flex: 1;
background-color: #000000;
}
.video {
flex: 1;
}
.snapshot {
position: absolute;
}
.snapshot_hide {
opacity: 0;
}
.videoHover {
position: absolute;
top: 0;
left: 0;
flex: 1;
background-color: rgba(0, 0, 0, 0.1);
justify-content: center;
align-items: center;
}
.playState {
width: 160rpx;
height: 160rpx;
opacity: 0.2;
}
</style>
subVideoList.nvue
<template>
<view class="page" :style="boxStyle">
<!--先屏蔽,便于修改bug: 推荐 朋友 关注 搜索 发布小视频(+)-->
<!-- <video-tabBar ></video-tabBar> -->
<list :loadmoreoffset="loadmoreoffset" :show-scrollbar="false" :pagingEnabled="true" :scrollable="scrollable"
@scrollstart="onScrollStart" @scrollend="onScrollEnd" @scroll="onScroll" @loadmore="loadMore">
<cell class="itemBox" v-for="(item, index) in videos" :key="index" :style="boxStyle">
<!-- 动态渲染视频为3个-->
<video-player :state="item.state" :index="index" :seektime="item.seektime"
:showVideo="Math.abs(currentIndex-index)<=1" :src="item.src" :boxStyle="boxStyle"
@dblClick='dblClick' @timeupdate='timeupdate'> </video-player>
<!--先屏蔽,便于修改bug: 头像,关注,点赞,收藏,分享,商品链接-->
<!-- <video-info class="infoBox" :item="item"></video-info> -->
</cell>
</list>
<!-- 拖动进度时,显示拖动时间-->
<view v-if="videoProgressDarg" class="progressBar_drag_time" >
<text style="font-size: 22px; font-weight: bold; color: #F1F1F1;">{{dragTimeStr}} / {{durationStr}}</text>
</view>
<!-- 可拖动进度条,触摸高度80upx -->
<view v-if="videoProgressBar" class="progressBar" @touchstart="touchstart" @touchmove="touchmove"
@touchend="touchend">
<view class="progressBar_ground"></view>
<view v-if="!videoProgressDarg" class="progressBar_value" :style="{'width':playProgress+'px'}"></view>
<view v-if="!videoProgressDarg" class="progressBar_slider" :style="{'left':playProgress+'px','width':'10upx'}"></view>
<view v-if="videoProgressDarg" class="progressBar_drag_value" :style="{'width':dragProgress+'px'}"></view>
<view v-if="videoProgressDarg" class="progressBar_drag_slider" :style="{'left':dragProgress+'px','width':'10upx'}"></view>
</view>
</view>
</template>
<script>
var deviceInfo = uni.getSystemInfoSync();
let scrollTimer = null;
let preplayTimer = null;
import videoPlayer from './videoPlayer.nvue'
export default {
components: {
videoPlayer,
},
data() {
return {
loadmoreoffset: deviceInfo.windowHeight * 2, // 触发 loadmore 事件所需要的垂直偏移距离 加载到剩余2个的时候 继续加载
boxStyle: {
width: deviceInfo.screenWidth + 'px',
height: (deviceInfo.windowHeight - 32) + 'px'
},
screenWidth: deviceInfo.screenWidth,
wHeight: deviceInfo.windowHeight - 32, //32是指底部tabBar高度,根据自己情况修改
videos: [],
currentIndex: 0,
scrollable: true,
videoProgressBar: true, //滑动过程中隐藏,同时避免进度条多次触发
videoProgressDarg: false, //是否拖拽进度条
playProgress: 0, //播放进度
dragProgress: 0, //拖拽进度
duration: 0, //视频总时长
dragTime: 0,
dragTimeStr: '00:00', //拖拽时间点
durationStr: '00:00', //视频总时长
};
},
onLoad() {
},
onReady() {
this.init();
},
watch: {
async currentIndex(newIndex, oldIndex) {
await this.setState(oldIndex, 'stop');
await this.setState(newIndex, 'play');
if (preplayTimer) {
clearTimeout(preplayTimer);
preplayTimer = false;
}
preplayTimer = setTimeout(() => {
this.setState(newIndex + 1, 'preplay'); //预加载下一个视频
}, 500)
}
},
methods: {
//初始化
async init() {
await this.getList(); //初次加载需要等待数据请求完毕
this.currentIndex = 0;
this.$nextTick(() => {
this.setState(0, 'play');
})
},
getList() {
return new Promise((resolve, reject) => {
this.videos = [{ //1
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/710b0cf7-bed9-4805-a2fb-0b703483dbec.MOV", //10.视频链接
}, { //2
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/ec383f81-6896-4274-8861-e329ae1376b4.mp4",
}, { //3
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/53543262-55f5-4685-a5e3-b56ce75bcb88.mp4",
}, { //4
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/bfc86ab8-bb3b-4cef-a5d2-8c5edce4ef17.mp4",
}, { //5
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/5017a17a-389b-45e0-8d91-711c9dc76759.mp4",
}, { //6
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/209180d8-3dfd-42ea-9ef5-5f98ae0d95e1.mp4",
}, { //7
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/c8f7a17f-6eb8-453a-9f43-944ecc7a9f11.mp4",
}, { //8
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/f905c750-c70e-46b2-aaa6-37778d308f13.mp4",
}, { //9
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/9392e85c-36db-473f-8ec3-4f8ed83a382a.mp4",
}, { //10
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/e1cd785e-56ae-4c96-a713-126bf2950e19.mp4",
}, {
//11
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/97b50a6d-f77d-4418-b38d-844e0b9eec97.mp4",
}, {
//12
"state": "pause",
"src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/c061b07a-4b34-4d6d-aa1a-2cf41679f17c.mp4",
}]
resolve();
});
},
dblClick() {
},
loadMore(e) {
console.log("加载更多");
},
touchstart() {
this.scrollable = false; //禁止滑动
this.dragProgress = this.playProgress;
this.videoProgressDarg = true;
let dragTime = this.duration * (this.playProgress / this.screenWidth);
this.dragTime = dragTime;
this.dragTimeStr = this.getTime(dragTime); //视频当前播放时长
this.durationStr = this.getTime(this.duration);
},
touchmove(event) {
let left = Math.round(event.changedTouches[0].screenX);
let dragTime = this.duration * (left / this.screenWidth);
this.dragProgress = left;
this.dragTime = dragTime;
this.dragTimeStr = this.getTime(dragTime);
},
touchend() {
let index = this.currentIndex;
if (0 <= index && index < this.videos.length) {
this.$set(this.videos[index], 'seektime', this.dragTime);
}
this.scrollable = true;
this.videoProgressDarg = false;
},
timeupdate(event, index) {
if (!this.videoProgressDarg&&this.videoProgressBar && this.currentIndex == index) { //不加判断会导致预播放视频也触发,导致进度条跳动;同时滑动的时候也不处理
let duration = event.detail.duration;
let currentTime = event.detail.currentTime;
if (this.playProgress <1) { //总时间赋值一次即可
this.duration = duration;
}
this.playProgress = this.screenWidth * (currentTime / duration);
}
},
getTime(time) {
/* let h = parseInt(time / 60 / 60 % 24)
h = h < 10 ? '0' + h : h */
let m = parseInt(time / 60 % 60)
m = m < 10 ? '0' + m : m
let s = parseInt(time % 60)
s = s < 10 ? '0' + s : s
return m + ":" + s;
},
onScrollStart() {
this.videoProgressBar = false;
},
onScrollEnd() {
this.playProgress = 0; //播放进度归零
this.duration = 0; //视频总时长
this.videoProgressBar = true;
},
onScroll(event) {
if (!event.isDragging) { //是否拖拽滑动
var index = Math.round(Math.abs(event.contentOffset.y) / this.wHeight) //获取视频下标
if (index !== this.currentIndex) {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
this.currentIndex = index //当前下标
}, 100)
}
}
},
setState(index, state) {
if (0 <= index && index < this.videos.length) {
this.$set(this.videos[index], 'state', state)
}
},
}
}
</script>
<style>
.page {
flex: 1;
background-color: #000000;
}
.itemBox {
position: relative;
}
.progressBar {
position: absolute;
right: 0;
left: 0;
bottom: 0;
height: 80upx;
}
.progressBar_ground {
position: absolute;
right: 0;
left: 0;
bottom: 20upx;
height: 4upx;
background-color: #b2b1b4;
}
.progressBar_value {
position: absolute;
left: 0;
bottom: 20upx;
height: 4upx;
background-color: #ffffff;
}
.progressBar_slider {
width: 10upx;
height: 10upx;
background-color: #ffffff;
border-radius: 2px;
position: absolute;
bottom: 17upx;
}
.progressBar_drag_value {
position: absolute;
left: 0;
bottom: 20upx;
height: 10upx;
background-color: #ffffff;
}
.progressBar_drag_slider {
width: 10upx;
height: 20upx;
background-color: #ffffff;
border-radius: 2px;
position: absolute;
bottom: 14upx;
}
.progressBar_drag_time {
position: absolute;
left: 0;
right: 0;
bottom: 60upx;
align-items: center;
}
.infoBox {
position: fixed;
right: 0;
bottom: 0;
}
</style>
videoPlayer.nvue管理视频,使用页面如 短视频列表,视频+图片混合预览,论坛视频,商品视频,轮播图视频,拍摄视频预览等,如果是vue页面使用,请使用subNVues
更多推荐
已为社区贡献3条内容
所有评论(0)