Ver Fonte

彩虹惠医 新增弹幕

XSLu08042 há 1 mês atrás
pai
commit
e3c7f48c1b
3 ficheiros alterados com 352 adições e 10 exclusões
  1. 3 0
      App.vue
  2. 6 1
      api/course.js
  3. 343 9
      pages_course/video.vue

+ 3 - 0
App.vue

@@ -42,6 +42,9 @@ import { checkLogin } from '@/api/user.js'
 
 
 export default {
+	globalData: {
+		danmuWSUrl:'wss://h5api.his.cdwjyyh.com',
+	},
   onLaunch: function () {
 		this.bindTIMEvent();
 		// wx.CallManager = new CallManager();

+ 6 - 1
api/course.js

@@ -64,4 +64,9 @@ export function getErrMsg(data) {
 // 获取getWxConfig
 export function getWxConfig(data) {
 	return request('/app/wx/mp/getWxConfig', data, 'GET','','https://h5api.his.cdwjyyh.com');
-}
+}
+
+ //获取弹幕列表
+ export function getDanmuList(videoId) {
+ 	 return request('/barrage/barrage/list/'+videoId,null,'GET','','https://h5api.his.cdwjyyh.com');
+ }

+ 343 - 9
pages_course/video.vue

@@ -13,18 +13,30 @@
 			@play="getPlay"
 			@pause="getPause" 
 			@ended="getEnded" 
+			@fullscreenchange="fullscreenchange"
+			@controlstoggle="controlstoggle"
 			:title="courseInfo.title"
 			style="width: 100%;height: 420rpx;" 
 			:poster="poster"  
 			id="video-content-box"  
 			controls
+			:show-fullscreen-btn="true"
 			:auto-pause-if-open-native="true"
 			:auto-pause-if-navigate="true"
 			:enable-progress-gesture="false" 
 			:show-progress="true"
 			:picture-in-picture-mode="[]"
 			:show-background-playback-button="false"
-			:src="videoUrl"></video>
+			:src="videoUrl"
+			:danmu-list="danmuList" 
+			enable-danmu
+			danmu-btn>
+				<cover-view class="video-danmu-btnbox" :style="{display: isfull&&crtShow&&isLogin&&isAddKf==1 ? 'block':'none'}">
+				<cover-image class="video-danmu-image"
+				 src="https://cos.his.cdwjyyh.com/fs/20250418/beaf9df1a6204b8babc3e28d9b563c62.png"
+				 @click="openDanmu(1)"></cover-image>
+				</cover-view>
+			</video>
 		</view>
 		<view class="title-content" id="title-content">
 			<!-- 答题时展示小节课程名,其他展示课程名 -->
@@ -107,6 +119,30 @@
 				</view>
 			</view>
 		</uni-popup>
+		<!-- 发送弹幕 -->
+		<view class="video-line danmu-line" @click="openDanmu(0)" v-if="isLogin&&isAddKf==1">
+			<image class="set_image" src="https://cos.his.cdwjyyh.com/fs/20250418/5e508642737a44169061382566043ac9.png" mode="aspectFill"></image>
+			<text>发弹幕</text>
+		</view>
+		<!-- 发送弹幕弹窗 -->
+		<uni-popup ref="danmuPopup" type="bottom" style="z-index: 999;" @change="changeShowPopup">
+			<view class="danmuPopup" :style="{marginLeft:isfull ? statusBarHeight+'px': 0,marginBottom: danmuboxHeight+'px'}">
+				<view class="danmuPopup-head border-line">
+					<u-input 
+					class="danmuPopup-input" 
+					placeholder="发个弹幕吧~" 
+					border="surround" 
+					shape="circle" 
+					:focus="focus"
+					:adjustPosition="false" 
+					:autoBlur="true" 
+					maxlength="140" 
+					clearable 
+					v-model="danmuIput"></u-input>
+					<button class="danmuPopup-send"  :disabled="danmubtnLoading"  @click="sendDanmu">发送</button>
+				</view>
+			</view>
+		</uni-popup>
 		<!-- 答题弹窗 -->
 		<uni-popup ref="answerPopup" type="center" :show="answerPopup">
 			<view :class="errTitle == '恭喜你,回答正确' ? 'answerPopup-box bg':'answerPopup-box'">
@@ -180,7 +216,8 @@
 		getIntegralByH5Video,
 		sendReward,
 		loginByMp,
-		getRealLink
+		getRealLink,
+		getDanmuList
 	} from "@/api/course.js"
 	export default {
 		data() {
@@ -228,11 +265,11 @@
 				showPlay: true,
 				showControls: false,
 				playStatus: "",
-				isFullscreen: false,
+				isfull: false,
 				isAddKf: 0,
 				lineIndex: 0,
 				// 是否展开
-				isExpand: true,
+				isExpand: false,
 				textHeight: 0, //文本高度
 				qwUserId: "",
 				qrcode: "",
@@ -282,6 +319,19 @@
 				menuButtonH: 45,
 				timer: null,
 				flag: false,
+				danmuList: [],
+				danmuIput: '',
+				focus: false,
+				danmubtnLoading: false,
+				openDanmuType: 0,
+				socket:null,
+				isSocketOpen: false,
+				isSend:true,
+				reOpenSocket: false,
+				pingpangTimes:null,
+				danmuboxHeight: 0,
+				user: {},
+				crtShow: true,
 			}
 		},
 		filters: {
@@ -334,12 +384,20 @@
 			// this.urlOption.linkType=0;
 			this.sortLink = this.urlOption.link || ''
 			this.getMenuButton()
+			// #ifndef H5
+			uni.onKeyboardHeightChange(this.keyboardHeightChange);
+			// #endif
 		},
 		onShow() {
 			this.tipsOpen = false
 			this.isExpand = true
 			// this.isLogin = this.$isLoginCourse()
 			this.uuId = generateRandomString(16)
+			if(uni.getStorageSync('userInfo') && JSON.stringify(uni.getStorageSync('userInfo'))!='{}') {
+				this.user = JSON.parse(uni.getStorageSync('userInfo'))
+			} else {
+				this.user = {}
+			}
 			if (this.videoId) {
 				this.getH5CourseByVideo()
 			}
@@ -371,6 +429,14 @@
 				clearInterval(this.interval)
 				this.interval = null
 			}
+			if(this.socket!=null){
+				this.socket.close()
+				clearInterval(this.pingpangTimes)
+				this.socket = null
+			}
+			// #ifndef H5
+			uni.offKeyboardHeightChange(this.keyboardHeightChange);
+			// #endif
 			this.clearIntegral()
 		},
 		beforeDestroy() {
@@ -383,9 +449,23 @@
 				clearInterval(this.interval)
 				this.interval = null
 			}
