安装xgplayer
# 最新稳定版
$ npm install xgplayer
封装H5端视频播放组件:index.h5.js
import './index.scss'
import Player from 'xgplayer'
import 'xgplayer/dist/index.min.css'
import { View, Image, Text } from '@tarojs/components'
import { nextTick } from '@tarojs/taro'
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'
import ReplayIcon from '@/images/xgplayer/video-replay.png'
import NextIcon from '@/images/xgplayer/video-next.png'
const Index = forwardRef((props, ref) => {
const { id = 'xgVideo', videoUrl = '', posterUrl = '', autoplay = false, isNextBtn = false, nextClick = '' } = props
// 西瓜视频实例
const [xgPlayer, setXgPlayer] = useState(null)
// 是否播放结束
const [isEnded, setIsEnded] = useState(false)
// 实例化播放器
useEffect(() => {
if (videoUrl) {
nextTick(() => {
let player = new Player({
id: id, // 选择器 id
url: videoUrl, // 视频源 url
poster: posterUrl, // 封面图地址
autoplay: autoplay, // 是否自动播放,不是所有场景配置了自动播放都可以成功自动起播的
height: '100%',
width: '100%',
// rotate: { //视频旋转按钮配置项
// innerRotate: true, //只旋转内部video
// clockwise: false // 旋转方向是否为顺时针
// },
rotateFullscreen: true, // 样式横屏全屏
enableVideoDbltouch: true, // 使移动端支持双击暂停/播放
videoInit: true, // 是否默认初始化video,当autoplay为true时,该配置为false无效
playsinline: true, // 是否启用内联播放模式
'x5-video-player-type': 'h5', // 微信同层播放
'x5-video-player-fullscreen': false, // 微信全屏播放
'webkit-playsinline': true,
'x5-playsinline': true,
loop: false, // 是否循环播放
fluid: true, // 是否启用流式布局,启用流式布局时根据width、height计算播放器宽高比,若width和height不是Number类型,默认使用16:9比例// 菜单控件
ignores: ['volume', 'fullscreen', 'playbackrate', 'pip', 'replay'], // 禁用插件
lang: 'zh-cn'
})
setXgPlayer(player)
// 监听是否播放结束
player.on('ended', () => {
setIsEnded(true)
})
// 监听是否播放
player.on('play', () => {
setIsEnded(false)
})
})
}
}, [videoUrl])
// 遮罩点击
const coverBoxClick = (e) => {
e.stopPropagation()
if (xgPlayer && !isEnded) {
if (xgPlayer.paused) {
xgPlayer.play()
} else {
xgPlayer.pause()
}
}
}
// 销毁video实例
const destroyVideo = () => {
if (xgPlayer) {
xgPlayer.destroy() // 销毁播放器
setXgPlayer(null) // 将实例引用置空
}
}
// 重播按钮点击
const replayBtn = (e) => {
e.stopPropagation()
if (xgPlayer) {
xgPlayer.replay()
}
}
// 暂停视频方法
const pauseVideo = () => {
if (xgPlayer && !isEnded && !xgPlayer.paused) {
xgPlayer.pause()
}
}
// 下一个点击
const nextBtn = (e) => {
e.stopPropagation()
nextClick && nextClick()
}
// 导出方法
useImperativeHandle(ref, () => ({
destroyVideo, // 销毁video实例
pauseVideo // 暂停视频
}))
// Html结构部分
return (
<View className="xgplayerBox">
<View id={id}></View>
{/*遮罩部分*/}
<View onClick={(e) => coverBoxClick(e)} className="xgplayerBox-coverBox">
{
isEnded && <View onClick={(e) => replayBtn(e)} className="btnBox">
<Image className="btnBox-icon" src={ReplayIcon}/>
<Text>重播</Text>
</View> || ''
}
{
isEnded && isNextBtn && <View onClick={(e) => nextBtn(e)} className="btnBox">
<Image className="btnBox-icon" src={NextIcon}/>
<Text>下一个</Text>
</View>
}
</View>
</View>
)
})
export default Index
封装微信小程序端视频播放组件:index.js
import './index.scss'
import { View, Video, Image, Text } from '@tarojs/components'
import { createVideoContext } from '@tarojs/taro'
import { forwardRef, useImperativeHandle, useState } from 'react'
import ReplayIcon from '@/images/xgplayer/video-replay.png'
import NextIcon from '@/images/xgplayer/video-next.png'
const Index = forwardRef((props, ref) => {
const { id = 'xgVideo', videoUrl = '', posterUrl = '', autoplay = false, isNextBtn = false, nextClick = '' } = props
// 视频实例
let videoContext = createVideoContext(id)
// 播放状态
const [paused, setPaused] = useState(true)
// 是否播放结束
const [isEnded, setIsEnded] = useState(false)
// 遮罩点击
const coverBoxClick = (e) => {
e.stopPropagation()
if (paused) {
videoContext.play()
} else {
videoContext.pause()
}
}
// 监听播放
const onPlay = () => {
setPaused(false)
setIsEnded(false)
}
// 监听暂停
const onPause = () => {
setPaused(true)
}
// 监听播放结束
const onEnded = () => {
setIsEnded(true)
}
// 重播按钮点击
const replayBtn = (e) => {
e.stopPropagation()
videoContext.play()
}
// 暂停视频方法
const pauseVideo = () => {
if (!isEnded && !paused) {
videoContext.pause()
}
}
// 下一个点击
const nextBtn = (e) => {
e.stopPropagation()
nextClick && nextClick()
}
// 导出方法
useImperativeHandle(ref, () => ({
pauseVideo
}))
// Html结构部分
return (
<View className="xgplayerBox">
<Video className="xgplayerBox-video" id={id} src={videoUrl} poster={posterUrl} autoplay={autoplay}
initialTime={0} controls enablePlayGesture loop={false} muted={false} showFullscreenBtn={false}
onPlay={() => onPlay()} onPause={() => onPause()} onEnded={() => onEnded()}/>
{/*遮罩部分*/}
<View onClick={(e) => coverBoxClick(e)} className="xgplayerBox-coverBox">
{
isEnded && <View onClick={(e) => replayBtn(e)} className="btnBox">
<Image className="btnBox-icon" src={ReplayIcon} mode="heightFix"/>
<Text>重播</Text>
</View> || ''
}
{
isEnded && isNextBtn && <View onClick={(e) => nextBtn(e)} className="btnBox">
<Image className="btnBox-icon" src={NextIcon} mode="heightFix"/>
<Text>下一个</Text>
</View> || ''
}
</View>
</View>
)
})
export default Index
双端的样式:index.scss
.xgplayerBox {
font-size: 12px;
line-height: 1.2;
overflow: hidden;
background: #FFFFFF;
position: relative;
// H5中使用
.xgplayer {
background: transparent;
}
// 小程序中使用
@at-root &-video {
width: 100%;
}
//遮罩部分
@at-root &-coverBox {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 50px;
z-index: 10;
background: rgba(0, 0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
.btnBox {
display: flex;
align-items: center;
justify-content: center;
background: rgba(22, 24, 35, 0.6);
margin: 50px 5px 0;
border-radius: 50px;
padding: 0 10px;
font-size: 14px;
color: #FFFFFF;
@at-root &-icon {
height: 30px;
width: auto;
vertical-align: middle;
}
}
}
}
使用组件xgplayerSwiper:index.js
import './index.scss'
import { View, Swiper, SwiperItem } from '@tarojs/components'
import { pxTransform } from '@tarojs/taro'
import { forwardRef, useRef, useState } from 'react'
import Xgplayer from '@/components/xgplayer/index'
const Index = forwardRef((props, ref) => {
// 环境标识
const isH5 = process.env.TARO_ENV === 'h5'
const { videoUrlList = [], itemStyle = '', swiperHeightH5 = 'auto', swiperHeightMini = 202 } = props
const refs = []
videoUrlList.forEach((item, index) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const itemRef = useRef(null) // 创建新的ref对象
refs[index] = itemRef // 将ref添加到数组中
})
// 激活的index
const [activeIndex, setActiveIndex] = useState(0)
// swiper改变暂停视频
const onXgplayerSwiperChange = (e) => {
refs.forEach((item, index) => {
item.current.pauseVideo()
})
setActiveIndex(e.detail.current)
}
// 下一个点击
const nextClick = () => {
if (activeIndex === videoUrlList.length - 1) {
setActiveIndex(0)
} else {
setActiveIndex(activeIndex + 1)
}
}
// Html结构部分
return (
<View className="xgplayerSwiperBox">
{
videoUrlList.length &&
<Swiper className="swiper" current={activeIndex} indicatorDots={videoUrlList.length > 1}
style={`height: ${isH5 ? swiperHeightH5 !== 'auto' ? pxTransform(swiperHeightH5) : 'auto' : pxTransform(swiperHeightMini + 26)}`}
onChange={(e) => onXgplayerSwiperChange(e)}>
{
videoUrlList.map((item, index) => {
return (
<SwiperItem key={`SwiperItem-${index}`}>
<View style={itemStyle}>
<Xgplayer id={`video-${index}`} ref={refs[index]} videoUrl={item.videoUrl}
posterUrl={item.posterUrl} isNextBtn={videoUrlList.length > 1} nextClick={nextClick}/>
</View>
</SwiperItem>
)
})
}
</Swiper> || ''
}
</View>
)
})
export default Index
使用组件xgplayerSwiper:index.scss
.xgplayerSwiperBox {
font-size: 12px;
line-height: 1.2;
overflow: hidden;
}