1.安装dplayer
见官网 https://dplayer.diygod.dev/zh/guide.html#special-thanks
注:本人使用版本 v1.25。0
2.组件引入dplyer
import DPlayer from 'dplayer'
import Hls from 'hls.js';//用于解析m3u8直播流
import "dplayer/dist/DPlayer.min.css" //v1.25.0版本需要引入css
3.tempate
<template>
<ClientOnly>
<div class="bit" :class="{ 'full': state.isFull }">
<div ref="videoRef" class="myvideo"></div>
<div class="mute" @click="changeVolume" v-show="state.show && !state.showLogin">
<img src="~/assets/images/mute.svg" alt="">
<div>CLICK TO UNMUTE</div>
</div>
<div class="goLogin" v-if="state.showLogin">
<div class="logo">
<img src="~/assets/images/logo.png" alt="">
<div v-if="state.countDown > 0">
{{ state.countDown }}s
<van-icon @click="closeLogin" name="cross" />
</div>
</div>
<div class="txt" v-if="state.watchTime < 600">
Inicie sesión para eliminar la ventana emergente y mirar en modo de pantalla completa
</div>
<div class="txt" v-else>
Su período de prueba de 10 minutos de hoy ha expirado. Por favor, inicie sesión para ver el juego.
</div>
<div class="login-btn" @click="goLogin">Login Account</div>
</div>
</div>
</ClientOnly>
</template>
4.完整代码
<script setup>
import DPlayer from 'dplayer'
import Hls from 'hls.js';
import "dplayer/dist/DPlayer.min.css"
import { ref, reactive, onBeforeUnmount, onMounted, onDeactivated, onActivated } from 'vue'
import logo from '~/assets/images/logo.png'
import load from '~/assets/images/reload.svg'
import { storage } from "~~/utils/storage";
import { useCounterStore } from "~/stores/conter";
const counterStore = useCounterStore();
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute()
const videoRef = ref()
const state = reactive({
instance: null,
player: {},
showLogin: false,
watchTime: 0,//观看总时长
showNum: 0,//showlogin 次数
countDown: 0,//showlogin 倒计时
minTime: 0,//分钟倒计时
show: false,//是否显示静音
isFull: false,//播放器是否全屏
isEnter: false,//画中画模式
})
const counter = ref(0);
let intervalId = null
const hls = new Hls(); //实例化Hls 用于解析m3u8
//dplayer 参数设置
const props = defineProps({
// 是否自动播放
autoplay: {
type: Boolean,
default: false
},
// 主题色
theme: {
type: String,
default: '#0093ff'
},
// 视频是否循环播放
loop: {
type: Boolean,
default: false
},
airplay: {
type: Boolean,
default: true
},
// 语言(可选值: 'en', 'zh-cn', 'zh-tw')
lang: {
type: String,
default: 'en'
},
// 是否开启截图(如果开启,视频和视频封面需要允许跨域)
screenshot: {
type: Boolean,
default: false
},
// 是否开启热键
hotkey: {
type: Boolean,
default: true
},
// 视频是否预加载(可选值: 'none', 'metadata', 'auto')
preload: {
type: String,
default: 'auto'
},
// 默认音量
volume: {
type: Number,
default: 0.7
},
// 可选的播放速率,可以设置成自定义的数组
playbackSpeed: {
type: Array,
default: [0.5, 0.75, 1, 1.25, 1.5, 2]
},
// 在左上角展示一个 logo,你可以通过 CSS 调整它的大小和位置
logo: {
type: String,
default: logo
},
// 视频信息
video: {
type: Object,
default: () => {
},
},
// 外挂字幕
// subtitle: {
// type: Object,
// default: {}
// },
// 显示弹幕
// danmaku: {
// type: Object,
// default: {}
// },
// 自定义右键菜单
contextmenu: {
type: Array,
default: []
},
// 自定义进度条提示点
highlight: {
type: Array,
default: []
},
// 阻止多个播放器同时播放,当前播放器播放时暂停其他播放器
mutex: {
type: Boolean,
default: false
},
// 直播
live: {
type: Boolean,
default: false
},
video_url: {
type: String,
default: ''
},
isPlay: {
type: Boolean,
default: false
},
pic: {
type: String,
default: ''
},
})
//跳转登录页
const goLogin = () => {
router.push({ path: '/login' });
}
// 初始化播放器
const init = async () => {
let player = {
container: videoRef.value,
autoplay: props.autoplay,
theme: props.theme,
loop: props.loop,
lang: props.lang,
screenshot: props.screenshot,
hotkey: props.hotkey,
preload: props.preload,
volume: props.volume,
playbackSpeed: props.playbackSpeed,
logo: props.logo,
video: props.video,
// contextmenu: props.contextmenu,
// highlight: props.highlight,
mutex: props.mutex,
live: props.live
}
player.video = {
url: props.video_url,
type: 'customHls',
customType: {
customHls: function (video, player) {
//实例化Hls 用于解析m3u8
hls.loadSource(video.src);
hls.attachMedia(video);
}
},
pic: props.pic
// muted:true
}
state.player = player
state.instance = new DPlayer(player)
console.log(state.instance, player)
let video = document.querySelector("video"); //获取video元素
let videoPlay = video.play();
setTimeout(() => {
// 判断video是否能够自动播放
videoPlay.then(() => {
console.log('canplay');
}).catch((err) => {
console.log(err);
console.log("no canplay");
//视频元素可以选择静音后再播放,提示用户打开声音
state.instance?.pause()
setTimeout(() => {
state.instance?.volume(0, true, true);
state.instance?.play()
}, 1500)
// video.muted = true;
});
})
//在全屏按钮旁边添加视频刷新按钮
if (state.instance) {
let dom = document.getElementsByClassName('dplayer-icons-right')[0]
let newElement = document.createElement('img');
newElement.src = load
newElement.className += 'reload'
// dom.appendChild(newElement)
dom.insertBefore(newElement, dom.firstChild);
newElement.addEventListener("click", function () {
// console.log("点击了新添加的元素!");
// state.instance.video.load()
state.instance.destroy()
setTimeout(() => {
init()
}, 500);
});
}
state.instance.on('pause', function () {
// console.log('pause')
})
// 监听视频是否画中画模式
video.addEventListener('enterpictureinpicture', function () {
// 已进入画中画模式
// console.log('已进入画中画模式')
state.isEnter = true
});
video.addEventListener('leavepictureinpicture', function () {
// 已退出画中画模式
// console.log('已退出画中画模式')
state.isEnter = false
if (state.instance?.paused) {
setTimeout(() => {
state.instance?.play()
}, 500);
}
});
// 监听video在IOS中是否全屏
video.addEventListener("webkitbeginfullscreen", () => {
console.log("webkitbeginfullscreen");
state.isFull = true
});
video.addEventListener("webkitendfullscreen", () => {
console.log("webkitendfullscreen");
state.isFull = false
if (state.instance?.paused) {
setTimeout(() => {
state.instance?.play()
}, 500);
}
});
state.instance.on('waiting', function () {
// console.log('waiting')
})
state.instance.on('volumechange', function () {
console.log('volumechange', state.instance.video.volume)
if (state.instance.video.volume == 0) {
state.show = true
} else {
state.show = false
}
})
// 监听是否全屏
state.instance.on('fullscreen', function () {
state.isFull = true
console.log(state.isFull)
})
state.instance.on('fullscreen_cancel', function () {
// console.log(state.instance.video)
state.isFull = false
console.log(state.isFull)
})
}
let timerId = null
// 登录弹窗倒计时
const downTime = () => {
// let timer = 0
state.showLogin = true
// 如果video进入全屏或画中画模式,观看时长达到限制后自动退出全屏和画中画
if (state.isFull) {
document.querySelector("video").webkitExitFullscreen()
state.isFull = false
state.instance.fullScreen.cancel()
//判断退出全屏后视频是否暂停,暂停继续播放
setTimeout(() => {
if (state.instance?.paused) {
setTimeout(() => {
state.instance?.play()
}, 500);
}
}, 500);
// console.log("webkitendfullscreen",state.isFull,2);
}
//
if (state.isEnter) {
document.exitPictureInPicture();
}
state.showNum++
if (state.showNum == 1) {
state.countDown = 15
timerId = setInterval(() => {
state.countDown--
console.log('countDown0', state.countDown)
if (state.countDown <= 0) {
clearInterval(timerId)
state.showLogin = false
state.minTime = 0
state.countDown = 0
return watchTimeFun()
}
}, 1000);
}
if (state.showNum == 2) {
state.countDown = 30
timerId = setInterval(() => {
state.countDown--
console.log('countDown1', state.countDown)
if (state.countDown <= 0) {
clearInterval(timerId)
// state.showNum++
state.showLogin = false
state.minTime = 0
state.countDown = 0
return watchTimeFun()
}
}, 1000);
}
}
// 记录游客观看视频时长
const watchTimeFun = () => {
if (counterStore.data.userInfo?.id) {
storage.set('liveTime', 0)
storage.set('startWatchTime', 0)
return
}
if (JSON.parse(JSON.stringify(storage.get('startWatchTime')))) {
if (Date.now() - JSON.parse(JSON.stringify(storage.get('startWatchTime'))) > 4 * 60 * 60 * 1000) {
storage.set('startWatchTime', Date.now())
storage.set('liveTime', 0)
}
} else {
storage.set('startWatchTime', Date.now())
}
state.watchTime = JSON.parse(JSON.stringify(storage.get('liveTime'))) || 0
// let time = 0
// 这边需求是4个小时内 游客总计观看时长10分钟,每观看一分钟提醒用户去登录(弹出登录弹窗)
if (state.watchTime >= 600) {
state.showLogin = true
state.minTime = 0
state.countDown = 0
clearInterval(intervalId)
clearInterval(timerId)
return
}
intervalId = setInterval(() => {
state.minTime++
if (state.minTime <= 60 && state.countDown <= 0) {
state.watchTime++;
if (state.watchTime >= 600) {
state.showLogin = true
if (state.isFull) {
// let video = document.querySelector("video")
// if (video.exitFullscreen) {
// video.exitFullscreen();
// } else if (video.mozCancelFullScreen) {
// video.mozCancelFullScreen();
// } else if (video.webkitCancelFullScreen) {
// video.webkitCancelFullScreen();
// }
document.querySelector("video").webkitExitFullscreen()
state.isFull = false
state.instance.fullScreen.cancel()
setTimeout(() => {
if (state.instance?.paused) {
setTimeout(() => {
state.instance?.play()
}, 500);
}
}, 500);
}
if (state.isEnter) {
document.exitPictureInPicture();
}
state.minTime = 0
state.countDown = 0
clearInterval(intervalId)
clearInterval(timerId)
}
storage.set('liveTime', state.watchTime)
} else {
clearInterval(intervalId)
if (state.showNum <= 2) {
downTime()
} else {
state.showLogin = true;
if (state.isFull) {
// let video = document.querySelector("video")
// if (video.exitFullscreen) {
// video.exitFullscreen();
// } else if (video.mozCancelFullScreen) {
// video.mozCancelFullScreen();
// } else if (video.webkitCancelFullScreen) {
// video.webkitCancelFullScreen();
// }
document.querySelector("video").webkitExitFullscreen()
state.isFull = false
state.instance.fullScreen.cancel()
setTimeout(() => {
if (state.instance?.paused) {
setTimeout(() => {
state.instance?.play()
}, 500);
}
}, 500);
}
if (state.isEnter) {
document.exitPictureInPicture();
}
return
}
}
// console.log(state.watchTime, state.countDown, state.showNum, state.minTime)
}, 1000);
}
const closeLogin = () => {
clearInterval(intervalId)
clearInterval(timerId)
state.showLogin = false
state.minTime = 0
state.countDown = 0
watchTimeFun()
}
const changeVolume = () => {
state.instance.video.muted = false
state.instance.volume(.7, false, true);
}
onMounted(() => {
nextTick(async () => {
await init()
console.log('onMounted')
watchTimeFun()
})
})
// 销毁
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
// state.instance
clearInterval(intervalId)
clearInterval(timerId)
hls.destroy() //销毁视频后不继续请求m3u8资源
state.instance.destroy() //销毁视频
})
onActivated(() => {
nextTick(() => {
console.log('onActivated')
})
})
onDeactivated(() => {
console.log('onDeactivated')
setInterval(intervalId)
})
</script>
<style lang='scss' scoped>
.bit {
height: 100% !important;
width: 100% !important;
position: relative;
&.full {
position: fixed;
// width: 100vw!important;
// height: 100vh!important;
left: 0;
top: 0;
}
}
.goLogin {
height: 100% !important;
width: 100% !important;
position: absolute;
top: 0;
// border-radius: 20px;
background: rgba(0, 0, 0, .8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: .16rem;
box-sizing: border-box;
gap: .24rem;
font-size: .28rem;
color: #fff;
z-index: 999999;
.logo {
position: absolute;
top: 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
// padding: 0;
padding: .16rem;
box-sizing: border-box;
font-size: .24rem;
img {
max-height: .70rem;
max-width: 2.40rem;
}
>div {
display: flex;
justify-content: space-between;
align-items: center;
gap: .12rem;
::v-deep .van-icon {
font-size: .36rem;
cursor: pointer;
}
}
}
.txt {
text-align: center;
}
.login-btn {
height: .80rem;
width: 3.20rem;
border-radius: 16px;
background: #69AE64;
display: flex;
justify-content: center;
align-items: center;
font-size: .32rem;
cursor: pointer;
}
}
.dplayer {
height: 100% !important;
width: 100% !important;
.dplayer-menu {
display: none !important;
visibility: hidden !important;
}
.dplayer-menu-show {
display: none !important;
visibility: hidden !important;
}
}
.mute {
position: absolute;
top: .20rem;
left: .20rem;
display: flex;
height: .60rem;
background: #69AE64;
border-radius: .08rem;
align-items: center;
padding: .12rem .24rem;
gap: .16rem;
font-size: .24rem;
box-sizing: border-box;
z-index: 9;
color: #fff;
img {
width: .40rem;
height: .40rem;
}
.line {
width: .02rem;
height: 100%;
background: #000;
}
}
::v-deep {
.dplayer-full-in-icon {
display: none !important;
}
.dplayer-icons-right {
display: flex;
align-items: center;
gap: .16rem;
.reload {
cursor: pointer;
height: .40rem;
width: .46rem;
background: url('~/assets/images/reload.svg') no-repeat;
opacity: .9;
}
}
}
::v-deep .dplayer-menu,
.dplayer-mask {
display: none !important;
}
</style>
5.总结
由于浏览器策略限制了网站在用户没有交互的情况下禁止自动播放音频,故 采用静音播放(如果能有声自动播放则自动播放,不能播放则设置视频音量为静音播放)。
以上兼容pc和移动端(ios也有效)