+			if(this.socket!=null){
+				this.socket.close()
+				clearInterval(this.pingpangTimes)
+				this.socket = null
+			}
+			// #ifndef H5
+			uni.offKeyboardHeightChange(this.keyboardHeightChange);
+			// #endif
 			this.clearIntegral()
 		},
 		methods: {
+			keyboardHeightChange(res) {
+				// #ifndef H5
+				console.log("this.danmuboxHeight",this.danmuboxHeight)
+				 this.danmuboxHeight = res.height
+				// #endif
+			},
 			getMenuButton(){
 				const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
 				this.menuButtonLeft = menuButtonInfo.left
@@ -433,6 +513,12 @@
 				this.isEnded = true
 				this.getFinishCourseVideo()
 			},
+			fullscreenchange(event) {
+				this.isfull = event.detail.fullScreen
+			},
+			controlstoggle(event) {
+				this.crtShow =  event.detail.show
+			},
 			getIP() {
 				uni.request({
 					url: 'https://ipinfo.io/json', //仅为示例,并非真实接口地址。
@@ -560,6 +646,17 @@
 									this.player.seek(this.playTime)
 									this.player.play();
 								},500);
+								this.getDanmuList()
+								if (this.socket) {
+									this.socket.close({
+										success:()=>{
+											this.reOpenSocket = true
+											clearInterval(this.pingpangTimes)
+										}
+									})
+								} else {
+									this.initSocket()
+								}
 							} else {
 								// let div = document.querySelector(".vjs-progress-control");
 								// if(div) {
@@ -906,6 +1003,7 @@
 										 if (res.code == 200) {
 											uni.setStorageSync('AppTokenmini_RTCourse', res.token);
 											uni.setStorageSync('userInfo', JSON.stringify(res.user));
+											this.user = res.user
 											this.isLogin = true
 											this.getIsAddKf() 
 										 } else {
@@ -991,6 +1089,174 @@
 			            typeof func === 'function' && func()
 			        }, wait)
 			    }
