|
@@ -0,0 +1,521 @@
|
|
|
+<template>
|
|
|
+ <view class="video-wrap" @click="handleClickWrap">
|
|
|
+ <video
|
|
|
+ autoplay
|
|
|
+ object-fit="contain"
|
|
|
+ show-mute-btn
|
|
|
+ enable-play-gesture
|
|
|
+ :poster="videoInfo.cover_img_url"
|
|
|
+ :src="videoInfo.video_url"
|
|
|
+ :id="videoInfo.id"
|
|
|
+ @ended="handleVideoEnd"
|
|
|
+ @play="handleVideoPlay"
|
|
|
+ @pause="handleVideoPause"
|
|
|
+ @timeupdate="handleTimeUpdate"
|
|
|
+ @fullscreenchange="handleFullscreenchange"
|
|
|
+ @controlstoggle="handleControlstoggle"
|
|
|
+ v-if="videoInfo.id==curVideoId"
|
|
|
+ >
|
|
|
+
|
|
|
+ <!-- 弹幕滚动模块 -->
|
|
|
+ <view class="danmu-scroll-box" v-show="!closeDM">
|
|
|
+ <view
|
|
|
+ :class="[
|
|
|
+ 'danmu-item',
|
|
|
+ play?'animat-run':'animat-pause',
|
|
|
+ item.user_id==selfUserid?'danmu-item-self':''
|
|
|
+ ]"
|
|
|
+ v-for="item in danmuList"
|
|
|
+ :key="item.id"
|
|
|
+ :style="{color:item.color,top:item.top,animationDuration:isFullScreen?item.speed+7+'s':item.speed+'s'}"
|
|
|
+ >{{item.content}}</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="video-inner-right-box" v-if="isShowControls">
|
|
|
+ <!-- 切换音频播放按钮 -->
|
|
|
+ <view :class="['change-music-icon',isFullScreen&&'isFullScreen']" @click.stop="handleChangeMusic"></view>
|
|
|
+ <!-- 弹幕控制按钮 -->
|
|
|
+ <view :class="['video-danmu-control-box',isFullScreen&&'isFullScreen']">
|
|
|
+ <view class="show-btn" v-if="!closeDM" @click.stop="closeDM=true"></view>
|
|
|
+ <view class="close-btn" v-else @click.stop="closeDM=false"></view>
|
|
|
+ <!-- <view class="send-btn" v-if="!closeDM" @click.stop="showInput=true">发弹幕</view> -->
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 倍速控制按钮 -->
|
|
|
+ <view class="video-speed-btn" @click.stop="showSpeedOpt=true">倍速</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 倍速选项模块 -->
|
|
|
+ <view class="speed-opt-box" v-if="showSpeedOpt">
|
|
|
+ <view
|
|
|
+ class="item"
|
|
|
+ :style="{color:item==curSpeed?'#F3A52F':''}"
|
|
|
+ v-for="item in speedOpts"
|
|
|
+ :key="item"
|
|
|
+ @click.stop="handleVideoSpeedChange(item)"
|
|
|
+ >{{item}}X</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </video>
|
|
|
+ <image @click="handelClickPlay" v-else class="poster" :src="videoInfo.cover_img_url" mode="aspectFill" lazy-load/>
|
|
|
+
|
|
|
+ <!-- 外面弹幕按钮 -->
|
|
|
+ <view class="danmu-btn-box" v-if="showDanmu">
|
|
|
+ <view class="big-box" v-if="!closeDM">
|
|
|
+ <view class="left" @click.stop="showInput=true"></view>
|
|
|
+ <view class="right" @click.stop="closeDM=true"></view>
|
|
|
+ </view>
|
|
|
+ <view class="small-box" v-else @click.stop="handleShowDM"></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 弹幕输入弹窗 -->
|
|
|
+ <view class="flex danmu-input-box" :style="{bottom:keyboardheight+'px',paddingLeft:isFullScreen?safeAreaTop+17+'px':'34rpx'}" v-if="showInput">
|
|
|
+ <view class="flex input-box">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ v-model="danmuText"
|
|
|
+ placeholder="发个友善的弹幕见证当下~"
|
|
|
+ cursor-spacing="20"
|
|
|
+ maxlength="50"
|
|
|
+ focus
|
|
|
+ confirm-type="send"
|
|
|
+ :adjust-position="false"
|
|
|
+ @confirm="handleSendDanmu"
|
|
|
+ @focus="handleDanmuInputFocus"
|
|
|
+ @blur="handleDanmuInputBlur"
|
|
|
+ />
|
|
|
+ <text>{{danmuText.length}}/50</text>
|
|
|
+ </view>
|
|
|
+ <view class="btn" @click="handleSendDanmu">发送</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {apiVideoDanmuSend} from '@/api/video'
|
|
|
+import { debounce } from '@/utils/common.js';
|
|
|
+export default {
|
|
|
+ props:{
|
|
|
+ showDanmu:{
|
|
|
+ type:Boolean,
|
|
|
+ default:false
|
|
|
+ },//是否显示弹幕
|
|
|
+ videoInfo:{},//{source:2-视频社区\3-路演视频,id:视频社区id\路演视频id,...其他数据}
|
|
|
+ curVideoId:0,//当前正在播放的id
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ curVideoId(){
|
|
|
+ this.curSpeed='1.0'
|
|
|
+ if(this.videoInfo.id==this.curVideoId){//显示弹幕
|
|
|
+ this.closeDM=false
|
|
|
+ }else{
|
|
|
+ this.closeDM=true
|
|
|
+ this.curVideoIns=null
|
|
|
+ this.play=false
|
|
|
+ this.danmuList=[]
|
|
|
+ this.temdanmuList.forEach(item=>{
|
|
|
+ item.done=false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showInput(n){
|
|
|
+ //写弹幕时暂停视频
|
|
|
+ if(n){
|
|
|
+ this.curVideoIns.pause()
|
|
|
+ }else{
|
|
|
+ this.curVideoIns.play()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ closeDM(){
|
|
|
+ if(this.closeDM){
|
|
|
+ this.danmuList=[]//如果关闭了弹幕显示 则清空一次 弹幕列表 防止 切换回来又会重新播放一次已有的弹幕
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed:{
|
|
|
+ selfUserid(){
|
|
|
+ return this.$store.state.user.userInfo.user_id;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ curVideoIns:null,
|
|
|
+ play:false,//视频播放状态
|
|
|
+ curVideoTime:0,
|
|
|
+
|
|
|
+ showInput:false,//显示悬浮输入弹幕弹窗
|
|
|
+ closeDM:true,//是否关闭弹幕
|
|
|
+ danmuText:'',
|
|
|
+
|
|
|
+ showSpeedOpt:false,
|
|
|
+ speedOpts:['0.5','0.8','1.0','1.25','1.5','2.0'],
|
|
|
+ curSpeed:'1.0',
|
|
|
+
|
|
|
+ keyboardheight:0,//键盘高度
|
|
|
+ isBlur:false,//是否弹幕输入框失焦了
|
|
|
+ isFullScreen:false,//是否为全屏
|
|
|
+ isShowControls:false,//是否显示视频的控制栏
|
|
|
+
|
|
|
+ safeAreaTop:0,
|
|
|
+
|
|
|
+ temdanmuList:this.videoInfo.bullet_chat_list||[],
|
|
|
+ danmuList:[],
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created(){
|
|
|
+ uni.getSystemInfo({
|
|
|
+ success:(res)=>{
|
|
|
+ this.safeAreaTop=res.safeArea.top
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ //点击最外层盒子
|
|
|
+ handleClickWrap(){
|
|
|
+ this.showSpeedOpt=false
|
|
|
+ },
|
|
|
+
|
|
|
+ // 点击播放
|
|
|
+ handelClickPlay(){
|
|
|
+ this.$emit('videoPlay', this.videoInfo)
|
|
|
+ setTimeout(() => {
|
|
|
+ this.curVideoIns=uni.createVideoContext(this.curVideoId.toString(),this)//由于是在自定义组件内 所有this不可少
|
|
|
+ }, 300);
|
|
|
+ },
|
|
|
+ handleVideoPlay(){
|
|
|
+ this.play=true
|
|
|
+ },
|
|
|
+ handleVideoEnd(){
|
|
|
+ // 此处因为如果不调用退出全屏方法 安卓和ios页面均会表现异常,安卓横屏不恢复竖屏,ios底部tabbar渲染异常
|
|
|
+ this.curVideoIns.exitFullScreen()
|
|
|
+ this.curVideoIns=null
|
|
|
+ this.play=false
|
|
|
+ this.danmuList=[]
|
|
|
+ this.temdanmuList.forEach(item=>{
|
|
|
+ item.done=false
|
|
|
+ })
|
|
|
+ this.$emit('ended')
|
|
|
+ },
|
|
|
+ handleVideoPause(){
|
|
|
+ this.play=false
|
|
|
+ this.$emit('pause')
|
|
|
+ },
|
|
|
+ handleTimeUpdate(e){
|
|
|
+ this.addDanmu(e.detail.currentTime)
|
|
|
+ this.curVideoTime=e.detail.currentTime
|
|
|
+ this.$emit('timeupdate',e)
|
|
|
+ },
|
|
|
+ handleFullscreenchange(e){
|
|
|
+ this.isFullScreen=e.detail.fullScreen
|
|
|
+ },
|
|
|
+ handleControlstoggle(e){
|
|
|
+ this.isShowControls=e.detail.show
|
|
|
+ },
|
|
|
+ handleDanmuInputFocus(e){
|
|
|
+ console.log(e.detail.height);
|
|
|
+ this.keyboardheight=e.detail.height
|
|
|
+ },
|
|
|
+ handleDanmuInputBlur:debounce(function(){
|
|
|
+ if(this.showInput){
|
|
|
+ this.showInput=false
|
|
|
+ }
|
|
|
+ },60),
|
|
|
+
|
|
|
+ //切换为背景音频播放
|
|
|
+ handleChangeMusic(){
|
|
|
+ console.log('切换背景音频播放');
|
|
|
+ this.curVideoIns.requestBackgroundPlayback()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 倍速切换
|
|
|
+ handleVideoSpeedChange(item){
|
|
|
+ const num=Number(item)
|
|
|
+ this.curVideoIns.playbackRate(num)
|
|
|
+ this.curSpeed=item
|
|
|
+ this.showSpeedOpt=false
|
|
|
+ },
|
|
|
+
|
|
|
+ //点击视频区域外面的 显示弹幕按钮 进行判断如果当前视频没有播放 则不改变状态
|
|
|
+ handleShowDM(){
|
|
|
+ if(this.videoInfo.id==this.curVideoId){
|
|
|
+ this.closeDM=false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ //发送弹幕
|
|
|
+ handleSendDanmu(){
|
|
|
+ if(!this.danmuText) return
|
|
|
+ apiVideoDanmuSend({
|
|
|
+ content:this.danmuText,
|
|
|
+ seconds:parseInt(this.curVideoTime),
|
|
|
+ primary_id:this.videoInfo.id,
|
|
|
+ source:this.videoInfo.source
|
|
|
+ }).then(res=>{
|
|
|
+ this.danmuText=''
|
|
|
+ if(res.code===200){
|
|
|
+ this.temdanmuList.push({...res.data,seconds:Number(res.data.seconds)+3})
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ //添加弹幕到视频上去
|
|
|
+ addDanmu(ctime){
|
|
|
+ this.temdanmuList.forEach(item => {
|
|
|
+ if(item.seconds>ctime-1&&item.seconds<ctime+1){// 前后误差一秒
|
|
|
+ if(!item.done){
|
|
|
+ item.done=true
|
|
|
+ this.danmuList.push({
|
|
|
+ ...item,
|
|
|
+ top:this.getTopPosition(),
|
|
|
+ speed:Math.floor(Math.random()*(16-8+1))+8//8~16 之间的随机数
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ // 如果播放过了 手贱又把进度条拖回去了 则重置done
|
|
|
+ if(ctime-1<item.seconds){
|
|
|
+ item.done=false
|
|
|
+ }
|
|
|
+ if(ctime+1>item.seconds){
|
|
|
+ item.done=true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ //设置弹幕位置
|
|
|
+ getTopPosition(){
|
|
|
+ const length=this.danmuList.length
|
|
|
+ let num=0
|
|
|
+ if(length%3===1){
|
|
|
+ num=10
|
|
|
+ }else if(length%3===2){
|
|
|
+ num=30
|
|
|
+ }else{
|
|
|
+ num=50
|
|
|
+ }
|
|
|
+ return num+'px'
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.video-wrap{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ video,.poster{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+ .poster{
|
|
|
+ position: relative;
|
|
|
+ &::after{
|
|
|
+ content:'';
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ width: 120rpx;
|
|
|
+ height: 120rpx;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%,-50%);
|
|
|
+ background-image: url('@/static/video-play-btn.png');
|
|
|
+ background-size: cover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .danmu-btn-box{
|
|
|
+ position: absolute;
|
|
|
+ bottom: -70rpx;
|
|
|
+ right: 6rpx;
|
|
|
+ .big-box{
|
|
|
+ width: 248rpx;
|
|
|
+ height: 50rpx;
|
|
|
+ background-image: url('@/static/danmu-show-btn.png');
|
|
|
+ background-size: cover;
|
|
|
+ display: flex;
|
|
|
+ .left{
|
|
|
+ width: 67%;
|
|
|
+ height: 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .right{
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .small-box{
|
|
|
+ width: 80rpx;
|
|
|
+ height: 50rpx;
|
|
|
+ background-image: url('@/static/danmu-close-btn.png');
|
|
|
+ background-size: cover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .danmu-input-box{
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 999999999;
|
|
|
+ background-color: #ffffff;
|
|
|
+ padding: 10rpx 34rpx;
|
|
|
+ border-top: 1px solid #E5E5E5;
|
|
|
+ align-content: center;
|
|
|
+ .input-box{
|
|
|
+ flex:1;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 6rpx 10rpx;
|
|
|
+ background: #EFEFEF;
|
|
|
+ border: 2rpx solid #E5E5E5;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 12px;
|
|
|
+ input{
|
|
|
+ flex:1;
|
|
|
+ }
|
|
|
+ text{
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .btn{
|
|
|
+ width: 100rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #F3A52F;
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .video-inner-right-box{
|
|
|
+ position: absolute;
|
|
|
+ bottom: 30%;
|
|
|
+ right: 5%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ .change-music-icon{
|
|
|
+ width: 40rpx;
|
|
|
+ height: 40rpx;
|
|
|
+ background-image: url('@/static/headphones-icon.png');
|
|
|
+ background-size: cover;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ &.isFullScreen{
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-speed-btn{
|
|
|
+ width: 80rpx;
|
|
|
+ height: 44rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ border-radius: 22rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ .speed-opt-box{
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ width: 20%;
|
|
|
+ background: rgba(0, 0, 0, 0.8);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-around;
|
|
|
+ padding-top: 55rpx;
|
|
|
+ .item{
|
|
|
+ color: #fff;
|
|
|
+ font-size: 26rpx;
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-danmu-control-box{
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+ &.isFullScreen{
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ .send-btn{
|
|
|
+ margin-top: 30rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ view{
|
|
|
+ margin-left: auto;
|
|
|
+ margin-right: auto;
|
|
|
+ }
|
|
|
+ .show-btn{
|
|
|
+ width: 80rpx;
|
|
|
+ height: 50rpx;
|
|
|
+ background-image: url('@/static/danmu-show-btn-2.png');
|
|
|
+ background-size: cover;
|
|
|
+ }
|
|
|
+ .close-btn{
|
|
|
+ width: 80rpx;
|
|
|
+ height: 50rpx;
|
|
|
+ background-image: url('@/static/danmu-close-btn-2.png');
|
|
|
+ background-size: cover;
|
|
|
+ }
|
|
|
+ .send-btn{
|
|
|
+ width: 100rpx;
|
|
|
+ height: 44rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ border-radius: 22rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 20rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .danmu-scroll-box{
|
|
|
+ .danmu-item{
|
|
|
+ color: #fff;
|
|
|
+ animation: move 6s linear;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ display: block;
|
|
|
+ left: 150%;
|
|
|
+ position: absolute;
|
|
|
+ font-size: 12px;
|
|
|
+ height: 18px;
|
|
|
+ white-space: nowrap;
|
|
|
+ background: rgba(48, 48, 48, 0.5);
|
|
|
+ padding-left: 10rpx;
|
|
|
+ padding-right: 10rpx;
|
|
|
+ border-radius: 18px;
|
|
|
+ padding-top: 9px;
|
|
|
+ }
|
|
|
+ .animat-pause{
|
|
|
+ animation-play-state: paused;
|
|
|
+ }
|
|
|
+ .animat-run{
|
|
|
+ animation-play-state: running;
|
|
|
+ }
|
|
|
+ .danmu-item-self{
|
|
|
+ color: #F9AC3A !important;
|
|
|
+ }
|
|
|
+ @keyframes move {
|
|
|
+ 0%{
|
|
|
+ left: 150%;
|
|
|
+ }
|
|
|
+ 100%{
|
|
|
+ left: -200%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|