基于uniapp实现的交互视频demo

1,421 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

基于uniapp实现的交互视频demo

优先提示:由于Video组件跨端时层级原因,目前仅支持网页使用

原始需求

一个互动视频项目,播放段剧情后出现交互按钮,用户通过按钮交互,进入下一对应剧情,类似 示例

可以跳过剧情直接进入到达剧情交互节点,可以重新播放本段视频,交互需沉浸,不能出现视频播放按钮等

需求分析

  • 全屏播放器 不带有控制组件 不能出现黑边
  • 需要交互,按钮可控制下一步开始进入什么剧情,显示什么按钮
  • 需要有跳过剧情功能
  • 需要有重播功能

开发

实现全屏播放器

首先使用fixed方式定位,将元素充满全屏幕并设置纯黑背景background-color: #000;

<view class="bg">
</view>
.bg {
	background-color: #000;
	position: fixed;
	width: 100%;
	height: 100%;
}

然后将内部元素宽高充满并放入video组件

<video :src="videoList[step].url" id="myVideo" :controls='false' :show-fullscreen-btn='false' autoplay @ended="showbtn(step)" :show-play-btn='false' :show-center-play-btn='false' :show-loading='false' :enable-progress-gesture='false' object-fit='cover' ></video>

在video组件上设置以下熟悉用于隐藏各种自带控制组件及设置自动播放等功能

属性功能
controls='false'隐藏默认播放控件(播放/暂停按钮、播放进度、时间)
show-fullscreen-btn='false'隐藏全屏按钮
show-play-btn='false'隐藏视频底部控制栏的播放按钮
show-center-play-btn='false'隐藏视频中间的播放按钮
show-loading='false'隐藏视频loading控件
enable-progress-gesture='false'关闭滑动手势操作
autoplay自动播放
object-fit='cover'当视频大小与 video 容器大小不一致时cover 覆盖全屏

此时已经完成了第一个需求,视频全屏显示并且关闭全部控件

增加交互层

根据需求,需要在视频上方增加交互按钮,这里我们通过使用absolute定位的方式,在视频上方覆盖一层交互层,然后按钮放在交互层上,便于控制管理

<view class="infoBox">
	<view class="skip" @tap="skip">跳过</view>
	<view class="btns" v-if="isShowbtn">
		<view @tap="next(i.step)" :style="{animation:`show ${(index+2)*0.2}s`}"
			v-for="(i,index) in videoList[step].btnList">
			{{i.info}}
		</view>
	</view>
</view>
.infoBox {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	// background-color: rgba(0,0,0,0.5);
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	.skip {
		position: absolute;
		top: 80rpx;
		right: 80rpx;
		background-color: #007AFF;
		color: #FFFFFF;
		border-radius: 60rpx;
		padding: 15rpx 60rpx;
		transition: 0.2s;
		font-size: 22px;
		font-weight: bold;
		&:hover {
			transform: scale(1.1);
		}
		&:active {
			transform: scale(0.9);
		}
	}
	.btns {
		padding: 200rpx 30rpx;
		width: 600rpx;
		animation: opacityShow 0.2s;
		view {
			text-align: center;
			margin-bottom: 30rpx;
			padding: 30rpx;
			border-top: 5rpx solid #333;
			border-bottom: 5rpx solid #333;
			transition: 0.2s;
			&:hover {
				background-color: rgba(0, 0, 0, 0.1);
				transform: scale(0.95);
			}
			&:active {
				background-color: rgba(0, 0, 0, 0.1);
				transform: scale(0.9);
			}
		}
	}
}

到此 交互按钮的样式已经实现了

需要添加JS来实现功能

交互实现

其实整个项目抽象出来本质就是一个播放器,然后有一个播放列表,通不通的按钮跳转到不同的视频片段进行播放,而整个项目的交互只有3种功能

  • 跳转按钮 :传入一个播放列表索引,点击之后播放指定的片段
  • 重播按钮:点击中后,视频进度回到开始,并播放
  • 跳过按钮:视频进度跳转到结尾

有了这个思路 首先需要维护一个播放列表

这个对用户是不可见的,所以此处直接定义一个数组

videoList: [{
		url: '../../static/V10711-113550.mp4',
		btnList: [{
				step: 0,
				info: '重新播放'
			}, {
				step: 1,
				info: '吃饭'
			},
			{
				step: 2,
				info: '喝水'
			},
		]
	},
	{
		url: '../../static/鸡蛋.mp4',
		btnList: [{
				step: 1,
				info: '重新播放'
			},
			{
				step: 0,
				info: '工作'
			},
			{
				step: 2,
				info: '喝水'
			},
		]
	},
	{
		url: '../../static/抠像.mp4',
		btnList: [{
				step: 2,
				info: '重新播放'
			},
			{
				step: 0,
				info: '工作'
			},
			{
				step: 1,
				info: '吃饭'
			},
		]
	}
]

然后我没需要在生命周期中初始化一下视频组件,方便后面控制

onReady() {
          this.videoObj = uni.createVideoContext('myVideo')
},

然后变写跳转方法,接收到进度后,将进度值存入全局进度字段step中,然后因为VUE的双向绑定机制视频地址会变为当前步骤的视频地址,然后调用play()方法开始播放,视频播放结束时,通过@ended="showbtn"事件控制交互层出现