+			},
+			// 弹幕
+			openDanmu(type) {
+				this.openDanmuType = type
+				this.danmuIput= ''
+				if(type == 1) {
+					this.player.exitFullScreen()
+				}
+				this.$refs.danmuPopup.open()
+			},
+			changeShowPopup(val) {
+				this.focus = val.show
+			},
+			// 发送弹幕
+			sendDanmu() {
+				if(this.danmuIput==''||this.danmuIput.trim()=='') {
+					uni.showToast({
+						title: '弹幕不能为空',
+						icon: 'none'
+					})
+					return;
+				}
+				this.sendMsg()
+			},
+			// 弹幕列表
+			getDanmuList(){
+				getDanmuList(this.videoId).then(res=>{
+					if(res.code == 200&&res.data&&res.data.length>0) {
+						this.danmuList = res.data.map(item=>({
+							id: item.id,
+							text: item.content,
+							time: item.timePoint ? Number(item.timePoint) : this.playTime,
+							color: "#FFFFFF",
+						}))
+					} else {
+						this.danmuList = []
+					}
+				})
+			},
+			//创建一个socket连接
+			initSocket() {
+				let userId = this.user.userId;
+				let that = this;
+				this.socket = uni.connectSocket({
+					url: getApp().globalData.danmuWSUrl + "/ws/barrage/" + this.videoId,
+					multiple: true,
+					header: {
+						'token': uni.getStorageSync('AppTokenmini_RTCourse')
+					},
+					success: res => {
+						console.log('WebSocket连接已打开1!');
+						that.isSocketOpen = true
+						that.reOpenSocket = false
+						
+						// 保持心跳
+						if(that.pingpangTimes) {
+							clearInterval(that.pingpangTimes)
+							that.pingpangTimes= null
+						}
+						that.pingpangTimes=setInterval(()=>{
+							let data={cmd:"heartbeat",userId: userId};
+							that.socket.send({
+								data: JSON.stringify(data),
+								success: () => {
+									// console.log('WebSocket发送心条数据!');
+								},
+								fail: () => {
+									that.isSocketOpen=false
+								}
+							});
+						},15000)
+					},
+					error: res => {
+						console.log(res)
+					},
+				})
+				this.socket.onMessage((res) => {
+					console.log("收到消息parse",JSON.parse(res.data))
+					const redata = JSON.parse(res.data);
+					if(redata.cmd=="heartbeat"){
+						  //心跳
+						  // console.log("heartbeat")
+					}else if(redata.cmd=="danmu"){
+						that.isSend=true;
+						that.addMsg(1,redata);
+					}
+				})
+				//监听socket打开
+				this.socket.onOpen(() => {
+					console.log('WebSocket连接已打开2!');
+					that.isSocketOpen = true
+					that.reOpenSocket = false
+					that.isSend = true;
+				})
+				//监听socket关闭
+				this.socket.onClose(() => {
+					that.isSocketOpen = false
+					that.socket = null
+					console.log('WebSocket连接已关闭!',that.reOpenSocket);
+					if(that.pingpangTimes) {
+						clearInterval(that.pingpangTimes)
+						that.pingpangTimes= null
+					}
+					if(that.reOpenSocket) {
+						//重启
+						that.initSocket()
+					}
+					// that.msgEnd = true
+				})
+				//监听socket错误
+				this.socket.onError((err) => {
+					console.log("socket err:",err)
+					that.isSocketOpen = false
+					that.reOpenSocket = false
+					that.socket = null
+					if(that.pingpangTimes) {
+						clearInterval(that.pingpangTimes)
+						that.pingpangTimes= null
+					}
+				})
+			},
+			sendMsg() {
+				if (!this.isSend) {
+					return;
+				}
+				if (this.isSocketOpen) {
+					var data = {
+						cmd: 'danmu',
+						userId: this.user.userId,
+						videoId: this.videoId,
+						content: this.danmuIput,
+						timePoint: this.playTime, // 弹幕对应视频时间节点()秒
+						platform: 'uniapp',  //发送平台,app传值“app”,小程序传值“uniapp”
+						fontSize: '14px',
+						mode: "scroll",
+						color: "#fff",
+					};
+					this.socket.send({
+						data: JSON.stringify(data),
+						success: () => {
+							console.log("发送成功")
+							this.$refs.danmuPopup.close()
+							this.isSend = false;
+						},
+						fail: () => {
+							console.log("发送失败")
+							uni.showToast({
+								title: '发送失败',
+								icon: 'none'
+							})
+						}
+					});
+			
+				}
+			
+			},
+			// 收到消息
+			addMsg(type, content) {
+				if (!this.player) {
+					this.player = uni.createVideoContext('video-content-box');
+				}
+				setTimeout(()=>{
+					this.player.sendDanmu({
+						text: content.content,
+						color: "#FF0000",
+						time: this.playTime
+					})
+				},100)
 			}
 		}
 	}
