313 lines
8.5 KiB
Vue
313 lines
8.5 KiB
Vue
<template>
|
||
<swiper
|
||
class="m-tiktok-video-swiper"
|
||
circular
|
||
@change="swiperChange"
|
||
:current="state.current"
|
||
:vertical="true"
|
||
duration="300"
|
||
>
|
||
<swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
|
||
<view class="swiper-item" @click="(e) => handleClick(index, e)">
|
||
<video
|
||
:id="`video__${index}`"
|
||
:controls="controls"
|
||
:autoplay="false"
|
||
:loop="loop"
|
||
@ended="ended"
|
||
@controlstoggle="controlstoggle"
|
||
@play="onPlay"
|
||
@error="emits('error')"
|
||
class="m-tiktok-video-player"
|
||
:src="item.src || item.explainUrl"
|
||
v-if="index === 0 || !state.isFirstLoad"
|
||
></video>
|
||
<view class="cover-triangle" v-if="pause"></view>
|
||
<image
|
||
v-if="item.poster && state.displayIndex != index"
|
||
:src="item.poster"
|
||
class="m-tiktok-video-poster"
|
||
mode="aspectFit"
|
||
></image>
|
||
<slot :item="item"></slot>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, getCurrentInstance, watch, nextTick } from 'vue';
|
||
import { onLoad, onUnload } from '@dcloudio/uni-app';
|
||
const _this = getCurrentInstance();
|
||
const emits = defineEmits(['play', 'error', 'loadMore', 'change', 'controlstoggle', 'click', 'ended']);
|
||
|
||
const lastTapTime = ref(0);
|
||
const pause = ref(false);
|
||
const props = defineProps({
|
||
/**
|
||
* 视频列表
|
||
*/
|
||
videoList: {
|
||
type: Array,
|
||
default: () => [],
|
||
},
|
||
/**
|
||
* 是否循环播放一个视频
|
||
*/
|
||
loop: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
/**
|
||
* 显示原生控制栏
|
||
*/
|
||
controls: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
/**
|
||
* 是否自动播放
|
||
*/
|
||
autoplay: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
/**
|
||
* 是否自动滚动播放
|
||
*/
|
||
autoChange: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
/**
|
||
* 滚动加载阈值(即播放到剩余多少个之后触发加载更多
|
||
*/
|
||
loadMoreOffsetCount: {
|
||
type: Number,
|
||
default: 2,
|
||
},
|
||
/**
|
||
* 暂停 1 单机暂停; 2 双击暂停; 0关闭
|
||
*/
|
||
pauseType: {
|
||
type: Number,
|
||
default: 2,
|
||
},
|
||
});
|
||
|
||
const state = reactive({
|
||
originList: [], // 源数据
|
||
displaySwiperList: [], // swiper需要的数据
|
||
displayIndex: 0, // 用于显示swiper的真正的下标数值只有:0,1,2。
|
||
originIndex: 0, // 记录源数据的下标
|
||
current: 0,
|
||
oid: 0,
|
||
showControls: '',
|
||
toggleShow: true, // 显示面板
|
||
videoContexts: [],
|
||
isFirstLoad: true,
|
||
});
|
||
|
||
const initVideoContexts = () => {
|
||
state.videoContexts = [
|
||
uni.createVideoContext('video__0', _this),
|
||
uni.createVideoContext('video__1', _this),
|
||
uni.createVideoContext('video__2', _this),
|
||
];
|
||
};
|
||
|
||
const onPlay = (e) => {
|
||
emits('play', e);
|
||
};
|
||
|
||
const setVideoRef = (el, index) => {
|
||
if (el) {
|
||
videoRefs.value[index] = el;
|
||
}
|
||
};
|
||
|
||
function handleClick(index, e) {
|
||
const now = Date.now();
|
||
switch (props.pauseType) {
|
||
case 1:
|
||
if (pause.value) {
|
||
state.videoContexts[index].play();
|
||
pause.value = false;
|
||
} else {
|
||
state.videoContexts[index].pause();
|
||
pause.value = true;
|
||
}
|
||
break;
|
||
case 2:
|
||
if (now - lastTapTime.value < 300) {
|
||
if (pause.value) {
|
||
state.videoContexts[index].play();
|
||
pause.value = false;
|
||
} else {
|
||
state.videoContexts[index].pause();
|
||
pause.value = true;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
lastTapTime.value = now;
|
||
state.toggleShow = !state.toggleShow;
|
||
emits('click', e);
|
||
}
|
||
function ended() {
|
||
// 自动切换下一个视频
|
||
if (props.autoChange) {
|
||
if (state.displayIndex < 2) {
|
||
state.current = state.displayIndex + 1;
|
||
} else {
|
||
state.current = 0;
|
||
}
|
||
}
|
||
emits('ended');
|
||
}
|
||
/**
|
||
* 初始一个显示的swiper数据
|
||
* @originIndex 从源数据的哪个开始显示默认0,如从其他页面跳转进来,要显示第n个,这个参数就是他的下标
|
||
*/
|
||
function initSwiperData(originIndex = state.originIndex) {
|
||
const originListLength = state.originList.length; // 源数据长度
|
||
const displayList = [];
|
||
displayList[state.displayIndex] = state.originList[originIndex];
|
||
displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
|
||
state.originList[originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1];
|
||
displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
|
||
state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
|
||
state.displaySwiperList = displayList;
|
||
|
||
if (state.oid >= state.originList.length) {
|
||
state.oid = 0;
|
||
}
|
||
if (state.oid < 0) {
|
||
state.oid = state.originList.length - 1;
|
||
}
|
||
// 暂停所有视频
|
||
state.videoContexts.map((item) => item?.stop());
|
||
setTimeout(() => {
|
||
// 当前视频
|
||
if (props.autoplay) {
|
||
uni.createVideoContext(`video__${state.displayIndex}`, _this).play();
|
||
}
|
||
}, 500);
|
||
// 数据改变
|
||
emits('change', {
|
||
index: originIndex,
|
||
detail: state.originList[originIndex],
|
||
});
|
||
// 加载更多
|
||
var pCount = state.originList.length - props.loadMoreOffsetCount;
|
||
if (originIndex == pCount) {
|
||
emits('loadMore');
|
||
}
|
||
}
|
||
/**
|
||
* swiper滑动时候
|
||
*/
|
||
function swiperChange(event) {
|
||
const { current } = event.detail;
|
||
state.isFirstLoad = false;
|
||
const originListLength = state.originList.length; // 源数据长度
|
||
// 向后滚动
|
||
if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
|
||
state.originIndex = state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
|
||
state.displayIndex = state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
|
||
state.oid = state.originIndex - 1;
|
||
initSwiperData(state.originIndex);
|
||
}
|
||
// 如果两者的差为-2或者1则是向前滑动
|
||
else if (state.displayIndex - current == -2 || state.displayIndex - current == 1) {
|
||
state.originIndex = state.originIndex - 1 == -1 ? originListLength - 1 : state.originIndex - 1;
|
||
state.displayIndex = state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
|
||
state.oid = state.originIndex + 1;
|
||
initSwiperData(state.originIndex);
|
||
}
|
||
state.toggleShow = true;
|
||
}
|
||
|
||
function controlstoggle(e) {
|
||
state.showControls = e.detail.show;
|
||
emits('controlstoggle', e);
|
||
}
|
||
|
||
watch(
|
||
() => props.videoList,
|
||
() => {
|
||
if (props.videoList?.length) {
|
||
state.originList = props.videoList;
|
||
if (state.isFirstLoad || !state.videoContexts?.length) {
|
||
initSwiperData();
|
||
initVideoContexts();
|
||
}
|
||
}
|
||
},
|
||
{
|
||
immediate: true,
|
||
}
|
||
);
|
||
|
||
let loadTimer = null;
|
||
onLoad(() => {
|
||
// 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
|
||
loadTimer = setTimeout(() => {
|
||
state.isFirstLoad = false;
|
||
clearTimeout(loadTimer);
|
||
}, 5000);
|
||
});
|
||
|
||
onUnload(() => {
|
||
clearTimeout(loadTimer);
|
||
});
|
||
|
||
defineExpose({
|
||
initSwiperData,
|
||
});
|
||
</script>
|
||
|
||
<style lang="stylus" scoped>
|
||
.m-tiktok-video-swiper,
|
||
.m-tiktok-video-player {
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: #000;
|
||
}
|
||
.m-tiktok-video-swiper {
|
||
.swiper-item {
|
||
position: relative;
|
||
height: 100%;
|
||
}
|
||
.m-tiktok-video-poster {
|
||
background-color: #000;
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.cover-triangle{
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%)
|
||
width: 132rpx
|
||
height: 132rpx
|
||
border-radius: 50%;
|
||
background: rgba(0,0,0,0.3)
|
||
}
|
||
.cover-triangle::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-40%, -50%) rotate(90deg);
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 40rpx solid transparent;
|
||
border-right: 40rpx solid transparent;
|
||
border-bottom: 60rpx solid #fff;
|
||
}
|
||
</style>
|