浏览代码

Merge branch 'master' of http://git.dayaedu.com/lex/dy-admin-live

lex-xin 3 年之前
父节点
当前提交
8c01615e31

+ 28 - 31
src/components/live-broadcast/action-bar.tsx

@@ -1,45 +1,42 @@
-import { defineComponent } from 'vue'
+import { defineComponent, reactive } from 'vue'
 import { ElButton, ElDropdown, ElDropdownMenu, ElDropdownItem, ElSlider, ElDialog, ElIcon } from 'element-plus'
 import runtime, * as RuntimeUtils from './runtime'
 import styles from './action-bar.module.less'
 import Share from './share'
+
+export const state = reactive({
+  volume: 30,
+  barStatus: {
+    camera: false, // 摄像头
+    volume: false, // 声音调节
+    microphone: false, // 麦克风
+    screen: false, // 共享屏幕
+    share: false, // 分享
+  },
+  shareVisiable: false
+})
+
 export default defineComponent({
   name: 'LiveBroadcast-ActionBar',
-  data() {
-    return {
-      volume: 30,
-      barStatus: {
-        camera: false, // 摄像头
-        volume: false, // 声音调节
-        microphone: false, // 麦克风
-        screen: false, // 共享屏幕
-        share: false, // 分享
-      },
-      shareVisiable: false
-    }
-  },
   computed: {
     isCameraDisabled() {
-      // @ts-ignore
-      return this.barStatus.camera && runtime.deviceStatus.camera !== 'denied' && runtime.cameras.length
+      return state.barStatus.camera && runtime.deviceStatus.camera !== 'denied' && runtime.cameras.length
     },
     isMicrophoneDisabled() {
-      // @ts-ignore
-      const isDisabled = this.barStatus.microphone && runtime.deviceStatus.microphone !== 'denied' && runtime.microphones.length
+      const isDisabled = state.barStatus.microphone && runtime.deviceStatus.microphone !== 'denied' && runtime.microphones.length
       return isDisabled
     },
     isVolumeDisabled() {
-      // @ts-ignore
-      return this.barStatus.volume && this.volume === 0
+      return state.volume === 0
     }
   },
   methods: {
     startShare() {
       console.log('调用')
-      this.shareVisiable = true
+      state.shareVisiable = true
     },
     volumeChange(value: number) {
-      this.volume = value
+      state.volume = value
       RuntimeUtils.setVolume(value)
     }
   },
@@ -51,7 +48,7 @@ export default defineComponent({
             <div class={styles.btnInner}>
               <SvgIcon
                 onClick={() => {
-                  this.barStatus.camera = !this.barStatus.camera
+                  state.barStatus.camera = !state.barStatus.camera
                   RuntimeUtils.toggleDevice('camera')
                 }}
                 name={this.isCameraDisabled ? 'bar-camera-disabled' : 'bar-camera'}
@@ -90,11 +87,11 @@ export default defineComponent({
             <div class={styles.btnInner}>
               <SvgIcon
                 onClick={() => {
-                  this.barStatus.volume = !this.barStatus.volume;
-                  if(!this.barStatus.volume) {
+                  state.barStatus.volume = !state.barStatus.volume;
+                  if(!state.barStatus.volume) {
                     sessionStorage.getItem('volume') && this.volumeChange(Number(sessionStorage.getItem('volume')))
                   } else {
-                    sessionStorage.setItem('volume', this.volume.toString())
+                    sessionStorage.setItem('volume', state.volume.toString())
                     this.volumeChange(0)
                   }
                 }}
@@ -112,7 +109,7 @@ export default defineComponent({
                   dropdown: () => (
                     <div class={styles.volumeSlider}>
                       <SvgIcon class={styles.volumeIcon} name="message-voice" color="#fff" />
-                      <ElSlider modelValue={this.volume} onInput={this.volumeChange} size="small" />
+                      <ElSlider modelValue={state.volume} onInput={this.volumeChange} size="small" />
                     </div>
                   )
                 }}
@@ -160,8 +157,8 @@ export default defineComponent({
               <SvgIcon
                 onClick={() => {
                   const needPublish = runtime.videoStatus === 'liveing'
-                  this.barStatus.microphone = !this.barStatus.microphone
-                  if (!this.barStatus.microphone) {
+                  state.barStatus.microphone = !state.barStatus.microphone
+                  if (!state.barStatus.microphone) {
                     RuntimeUtils.openDevice('microphone', needPublish)
                   } else {
                     RuntimeUtils.closeDevice('microphone', needPublish)
@@ -219,8 +216,8 @@ export default defineComponent({
         {/* <ElButton onClick={RuntimeUtils.shareScreenVideo}>屏幕共享</ElButton> */}
         <ElDialog width="510px"
         destroy-on-close
-          append-to-body modelValue={this.shareVisiable} title="分享" before-close={() => { this.shareVisiable = false }}>
-            <Share onClose={()=>this.shareVisiable = false}/>
+          append-to-body modelValue={state.shareVisiable} title="分享" before-close={() => { state.shareVisiable = false }}>
+            <Share onClose={()=>state.shareVisiable = false}/>
         </ElDialog>
       </div>
     )

+ 5 - 3
src/components/live-broadcast/groupChat.tsx

@@ -1,5 +1,5 @@
 import { defineComponent, ref } from 'vue'
-import { ElButton, ElFormItem, ElForm, ElRadio, ElMessage, ElCheckboxGroup, ElCheckbox, ElInput } from 'element-plus'
+import { ElButton, ElFormItem, ElForm, ElRadio, ElMessage, ElCheckboxGroup, ElCheckbox, ElInput,ElEmpty } from 'element-plus'
 import { state } from '/src/state'
 import request from "/src/helpers/request";
 import { removeMedia } from './helpers'
@@ -68,7 +68,7 @@ export default defineComponent({
                         <ElButton type="danger" onClick={this.onReSet}>重置</ElButton>
                     </ElFormItem>
                 </ElForm>
-                <div class={styles.tableWrap}>
+                { this.tableList.length >0?        <div class={styles.tableWrap}>
                     <ElCheckboxGroup modelValue={this.checkList}>
                         {this.tableList.map((item: any) => {
                             return <div class={styles.cell} onClick={() => this.resectCheck(item.id)}>
@@ -83,7 +83,9 @@ export default defineComponent({
                         })}
 
                     </ElCheckboxGroup>
-                </div>
+                </div>:<ElEmpty description={'暂无群聊'}/>}
+         
+                
             </div>
         )
     }

+ 44 - 13
src/components/live-broadcast/index.tsx

@@ -1,13 +1,13 @@
 import { defineComponent, ref } from 'vue'
 import * as RTC from '@rongcloud/plugin-rtc'
 import Header from './header'
-import ActionBar from './action-bar'
+import ActionBar, { state as ActionBarRuntime} from './action-bar'
 import VideoStatus from './video-status'
 import { state } from '/src/state'
 import event, { LIVE_EVENT_MESSAGE } from './event'
 import runtime, * as RuntimeUtils from './runtime'
 import Chronography from './chronography'
-import { removeMedia } from './helpers'
+// import { removeMedia } from './helpers'
 import styles from './index.module.less'
 
 const videoRef = ref<HTMLVideoElement | null>(null)
@@ -21,29 +21,54 @@ export default defineComponent({
     this.initializeRoom()
     RuntimeUtils.loopSyncLike()
     event.on(LIVE_EVENT_MESSAGE['RC:Chatroom:Like'], this.onLikeMessage)
+    window.onbeforeunload = this.beforeunload
   },
   beforeUnmount() {
     event.off(LIVE_EVENT_MESSAGE['RC:Chatroom:Like'], this.onLikeMessage)
+    window.onbeforeunload = null
   },
   methods: {
+    beforeunload() {
+      if (runtime.videoStatus === 'liveing') {
+        return '当前正在直播中是否确认关闭页面?'
+      }
+    },
     onLikeMessage(msg: any) {
       runtime.likeCount += msg.counts
     },
+    getDeviceByDeviceType(type: RuntimeUtils.TrackType) {
+      const videoDeviceId = localStorage.getItem(RuntimeUtils.VIDEO_DEVICE_ID)
+      const audioDeviceId = localStorage.getItem(RuntimeUtils.AUDIO_DEVICE_ID)
+      if (type === 'camera') {
+        if (videoDeviceId) {
+          return runtime.cameras.find(camera => camera.deviceId === videoDeviceId) || runtime.cameras[0]
+        }
+        return runtime.cameras[0]
+      }
+      if (audioDeviceId) {
+        return runtime.microphones.find(microphone => microphone.deviceId === audioDeviceId) || runtime.microphones[0]
+      }
+      return runtime.microphones[0]
+    },
     async initializeRoom () {
       if (!state.user) throw Error('请先登录')
       try {
+        // IM连接
         await RuntimeUtils.connectIM(state.user?.imToken)
         runtime.videoRef = videoRef.value
+        // 获取设备
         await RuntimeUtils.getMicrophones()
         await RuntimeUtils.getCameras()
-        RuntimeUtils.setSelectCamera(runtime.cameras[0])
-        RuntimeUtils.setSelectMicrophone(runtime.microphones[0])
+        // 设置播放设备
+        RuntimeUtils.setSelectCamera(this.getDeviceByDeviceType('camera'))
+        RuntimeUtils.setSelectMicrophone(this.getDeviceByDeviceType('microphone'))
         cameraVideoTrack = await RuntimeUtils.getTrack('camera')
         runtime.videoRef && cameraVideoTrack.play(runtime.videoRef)
-        await RuntimeUtils.setTrack([cameraVideoTrack], 'camera')
-        microphoneAudioTrack = await RuntimeUtils.getTrack('microphone')
-        runtime.videoRef && microphoneAudioTrack.play(runtime.videoRef)
-        console.log(runtime.deviceStatus)
+        await RuntimeUtils.setTrack([cameraVideoTrack], 'camera', false)
+        // microphoneAudioTrack = await RuntimeUtils.getTrack('microphone')
+        // microphoneAudioTrack.play()
+        // console.log(microphoneAudioTrack)
+        // console.log(runtime.deviceStatus)
         runtime.videoStatus = 'stream'
         const join = await RuntimeUtils.joinRoom(state.user?.roomUid, RTC.RCLivingType.VIDEO, {
           onMessageReceive(name, content) {
@@ -83,20 +108,26 @@ export default defineComponent({
             console.log('onUserLeave', userIds)
           },
         })
-        if (sessionStorage.getItem(RuntimeUtils.START_LIVE_STATUS) === 'liveing') {
-          await RuntimeUtils.startLive()
-        }
         if (join.room && join.code === RTC.RCRTCCode.SUCCESS) {
           runtime.joinedRoom = join.room
         }
+        if (sessionStorage.getItem(RuntimeUtils.START_LIVE_STATUS) === 'liveing') {
+          await RuntimeUtils.startLive(false)
+          runtime.videoStatus = 'liveing'
+        }
+        const volume =localStorage.getItem(RuntimeUtils.AUDIO_DEVICE_VOLUME)
+        if (volume) {
+          ActionBarRuntime.volume = parseInt(volume)
+          RuntimeUtils.setVolume(parseInt(volume))
+        }
       } catch (error) {
         runtime.videoStatus = 'error'
         console.log(error)
       }
     },
     closeLive() {
-      removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
-      runtime.videoStatus = 'stopped'
+      // removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
+      runtime.videoStatus = 'stream'
     },
   },
   render() {

+ 48 - 18
src/components/live-broadcast/runtime.ts

@@ -11,7 +11,7 @@ type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
 
 type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
 
-type TrackType = 'microphone' | 'camera' | 'screen'
+export type TrackType = 'microphone' | 'camera' | 'screen'
 
 type ActiveTracks = {
   [key in TrackType]: RTC.RCLocalTrack | null
@@ -25,6 +25,12 @@ export const START_LIVE_TIME = 'start-live-time'
 
 export const START_LIVE_STATUS = 'start-live-status'
 
+export const VIDEO_DEVICE_ID = 'video-deviceId'
+
+export const AUDIO_DEVICE_ID = 'audio-deviceId'
+
+export const AUDIO_DEVICE_VOLUME = 'audio-device-volume'
+
 const runtime = reactive({
   /** 房间id */
   roomUid: 'LIVE-2112263-12345',
@@ -162,9 +168,15 @@ const Events = RongIMLib.Events
  * @param Value 声音大小
  */
 export const setVolume = (value: number) => {
+  localStorage.setItem(AUDIO_DEVICE_VOLUME, value.toString())
   if(runtime.videoRef) {
     runtime.videoRef.volume = value / 100
   }
+  // @ts-ignore
+  if (runtime.activeTracks.microphone && runtime.activeTracks.microphone._element) {
+    // @ts-ignore
+    runtime.activeTracks.microphone._element.volume = value / 100
+  }
 }
 
 /**
@@ -184,7 +196,7 @@ export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams:
  * 发起屏幕共享
  */
 export const shareScreenVideo = async () => {
-  if (runtime.rtcClient) {
+  if (runtime.rtcClient && !runtime.screenShareStatus) {
     const screenTrack = await getTrack('screen')
     const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack
     // removeTrack([oldTrack], 'camera')
@@ -239,6 +251,7 @@ export const getCameras = async () => {
  */
 export const setSelectCamera = async (camera: MediaDeviceInfo) => {
   runtime.selectedCamera = camera
+  localStorage.setItem(VIDEO_DEVICE_ID, camera.deviceId)
   const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack
   if (oldTrack) {
     await removeTrack([oldTrack], 'camera', oldTrack.isPublished())
@@ -254,6 +267,7 @@ export const setSelectCamera = async (camera: MediaDeviceInfo) => {
  */
 export const setSelectMicrophone = async (microphone: MediaDeviceInfo) => {
   runtime.selectedMicrophone = microphone
+  localStorage.setItem(AUDIO_DEVICE_ID, microphone.deviceId)
   const oldTrack = runtime.activeTracks.microphone as RTC.RCLocalTrack
   if (oldTrack) {
     await removeTrack([oldTrack], 'microphone', oldTrack.isPublished())
@@ -312,9 +326,14 @@ export const setTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType,
   for (const track of tracks) {
     // @ts-ignore
     // await runtime.mediaStreams?.addTrack(track._msTrack)
+    if (trackType === 'microphone') {
+      console.log('添加麦克风')
+      track.play()
+    }
     runtime.activeTracks[trackType] = track
   }
   if (needPublish) {
+    // console.log('publish', runtime.joinedRoom)
     await runtime.joinedRoom?.publish(tracks)
   }
 }
@@ -323,17 +342,17 @@ export const setTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType,
  * @param track
  */
 export const removeTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType, needPublish = true) => {
+  if (needPublish) {
+    await runtime.joinedRoom?.unpublish(tracks)
+  }
   for (const track of tracks) {
     // @ts-ignore
     // await runtime.mediaStreams?.removeTrack(track._msTrack)
     // runtime.activeTracks[trackType].destroy()
     // console.log(runtime.activeTracks[trackType])
-    track.destroy()
+    track?.destroy()
     runtime.activeTracks[trackType] = null
   }
-  if (needPublish) {
-    await runtime.joinedRoom?.unpublish(tracks)
-  }
 }
 
 export const joinIMRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
@@ -360,22 +379,24 @@ export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEve
  * 开始直播
  */
 
-export const startLive = async () => {
+export const startLive = async (resetTime = true) => {
   if (runtime.videoStatus !== 'stream') throw Error('当前无视频流')
   const room = runtime.joinedRoom
   if (room) {
-    const microphoneAudioTrack = await getTrack('microphone')
-    const cameraVideoTrack = await getTrack('camera')
-    await setTrack([cameraVideoTrack], 'camera')
-    await setTrack([microphoneAudioTrack], 'microphone')
-    const builder = await runtime.joinedRoom?.getMCUConfigBuilder()
-    // @ts-ignore
-    await builder.setOutputVideoRenderMode?.(RTC.MixVideoRenderMode.WHOLE)
-    // @ts-ignore
-    await builder.flush()
+    // const microphoneAudioTrack = await getTrack('microphone')
+    // const cameraVideoTrack = await getTrack('camera')
+    // await setTrack([cameraVideoTrack], 'camera')
+    // await setTrack([microphoneAudioTrack], 'microphone')
+    // const builder = await runtime.joinedRoom?.getMCUConfigBuilder()
+    // // @ts-ignore
+    // await builder.setOutputVideoRenderMode?.(RTC.MixVideoRenderMode.WHOLE)
+    // // @ts-ignore
+    // await builder.flush()
     runtime.videoStatus = 'liveing'
   }
-  sessionStorage.setItem(START_LIVE_TIME, dayjs().valueOf().toString())
+  if (resetTime) {
+    sessionStorage.setItem(START_LIVE_TIME, dayjs().valueOf().toString())
+  }
   sessionStorage.setItem(START_LIVE_STATUS, 'liveing')
 }
 
@@ -484,8 +505,9 @@ export const openDevice = async (trackType: TrackType, needPublish = true) => {
 export const closeDevice = async (trackType: TrackType, needPublish = true) => {
   const track = runtime.activeTracks[trackType]
   if (trackType !== 'microphone') {
+    // console.log('closeDevice', track)
+    // track?.destroy()
     await removeTrack([track] as RTC.RCLocalTrack[], trackType, needPublish)
-    track?.destroy()
   } else {
     track?.mute()
   }
@@ -507,3 +529,11 @@ export const toggleDevice = async (trackType: TrackType) => {
   }
 }
 
+export const leaveIMRoom = async () => {
+  await closeLive()
+  if (runtime.joinedRoom) {
+    // @ts-ignore
+    await runtime.rtcClient?.leaveRoom(runtime.joinedRoom)
+    runtime.joinedRoom = null
+  }
+}

+ 8 - 17
src/components/live-broadcast/share.tsx

@@ -8,6 +8,7 @@ import { toPng } from "html-to-image";
 import copy from "copy-to-clipboard";
 import GroupChat from './groupChat'
 import Preview from './preview'
+import { state } from '/src/state'
 const GroupChatRef: Ref<DefineComponent<{}, {}, any> | null> = ref(null)
 export default defineComponent({
     name: 'LiveBroadcastShare',
@@ -24,25 +25,14 @@ export default defineComponent({
                 roomTitle: '',
                 liveStartTime: '',
                 liveRemark: '',
-                roomUid: ''
+                roomUid: '',
+                speakerName:''
             },
             url: ''
         }
     },
     async mounted() {
-        let details: any = JSON.parse(sessionStorage.getItem('details') || '')
-        if (!details) {
-            const roomUid = sessionStorage.getItem('roomUid')
-            const details: any = await request.get('/api-web/imLiveBroadcastRoom/queryRoom', {
-                params: {
-                    roomUid: roomUid
-                }
-            })
-            sessionStorage.setItem('details', details.data)
-            this.detail = { ...details.data }
-        } else {
-            this.detail = { ...details }
-        }
+        this.detail = {...state.user}
         this.url = vaildStudentUrl() + `/#/liveClassTransfer?roomUid=${this.detail.roomUid}`;
     },
     methods: {
@@ -121,9 +111,10 @@ export default defineComponent({
                         <div class={styles.shareWrap}>
                             <h2>乐团老师邀请您参与直播课!</h2>
                             <h4>{this.detail.roomTitle}</h4>
-                            <p>直播时间:{this.detail.liveStartTime}</p>
-                            <p>直播内容:{this.detail.liveRemark}</p>
-                            <p>直播地址:{this.url}</p>
+                            <p>主讲人:{this.detail.speakerName}</p>
+                            <p>开播时间:{this.detail.liveStartTime}</p>
+                            <p>直播内容:{this.detail.liveRemark}</p>
+                            <p>直播地址:{this.url}</p>
                         </div>
                         <div class={styles.shareBtn} onClick={this.copyText}>复制分享内容</div>
                     </div> : null}

+ 1 - 0
src/pages/home/header/index.tsx

@@ -15,6 +15,7 @@ export default defineComponent({
         await request.post('/api-auth/exit', { data: {} });
         RuntimeUtils.closeDevice('camera')
         RuntimeUtils.closeDevice('microphone')
+        await RuntimeUtils.leaveIMRoom()
         state.user = null
         ElMessage.success('退出成功');
         removeToken();