|
@@ -0,0 +1,570 @@
|
|
|
+<script setup>
|
|
|
+import {reactive, ref} from 'vue'
|
|
|
+import {apiVideoDanmuSend} from '@/api/video'
|
|
|
+import moment from 'moment'
|
|
|
+
|
|
|
+const props=defineProps({
|
|
|
+ videoInfo:null,
|
|
|
+ curVideoId:{
|
|
|
+ type:Number,
|
|
|
+ default:0
|
|
|
+ }
|
|
|
+})
|
|
|
+const emit = defineEmits(['clickPlay','ended', 'pause'])
|
|
|
+
|
|
|
+let info=ref(props.videoInfo)
|
|
|
+const videoIns=ref(null)
|
|
|
+
|
|
|
+// 弹幕状态值
|
|
|
+let danmuState=reactive({
|
|
|
+ show:true,//显示弹幕
|
|
|
+ temList:props.videoInfo.bullet_chat_list||[],
|
|
|
+ list:[],
|
|
|
+ content:'',//弹幕内容
|
|
|
+})
|
|
|
+
|
|
|
+// 添加弹幕到页面
|
|
|
+const handleAddDanmu=(time)=>{
|
|
|
+ danmuState.temList.forEach(item => {
|
|
|
+ if(item.seconds>time-1&&item.seconds<time+1){// 前后误差一秒
|
|
|
+ if(!item.done){
|
|
|
+ item.done=true
|
|
|
+ danmuState.list.push({
|
|
|
+ ...item,
|
|
|
+ top:handleGetTopPosition(),
|
|
|
+ speed:Math.floor(Math.random()*(16-8+1))+8//4~8 之间的随机数
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ // 如果播放过了 手贱又把进度条拖回去了 则重置done
|
|
|
+ if(time-1<item.seconds){
|
|
|
+ item.done=false
|
|
|
+ }
|
|
|
+ if(time+1>item.seconds){
|
|
|
+ item.done=true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+const handleGetTopPosition=()=>{
|
|
|
+ const length=danmuState.list.length
|
|
|
+ let num=0
|
|
|
+ if(length%3===1){
|
|
|
+ num=10
|
|
|
+ }else if(length%3===2){
|
|
|
+ num=30
|
|
|
+ }else{
|
|
|
+ num=50
|
|
|
+ }
|
|
|
+ return num+'px'
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+//视频状态值
|
|
|
+let videoState=reactive({
|
|
|
+ ctime:0,//当前播放的时间
|
|
|
+ play:false,
|
|
|
+ isPageFullScreen:false,//网页全屏
|
|
|
+ isHover:false,
|
|
|
+ speedOpts:['0.5','0.8','1.0','1.25','1.5','2.0'],
|
|
|
+ speed:'1.0',
|
|
|
+ showSpeedOpt:false,
|
|
|
+ showSpeedOptInner:false,//显示内部
|
|
|
+})
|
|
|
+
|
|
|
+// 视频事件
|
|
|
+const handleVideoPlay=()=>{
|
|
|
+ videoState.play=true
|
|
|
+}
|
|
|
+const handleVideoEnd=()=>{
|
|
|
+ videoState.play=false
|
|
|
+ videoState.isPageFullScreen=false
|
|
|
+ emit('ended')
|
|
|
+}
|
|
|
+const handleVideoPause=(e)=>{
|
|
|
+ videoState.play=false
|
|
|
+ emit('pause',e)
|
|
|
+}
|
|
|
+const handelClickPlay=()=>{
|
|
|
+ emit('clickPlay',props.videoInfo)
|
|
|
+}
|
|
|
+const handleVideoTimeUpdate=(e)=>{
|
|
|
+ const time=e.target.currentTime
|
|
|
+ handleAddDanmu(time)
|
|
|
+ videoState.ctime=time
|
|
|
+}
|
|
|
+//改变倍速
|
|
|
+const handleVideoSpeedChange=(item)=>{
|
|
|
+ if(videoState.speed==item) return
|
|
|
+ videoState.speed=item
|
|
|
+ videoIns.value.playbackRate=Number(item)
|
|
|
+}
|
|
|
+
|
|
|
+// 鼠标进入视频区域
|
|
|
+const handleMouseMoveInVideo=()=>{
|
|
|
+ videoState.isHover=true
|
|
|
+}
|
|
|
+const handleMouseOutVideo=()=>{
|
|
|
+ videoState.isHover=false
|
|
|
+}
|
|
|
+
|
|
|
+// 新建一条弹幕
|
|
|
+const handleSendDanmu=async ()=>{
|
|
|
+ if(!danmuState.content||info.value.id!=props.curVideoId) return
|
|
|
+ const res=await apiVideoDanmuSend({
|
|
|
+ content:danmuState.content,
|
|
|
+ seconds:parseInt(videoState.ctime),
|
|
|
+ primary_id:props.videoInfo.id,
|
|
|
+ source:props.videoInfo.source
|
|
|
+ })
|
|
|
+ if(res.code===200){
|
|
|
+ danmuState.content=''
|
|
|
+ danmuState.temList.push({...res.data,seconds:Number(res.data.seconds)+3})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="video-play-wrap" @keyup.esc="videoState.isPageFullScreen=false">
|
|
|
+ <div
|
|
|
+ :class="[
|
|
|
+ 'video-content-box',
|
|
|
+ videoState.isPageFullScreen?'page-full-screen':''
|
|
|
+ ]"
|
|
|
+ @mouseover="handleMouseMoveInVideo"
|
|
|
+ @mouseout="handleMouseOutVideo"
|
|
|
+ >
|
|
|
+ <video
|
|
|
+ class="video"
|
|
|
+ ref="videoIns"
|
|
|
+ :src="info.video_url"
|
|
|
+ controls
|
|
|
+ :poster="info.cover_img_url"
|
|
|
+ controlslist="nodownload nofullscreen noplaybackrate"
|
|
|
+ disablePictureInPicture
|
|
|
+ autoplay
|
|
|
+ v-if="info.id==props.curVideoId"
|
|
|
+ @ended="handleVideoEnd"
|
|
|
+ @pause="handleVideoPause"
|
|
|
+ @play="handleVideoPlay"
|
|
|
+ @timeupdate="handleVideoTimeUpdate"
|
|
|
+ ></video>
|
|
|
+ <div v-else class="poster-img" :style="'background-image:url('+info.cover_img_url+')'" @click="handelClickPlay"></div>
|
|
|
+ <!-- 视频按钮 -->
|
|
|
+ <div
|
|
|
+ class="no-select-text video-control-btns-box"
|
|
|
+ :style="{right:videoState.isPageFullScreen?'30px':'10px',bottom:videoState.isPageFullScreen?'95px':'70px'}"
|
|
|
+ v-if="info.id==props.curVideoId"
|
|
|
+ >
|
|
|
+ <!-- 小屏时倍速 -->
|
|
|
+ <div
|
|
|
+ class="small-screen-speed-btn"
|
|
|
+ v-show="!videoState.isPageFullScreen&&videoState.isHover"
|
|
|
+ @click.stop="videoState.showSpeedOpt=true"
|
|
|
+ >倍速</div>
|
|
|
+ <div
|
|
|
+ :class="['screen-change-box', videoState.isPageFullScreen?'full-screen':'small-screen']"
|
|
|
+ v-show="videoState.isHover"
|
|
|
+ @click.stop="videoState.isPageFullScreen=!videoState.isPageFullScreen"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 全屏时发送弹幕模块 -->
|
|
|
+ <div class="flex inside-danmu-input-box" v-if="videoState.isPageFullScreen">
|
|
|
+ <div :class="danmuState.show?'show-icon':'close-icon'" @click.stop="danmuState.show=!danmuState.show"></div>
|
|
|
+ <div class="flex input-box">
|
|
|
+ <input v-model="danmuState.content" type="text" maxlength="50" placeholder="发个友善的弹幕见证当下~" @keyup.enter="handleSendDanmu">
|
|
|
+ <span class="btn" @click.stop="handleSendDanmu">发送</span>
|
|
|
+ </div>
|
|
|
+ <div style="position:relative">
|
|
|
+ <div class="speed-btn">倍速{{videoState.speed}}X</div>
|
|
|
+ <div class="speed-opt-box">
|
|
|
+ <div
|
|
|
+ :class="['item',item==videoState.speed?'active':'']"
|
|
|
+ v-for="item in videoState.speedOpts"
|
|
|
+ :key="item"
|
|
|
+ @click.stop="handleVideoSpeedChange(item)"
|
|
|
+ >{{item}}X</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 弹幕滚动区域 -->
|
|
|
+ <div class="danmu-scroll-box" :style="{opacity: danmuState.show?1:0}">
|
|
|
+ <span
|
|
|
+ :class="[
|
|
|
+ 'no-select-text danmu-item',
|
|
|
+ videoState.play?'animat-run':'animat-pause',
|
|
|
+ item.user_id==$store.state.userInfo.user_id?'border':''
|
|
|
+ ]"
|
|
|
+ v-for="item in danmuState.list"
|
|
|
+ :key="item.id"
|
|
|
+ :style="{
|
|
|
+ color:item.color,
|
|
|
+ top:item.top,
|
|
|
+ animationDuration:videoState.isPageFullScreen?item.speed+20+'s':item.speed+'s'
|
|
|
+ }"
|
|
|
+ >{{item.content}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频外面发弹幕输入模块 -->
|
|
|
+ <div class="flex outside-danmu-input-box">
|
|
|
+ <div :class="danmuState.show?'show-icon':'close-icon'" @click.stop="danmuState.show=!danmuState.show"></div>
|
|
|
+ <div class="flex input-box">
|
|
|
+ <input
|
|
|
+ :disabled="info.id!=props.curVideoId"
|
|
|
+ v-model="danmuState.content"
|
|
|
+ type="text"
|
|
|
+ maxlength="50"
|
|
|
+ placeholder="发个友善的弹幕见证当下~"
|
|
|
+ @keyup.enter="handleSendDanmu"
|
|
|
+ @keyup.esc="videoState.isPageFullScreen=false"
|
|
|
+ >
|
|
|
+ <span class="btn" :style="{background:info.id!=props.curVideoId?'#929292':''}" @click.stop="handleSendDanmu">发送</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频倍速选择弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="videoState.showSpeedOpt"
|
|
|
+ title="倍速"
|
|
|
+ width="350px"
|
|
|
+ draggable
|
|
|
+ center
|
|
|
+ >
|
|
|
+ <div class="outside-speed-opt-box">
|
|
|
+ <span
|
|
|
+ :class="['item',videoState.speed==item?'active':'']"
|
|
|
+ v-for="item in videoState.speedOpts"
|
|
|
+ :key="item"
|
|
|
+ @click.stop="handleVideoSpeedChange(item)"
|
|
|
+ >{{item}}X</span>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.video-play-wrap{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ .video-content-box{
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ width: 100%;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+ .page-full-screen{
|
|
|
+ position: fixed;
|
|
|
+ left: 0 !important;
|
|
|
+ right: 0 !important;
|
|
|
+ top: 0 !important;
|
|
|
+ bottom: 0 !important;
|
|
|
+ width: auto !important;
|
|
|
+ height: auto !important;
|
|
|
+ z-index: 999999 !important;
|
|
|
+ background: rgba(0, 0, 0, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-control-btns-box{
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ bottom: 70px;
|
|
|
+ .screen-change-box{
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 50%;
|
|
|
+ &:hover{
|
|
|
+ background: rgba(0, 0, 0, 0.3);
|
|
|
+ }
|
|
|
+ &::after{
|
|
|
+ content: '';
|
|
|
+ display: block;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ background-size: cover;
|
|
|
+ position: absolute;
|
|
|
+ top: 5px;
|
|
|
+ left: 5px;
|
|
|
+ }
|
|
|
+ &.full-screen{
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ &::after{
|
|
|
+ background-image: url('@/assets/video/smallscreen-icon.png');
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.small-screen::after{
|
|
|
+ background-image: url('@/assets/video/fullscreen-icon.png');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .small-screen-speed-btn{
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ border-radius: 22px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ width: 40px;
|
|
|
+ height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 20px;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .video{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: block;
|
|
|
+ object-fit: contain;
|
|
|
+ box-sizing: border-box;
|
|
|
+ &::-webkit-media-controls-fullscreen-button {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .poster-img{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ background-size: cover;
|
|
|
+ background-position: center;
|
|
|
+ cursor: pointer;
|
|
|
+ &::after{
|
|
|
+ content:'';
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ width: 80px;
|
|
|
+ height: 80px;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%,-50%);
|
|
|
+ background-image: url('@/assets/video-play-btn.png');
|
|
|
+ background-size: cover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .inside-danmu-input-box{
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ bottom: 100px;
|
|
|
+ .close-icon{
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background-image: url('@/assets/video/danmu-close2-icon.png');
|
|
|
+ background-size: cover;
|
|
|
+ flex-shrink: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .show-icon{
|
|
|
+ width: 34px;
|
|
|
+ height: 32px;
|
|
|
+ background-image: url('@/assets/video/danmu-show2-icon.png');
|
|
|
+ background-size: cover;
|
|
|
+ flex-shrink: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .input-box{
|
|
|
+ margin-left: 10px;
|
|
|
+ margin-right: 20px;
|
|
|
+ background: rgba(0, 0, 0, 0.2);
|
|
|
+ border: 1px solid #E5E5E5;
|
|
|
+ border-radius: 42.5px;
|
|
|
+ width: 40vw;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ height: 32px;
|
|
|
+ font-size: 14px;
|
|
|
+ padding-left: 10px;
|
|
|
+ input{
|
|
|
+ flex: 1;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ height: 100%;
|
|
|
+ outline: none;
|
|
|
+ color: #fff;
|
|
|
+ &::placeholder{
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .btn{
|
|
|
+ display: inline-block;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #fff;
|
|
|
+ background: #F3A52F;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 60px;
|
|
|
+ height: 32px;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .speed-btn{
|
|
|
+ display: inline-block;
|
|
|
+ width: 90px;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ text-align: center;
|
|
|
+ color: #fff;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ border-radius: 22px;
|
|
|
+ &:hover+.speed-opt-box{
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .speed-opt-box{
|
|
|
+ width: 120px;
|
|
|
+ padding: 20px 0 0px 0;
|
|
|
+ position: absolute;
|
|
|
+ bottom: 105%;
|
|
|
+ left: 50%;
|
|
|
+ display: none;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ background: rgba(0, 0, 0, 0.8);
|
|
|
+ &:hover{
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ .item{
|
|
|
+ cursor: pointer;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ &.active{
|
|
|
+ color: #F3A52F;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .outside-danmu-input-box{
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 20px;
|
|
|
+ .close-icon{
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background-image: url('@/assets/video/danmu-close-icon.png');
|
|
|
+ background-size: cover;
|
|
|
+ flex-shrink: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .show-icon{
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background-image: url('@/assets/video/danmu-show-icon.png');
|
|
|
+ background-size: cover;
|
|
|
+ flex-shrink: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .input-box{
|
|
|
+ margin-left: 10px;
|
|
|
+ background: #F4F4F4;
|
|
|
+ border: 1px solid #E5E5E5;
|
|
|
+ border-radius: 42.5px;
|
|
|
+ flex: 1;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ height: 32px;
|
|
|
+ font-size: 12px;
|
|
|
+ padding-left: 10px;
|
|
|
+ input{
|
|
|
+ flex: 1;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ height: 100%;
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+ .btn{
|
|
|
+ display: inline-block;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #fff;
|
|
|
+ background: #F3A52F;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 60px;
|
|
|
+ height: 32px;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .danmu-scroll-box{
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 70px;
|
|
|
+ // background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 100%);
|
|
|
+ .danmu-item{
|
|
|
+ color: #fff;
|
|
|
+ animation: move 6s linear;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ display: block;
|
|
|
+ left: calc(100% + 1000px);
|
|
|
+ position: absolute;
|
|
|
+ font-size: 12px;
|
|
|
+ height: 18px;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ .animat-pause{
|
|
|
+ animation-play-state: paused;
|
|
|
+ }
|
|
|
+ .animat-run{
|
|
|
+ animation-play-state: running;
|
|
|
+ }
|
|
|
+ .border{
|
|
|
+ border: 1px solid #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ padding-left: 2px;
|
|
|
+ padding-right: 2px;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+ @keyframes move {
|
|
|
+ 0%{
|
|
|
+ left: calc(100% + 1000px);
|
|
|
+ }
|
|
|
+ 100%{
|
|
|
+ left: -1000px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .outside-speed-opt-box{
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ justify-content: space-between;
|
|
|
+ .item{
|
|
|
+ display: inline-block;
|
|
|
+ width: 80px;
|
|
|
+ height: 36px;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 36px;
|
|
|
+ border: 1px solid #F3A52F;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ color: #F3A52F;
|
|
|
+ margin: 10px 0;
|
|
|
+ cursor: pointer;
|
|
|
+ &.active{
|
|
|
+ background-color: #F3A52F;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|