next(step) {
	this.isShowbtn = false 
        this.step = step	
        this.videoObj.seek(0)
	this.videoObj.play()
},

播放完成后通过控制isShowbtn字段显示当前进度结束后的按钮,至此,进度跳转已经实现

重新播放

重新播放功能只需要将播放进度设置为0,然后调用play()方法即可,同时重播时也要隐藏下交互层

replay() {
        this.isShowbtn = false
	this.videoObj.seek(0)
	this.videoObj.play()
},

跳过

跳过按钮一直显示在屏幕上,点击时需要使用seek()将进度设置到结尾即可,这个方法作用是跳转进度到指定位置,当位置超过视频长度时默认跳转到最后

skip() {
    this.videoObj.seek(999999999999)
},

至此整个项目功能基本已经可以实现

完整代码

<template>
	<view class="bg">
		<view class="openBox" v-if="!isOpen">
			<view @tap="next(0)">开始</view>
		</view>
		<view class="stepBox" >
			<video :src="videoList[step].url" id="myVideo" :controls='false' :show-fullscreen-btn='false' autoplay
				@ended="showbtn" :show-play-btn='false' :show-center-play-btn='false' :show-loading='false'
				:enable-progress-gesture='false' object-fit='cover' ></video>
			<view class="infoBox">
				<view class="skip" @tap="skip">跳过</view>
				<view class="btns" v-if="isShowbtn">
					<view @tap="next(i.step)" :style="{animation:`show ${(index+2)*0.2}s`}"
						v-for="(i,index) in videoList[step].btnList">
						{{i.info}}
					</view>
				</view>
			</view>
		</view>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				isOpen:false,
				videoObj: null,
				step: 0,
				isShowbtn: false,
				videoList: [{
						url: '../../static/V10711-113550.mp4',
						btnList: [{
								step: 0,
								info: '重新播放'
							}, {
								step: 1,
								info: '吃饭'
							},
							{
								step: 2,
								info: '喝水'
							},
						]
					},
					{
						url: '../../static/鸡蛋.mp4',
						btnList: [{
								step: 1,
								info: '重新播放'
							},
							{
								step: 0,
								info: '工作'
							},
							{
								step: 2,
								info: '喝水'
							},
						]
					},
					{
						url: '../../static/抠像.mp4',
						btnList: [{
								step: 2,
								info: '重新播放'
							},
							{
								step: 0,
								info: '工作'
							},
							{
								step: 1,
								info: '吃饭'
							},
						]
					}
				]
			}
		},
		onLoad() {

		},
		onReady() {
			this.videoObj = uni.createVideoContext('myVideo')
		},
		methods: {
			skip() {
				this.videoObj.seek(999999999999)
			},
			replay() {
				this.isShowbtn = false
				this.videoObj.seek(0)
				this.videoObj.play()
			},
			next(step) {
				this.isOpen = true
				this.isShowbtn = false
				this.step = step
				this.videoObj.seek(0)
				this.videoObj.play()
			},
			showbtn() {
				this.isShowbtn = true
			},

		}
	}
</script>

<style lang="scss" scoped>
	.bg {
		background-color: #000;
		position: fixed;
		width: 100%;
		height: 100%;
		.openBox{
			position: absolute;
			top: 0;
			left: 0;
			z-index: 99;
			width: 100%;
			height: 100%;
			background-color: rgba(0,0,0,0.8);
			display: flex;
			justify-content: center;
			align-items: center;
			view{
				background-color: #007AFF;
				color: #FFFFFF;
				border-radius: 100rpx;
				padding: 15rpx 300rpx;
				transition: 0.2s;
				font-size: 56px;
				font-weight: bold;
				transition: 0.2s;
				&:hover {
					transform: scale(1.1);
				}
				
				&:active {
					transform: scale(0.9);
				}
			}
		}

		.stepBox {
			width: 100%;
			height: 100%;
			position: relative;

			.infoBox {
				position: absolute;
				width: 100%;
				height: 100%;
				top: 0;
				left: 0;
				// background-color: rgba(0,0,0,0.5);
				display: flex;
				flex-direction: column;
				justify-content: flex-end;

				.skip {
					position: absolute;
					top: 80rpx;
					right: 80rpx;
					background-color: #007AFF;
					color: #FFFFFF;
					border-radius: 60rpx;
					padding: 15rpx 60rpx;
					transition: 0.2s;
					font-size: 22px;
					font-weight: bold;

					&:hover {
						transform: scale(1.1);
					}

					&:active {
						transform: scale(0.9);
					}
				}

				.btns {
					padding: 200rpx 30rpx;
					width: 600rpx;
					animation: opacityShow 0.2s;

					view {
						text-align: center;
						margin-bottom: 30rpx;
						padding: 30rpx;
						border-top: 5rpx solid #333;
						border-bottom: 5rpx solid #333;
						transition: 0.2s;

						&:hover {
							background-color: rgba(0, 0, 0, 0.1);
							transform: scale(0.95);
						}

						&:active {
							background-color: rgba(0, 0, 0, 0.1);
							transform: scale(0.9);
						}
					}
				}
			}

			video {
				width: 100%;
				height: 100%;
			}

		}
	}
</style>