自定义video控件,实现拖动进度条,自定义暂停、播放、和加载中的图标
由于小程序原生的video在加载中会与暂停按钮重叠,所以根据需求自定义播放加载中暂停按钮、进度条和全屏按钮,小程序项目中使用到微信的video组件和第三方vant的进度条组件,需自己引入第三方的库
子组件index.js
Component({
/**
* 组件的属性列表
*/
properties: {
//视频链接
videoUrl: {
type: String,
value: ''
},
//是否是首次播放,用于处理首次增加播放量
firstPlay: {
type: Boolean,
value: true
},
//全屏的标题
title: {
type: String,
value: ''
},
//是否自动播放
autoplay: {
type: Boolean,
value: false
},
},
/**
* 组件的初始数据
*/
data: {
controlsShow: false, //控件显示
slidering: false,
isPlaying: true, //初始化显示播放按钮
isWaiting: false, //加载按钮
isPauseing: false, //暂停按钮
// isStoping: false, //为了停止视频不调用暂停视频事件
full_screen: true, //进入全屏
currentTime: 0, //进度时间
duration: 100, //视频总时间
timer: null, //点击视频显示控件
waitNumber: 0, // 主用于视频加载缓存时没播放就调用了播放进度变化事件导致加载按钮隐藏
},
attached() {
if (this.data.autoplay) {
this.setData({
isPlaying: false,
isPauseing: false,
isWaiting: true,
});
}
},
detached() {
if (this.data.timer) {
clearTimeout(this.data.timer);
}
},
/**
* 组件的方法列表
*/
methods: {
//视频元数据加载完成时触发
loadedmetadata: function (e) {
console.log('视频元数据加载完成时触发');
this.setData({
duration: Math.floor(e.detail.duration)
})
},
// 成功播放的监听事件,处理成功播放的增加浏览量的
onPlay() {
console.log('成功播放的监听事件', this.data);
if (this.data.firstPlay) {
this.triggerEvent('play')
}
this.triggerEvent('firstPlayEvent')
this.setData({
isPlaying: false,
isPauseing: true,
isWaiting: false,
});
},
// 暂停播放的监听事件
onPause(e) {
console.log('暂停播放的监听事件');
// if (!this.data.isStoping) {
// this.setData({
// isPlaying: true,
// });
// }
this.setData({
isPlaying: true,
isPauseing: false,
isWaiting: false
})
},
// 视频加载的监听事件
onWaiting(e) {
//解决在安卓真机上首次进去会自动触发这个事件
if (!this.data.firstPlay) {
this.setData({
isWaiting: true,
isPauseing: false,
waitNumber: 0,
});
}
},
//当播放到末尾时触发
onEnded() {
this.setData({
isPlaying: true,
isPauseing: false,
currentTime: 0
});
},
// 切换全屏模式
toggleFullScreen() {
console.log(this.data.isWaiting);
const videoContext = wx.createVideoContext('myVideo', this);
if (this.data.full_screen) {
videoContext.requestFullScreen(); // 进入全屏
this.setData({
full_screen: false
})
} else {
videoContext.exitFullScreen() // 退出全屏
this.setData({
full_screen: true
})
}
},
// 播放进度变化时触发
onTimeUpdate(e) {
// console.log(this.data.currentTime);
//waitNumber 主用于视频加载缓存时没播放就调用了播放进度变化事件导致加载按钮隐藏
this.setData({
waitNumber: this.data.waitNumber + 1,
})
if (!this.data.slidering && this.data.waitNumber > 2) {
this.setData({
isWaiting: false,
isPauseing: true,
currentTime: Math.floor(e.detail.currentTime),
duration: Math.floor(e.detail.duration)
});
}
},
// 松开进度条触发
onSliderChange(e) {
const videoContext = wx.createVideoContext('myVideo', this);
console.log('松开进度条触发', e.detail);
videoContext.seek(e.detail);
this.setData({
slidering: false
})
this.videoClick()
},
// 拖动进度条时触发
dragSlider(e) {
console.log('拖动进度条时触发', e);
this.setData({
slidering: true,
currentTime: Math.floor(e.detail.value)
});
this.videoClick()
},
//点击播放视频按钮
playVideo: function () {
console.log('点击播放视频按钮', this.data);
this.setData({
isPlaying: false,
isWaiting: true,
isPauseing: false,
});
// 获取video上下文
const videoContext = wx.createVideoContext('myVideo', this);
// 调用播放方法
videoContext.play();
},
// 停止播放
stopVideo: function () {
this.setData({
// isStoping: true,
currentTime: 0,
})
console.log('停止播放', this.data.duration);
// 获取video上下文
const videoContext = wx.createVideoContext('myVideo', this);
// 调用停止播放
videoContext.stop();
},
// 暂停播放
pauseVideo: function () {
console.log('暂停播放');
// 获取video上下文
const videoContext = wx.createVideoContext('myVideo', this);
// 调用停止播放
videoContext.pause();
},
// 视频进入和退出全屏时触发
onFullscreenchange(e) {
console.log('视频进入和退出全屏时触发');
if (!e.detail.fullScreen) {
console.log('退出全屏');
this.setData({
full_screen: true
})
} else {
console.log('进入全屏');
}
},
// video点击事件,让控件显示3秒
videoClick() {
// 清除之前的定时器
if (this.data.timer) {
clearTimeout(this.data.timer);
}
// 显示自定义组件
this.setData({
controlsShow: true
});
//拖动进度条是不隐藏控件
if (this.data.slidering) return
// 设置新的定时器,3秒后隐藏组件
this.data.timer = setTimeout(() => {
this.setData({
controlsShow: false
});
}, 3000);
}
}
})
子组件index.wxml
<view class="phoneVideo">
<video bind:tap="videoClick" id="myVideo" class="video" title="{{title}}" show-fullscreen-btn="{{true}}" controls="{{false}}" bind:firstPlay="firstPlayEvent" autoplay="{{autoplay}}" show-center-play-btn="{{false}}" enable-play-gesture="{{true}}" play-btn-position="center" enable-progress-gesture="{{false}}" style="width: 100%;height: 100%;" bindloadedmetadata="loadedmetadata" src="{{videoUrl}}" bindtimeupdate="onTimeUpdate" bindplay="onPlay" bindpause="onPause" bindended="onEnded" bindwaiting="onWaiting" bindfullscreenchange="onFullscreenchange">
<!-- 退出全屏按钮 -->
<image class="backVideo xiaoshou" bind:tap="toggleFullScreen" wx:if="{{!full_screen}}" src="/images/phone/backVideo.png" mode="" />
<!-- 播放按钮 -->
<image wx:if="{{isPlaying}}" class="audioPlay xiaoshou" catch:tap="playVideo" src="/images/phone/audioPlay.png" mode="" />
<!-- 暂停按钮 -->
<image wx:if="{{isPauseing && controlsShow}}" class="audioPause xiaoshou" catch:tap="pauseVideo" src="/images/phone/pause.png" mode="" />
<!-- 加载中图标 -->
<view wx:if="{{isWaiting}}" class="loading">
<image class="rotate" src="/images/phone/loading.png" />
</view>
<!-- 播放控件 -->
<view class="controls" style="visibility: {{controlsShow ? 'visible' : 'hidden'}};">
<text>{{computed.formatTime(currentTime)}}</text>
<view class="slider" wx:if="{{controlsShow}}">
<van-slider bind:change="onSliderChange" bind:drag="dragSlider" min="{{0}}" max="{{duration}}" value="{{currentTime}}" use-button-slot bar-height="calc(100vmin / 750 * 6)" active-color="#ffffff" inactive-color="rgba(255,255,255,0.47)" class="slider-item">
<view class="slider-button xiaoshou" slot="button">
<view class="slider-button-item"></view>
</view>
</van-slider>
</view>
<text>{{computed.formatTime(duration)}}</text>
<!-- 全屏按钮 -->
<image class="xiaoshou {{full_screen?'full_screen':'cancel_full_screen'}}" catch:tap="toggleFullScreen" src="{{full_screen?'/images/phone/full_screen.png':'/images/phone/cancel_full_screen.png'}}" mode="" />
</view>
</video>
</view>
<wxs module="computed">
module.exports = {
formatTime: function (totalSeconds) {
var minutes = Math.floor((totalSeconds % 3600) / 60);
var seconds = Math.floor(totalSeconds % 60);
// 添加前导零
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return minutes + ':' + seconds;
}
}
</wxs>
子组件index.wxss
.phoneVideo {
width: 100%;
height: 100%;
/* 定义一个名为 rotate 的旋转动画 */
/* 将旋转动画应用到图片上 */
}
.phoneVideo .xiaoshou {
cursor: pointer;
}
.phoneVideo .video {
position: relative;
}
.phoneVideo .backVideo {
position: absolute;
top: 15px;
left: 15px;
width: 30px;
height: 30px;
}
.phoneVideo .audioPlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
}
.phoneVideo .audioPause {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 30px;
}
.phoneVideo .loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(50deg);
transform-origin: center center;
/* 设置旋转中心为元素的中心 */
}
.phoneVideo .full_screen {
margin-left: 10px;
width: 20px;
height: 16px;
}
.phoneVideo .cancel_full_screen {
margin-left: 10px;
width: 30px;
height: 23px;
}
@keyframes rotate {
from {
transform: rotate(0deg);
/* 从0度开始旋转 */
}
to {
transform: rotate(360deg);
/* 旋转到360度,一圈 */
}
}
.phoneVideo .rotate {
width: 30px;
height: 30px;
animation: rotate 2s linear infinite;
/* 应用名为 rotate 的动画,持续时间2秒,线性变化,无限循环 */
}
.phoneVideo .controls {
position: absolute;
bottom: calc(100vmin / 750 * 20);
left: calc(100vmin / 750 * 20);
right: calc(100vmin / 750 * 20);
display: flex;
color: #ffffff;
align-items: center;
font-size: 12px;
}
.phoneVideo .controls .slider {
flex: 1;
margin: 0 9px;
}
.phoneVideo .controls .slider .slider-button {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.phoneVideo .controls .slider .slider-button .slider-button-item {
background: #FFFFFF;
width: 12px;
height: 12px;
border-radius: calc(100vmin / 750 * 50);
}
index.json
{
"component": true,
"usingComponents": {
"van-slider": "@vant/weapp/slider/index"
}
}
父组件
<phoneVideo autoplay="{{autoplay}}" bind:firstPlayEvent="firstPlayEvent" firstPlay="{{firstPlay}}" id="phoneVideo" videoUrl="{{videoUrl}}" bind:play="playVideo" />
父组件js
Page({
/**
* 页面的初始数据
*/
data: {
videoUrl: '', //选中播放的视频地址
autoplay: false, //视频是否自动播放
firstPlay: true, //是否是首次播放
},
//用于第一次播放的逻辑处理
firstPlayEvent() {
this.setData({
firstPlay: false
})
},
//播放视频事件
playVideo: function () {
//成功播放的处理业务逻辑
})
},
})
由于csdn不能单独上传控件上的icon,自行在阿里巴巴图标库下载图标替换吧,在安卓和ios上测试是没问题的,有问题欢迎来提!!!