@@ -1595,12 +1861,19 @@
 
 			image {
 				flex-shrink: 0;
-				height: 30rpx;
-				width: 30rpx;
+				height: 34rpx;
+				width: 34rpx;
 				margin-right: 6rpx;
 			}
 		}
-
+		.danmu-line {
+			bottom: calc(var(--window-bottom) + 370rpx);
+			word-break: keep-all;
+			.set_image {
+				height: 40rpx;
+				width: 40rpx;
+			}
+		}
 		.footer {
 			border-top: 1rpx solid #ededef;
 			background: #fff;
@@ -1662,7 +1935,68 @@
 		align-items: center;
 		justify-content: center;
 	}
-	 
- 
+	.video-danmu-btnbox {
+		width: 50px;
+		height: 50px;
+		border-radius: 50%;
+		overflow: hidden;
+		position: absolute;
+		right: 10px;
+		bottom: calc(50% - 50px);
+		transform: translateY(-50%);
+		padding: 8px;
+		box-sizing: border-box;
+	}
+	.video-danmu-image {
+		width: 100%;
+		height: 100%;
+	}
+	.danmuPopup {
+		background-color: #fff;
+		padding-bottom: var(--window-bottom);
+		&-head {
+			width: 100%;
+			padding: 10px;
+			box-sizing: border-box;
+			overflow: hidden;
+			@include u-flex(row,center,flex-start);
+			.danmu-icon {
+				height: 24px;
+				width: 24px;
+				margin-right: 12px;
+			}
+		}
+		&-input {
+			flex: 1;
+			height: 35px;
+		}
+		&-send {
+			flex-shrink: 0;
+			height: 35px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			padding: 5px 15px;
+			box-sizing: border-box;
+			background: #FF5C03 !important;
+			border-radius: 22px;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 500;
+			font-size: 15px;
+			color: #fff !important;
+			margin-left: 12px;
+			&::after {
+				border: none;
+			}
+		}
+		&-con {
+			background-color: #F5F7FA;
+			padding: 24px 12px 48px 12px;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 14px;
+			color: #757575;
+		}
+	}
 	 
 </style>