Selaa lähdekoodia

添加计时与调整部分播放问题

wolyshaw 3 vuotta sitten
vanhempi
commit
c2494158f6

+ 5 - 0
package-lock.json

@@ -3543,6 +3543,11 @@
         }
       }
     },
+    "loaders.css": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/loaders.css/-/loaders.css-0.1.2.tgz",
+      "integrity": "sha1-Op+0NybHMzSjgUKvnQYpAZtlh0M="
+    },
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "element-plus": "^2.0.2",
     "html-to-image": "^1.9.0",
     "js-cookie": "^3.0.1",
+    "loaders.css": "^0.1.2",
     "mitt": "^3.0.0",
     "nprogress": "^0.2.0",
     "query-string": "^7.1.1",

+ 3 - 0
src/base.css

@@ -1,6 +1,7 @@
 /* 基础颜色值 */
 :root {
   --el-color-primary: #01A79E;
+  --white: white
 }
 body{
   --live-backound-color: #25292E;
@@ -12,4 +13,6 @@ body{
   --live-text-color: #00D6C9;
   --tips-backound-color: #32363B;
   --tips-color: #A4A6A9;
+  --live-time-background-color: rgba(0, 0, 0, 0.32);
+  --live-time-status-background-color: #EA4132;
 }

+ 32 - 0
src/components/live-broadcast/chronography.module.less

@@ -0,0 +1,32 @@
+.time{
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  display: flex;
+  color: var(--white);
+  border-radius: 4px;
+  overflow: hidden;
+  height: 36px;
+  line-height: 36px;
+  box-sizing: border-box;
+  font-size: 18px;
+  .status{
+    display: flex;
+    align-items: center;
+    background-color: var(--live-time-status-background-color);
+    padding: 10px;
+    >div{
+      margin-right: 5px;
+      height: 20px;
+      >div{
+        height: 20px;
+      }
+    }
+  }
+  .text{
+    background-color: var(--live-time-background-color);
+    display: flex;
+    align-items: center;
+    padding: 10px;
+  }
+}

+ 45 - 0
src/components/live-broadcast/chronography.tsx

@@ -0,0 +1,45 @@
+import {  defineComponent, onBeforeUnmount, onMounted, reactive } from 'vue'
+import dayjs from 'dayjs'
+import duration from 'dayjs/plugin/duration'
+import 'loaders.css/loaders.min.css'
+import runtime, { START_LIVE_TIME } from '../../components/live-broadcast/runtime'
+import styles from './chronography.module.less'
+
+dayjs.extend(duration)
+
+export default defineComponent({
+  setup() {
+    const data = reactive({
+      duration: ''
+    })
+    const starttimestr = sessionStorage.getItem(START_LIVE_TIME)
+    const starttime = Number(starttimestr)
+    let timer: NodeJS.Timer | null = null
+    onMounted(() => {
+      timer = setInterval(() => {
+        const nowtime = dayjs().valueOf()
+        if (starttime && nowtime - starttime) {
+          data.duration = dayjs.duration((nowtime - starttime)).format('HH:mm:ss')
+        }
+      })
+    })
+    onBeforeUnmount(() => {
+      if (timer) {
+        clearInterval(timer)
+      }
+    })
+    return () => runtime.videoStatus === 'liveing' && starttime ? (
+      <div class={styles.time}>
+        <div class={styles.status}>
+          <div class="line-scale-pulse-out">
+            <div></div>
+            <div></div>
+            <div></div>
+          </div>
+          <span>直播中</span>
+        </div>
+        <span class={styles.text}>{data.duration}</span>
+      </div>
+    ) : null
+  }
+})

+ 10 - 8
src/components/live-broadcast/header.tsx

@@ -16,7 +16,9 @@ export default defineComponent({
           type: 'warning',
         })
         await RuntimeUtils.startLive()
-      } catch (error) {}
+      } catch (error) {
+        console.log(error)
+      }
     },
     async closeLive() {
       try {
@@ -25,13 +27,13 @@ export default defineComponent({
           cancelButtonText: '取消',
           type: 'warning',
         })
-        await request.post('/api-im/user/statusImUser', {
-          data: {
-            os: 'IOS',
-            status: '2',
-            userid: state.user?.id
-          }
-        })
+        // await request.post('/api-im/user/statusImUser', {
+        //   data: {
+        //     os: 'PC',
+        //     status: '3',
+        //     userId: state.user?.speakerId
+        //   }
+        // })
         await RuntimeUtils.closeLive()
       } catch (error) {}
     }

+ 3 - 1
src/components/live-broadcast/index.tsx

@@ -6,6 +6,7 @@ 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 styles from './index.module.less'
 
@@ -101,7 +102,8 @@ export default defineComponent({
         <Header/>
         <div class={styles.video}>
           <video ref={videoRef}></video>
-          <VideoStatus/>
+          {!runtime.screenShareStatus ? <VideoStatus/> : null}
+          {runtime.videoStatus === 'liveing' ? <Chronography/> : null}
         </div>
         <ActionBar/>
         {/* <div>video: {runtime.videoStatus}, imStatus: {runtime.imConnectStatus}</div> */}

+ 59 - 19
src/components/live-broadcast/runtime.ts

@@ -4,7 +4,7 @@ import * as RTC from '@rongcloud/plugin-rtc'
 import request from '/src/helpers/request'
 import { state } from '/src/state'
 import event, { LIVE_EVENT_MESSAGE } from './event'
-import { removeMedia } from './helpers'
+import dayjs from 'dayjs'
 // import { SeatsCtrl } from './message-type'
 
 type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
@@ -18,9 +18,11 @@ type ActiveTracks = {
 }
 
 type DeviceStatus = {
-  [key in TrackType]: 'init' | 'granted' | 'denied'
+  [key in TrackType]: 'init' | 'granted' | 'denied' | 'closed' | 'none'
 }
 
+export const START_LIVE_TIME = 'start-live-time'
+
 const runtime = reactive({
   /** 房间id */
   roomUid: 'LIVE-2112263-12345',
@@ -188,14 +190,17 @@ export const shareScreenVideo = async () => {
     setTrack([screenTrack as RTC.RCLocalTrack], 'screen')
     if (runtime.videoRef) {
       screenTrack.play(runtime.videoRef)
+      runtime.screenShareStatus = true
     }
     screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => {
       runtime.screenShareStatus = false
       track.destroy()
       // removeTrack([track], 'screen')
-      setTrack([oldTrack as RTC.RCLocalTrack], 'camera')
-      if (runtime.videoRef) {
-        oldTrack.play(runtime.videoRef)
+      if (oldTrack) {
+        setTrack([oldTrack as RTC.RCLocalTrack], 'camera')
+        if (runtime.videoRef) {
+          oldTrack.play(runtime.videoRef)
+        }
       }
       // setVideoSrcObject(runtime.videoRef, this.mediaStreams)
     })
@@ -230,8 +235,14 @@ export const getCameras = async () => {
  * 设置当前视频设备
  * @param camera MediaDeviceInfo
  */
-export const setSelectCamera = (camera: MediaDeviceInfo) => {
+export const setSelectCamera = async (camera: MediaDeviceInfo) => {
   runtime.selectedCamera = camera
+  const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack
+  if (oldTrack) {
+    await removeTrack([oldTrack], 'camera', oldTrack.isPublished())
+  }
+  const track = await getTrack('camera')
+  setTrack([track], 'camera', runtime.videoStatus === 'liveing')
 }
 
 /**
@@ -239,8 +250,14 @@ export const setSelectCamera = (camera: MediaDeviceInfo) => {
  * 设置当前麦克风设备
  * @param microphone MediaDeviceInfo
  */
-export const setSelectMicrophone = (microphone: MediaDeviceInfo) => {
+export const setSelectMicrophone = async (microphone: MediaDeviceInfo) => {
   runtime.selectedMicrophone = microphone
+  const oldTrack = runtime.activeTracks.microphone as RTC.RCLocalTrack
+  if (oldTrack) {
+    await removeTrack([oldTrack], 'microphone', oldTrack.isPublished())
+  }
+  const track = await getTrack('microphone')
+  setTrack([track], 'microphone', runtime.videoStatus === 'liveing')
 }
 
 type TrackResult = {
@@ -259,14 +276,21 @@ export const getTrack = async (trackType: TrackType): Promise<RTC.RCLocalTrack>
     res = await runtime.rtcClient?.createCameraVideoTrack('RongCloudRTC', {
       cameraId: runtime.selectedCamera?.deviceId,
       faceMode: 'user',
-      frameRate: RTC.RCFrameRate.FPS_15,
-      resolution: RTC.RCResolution.W1280_H720,
+      frameRate: RTC.RCFrameRate.FPS_24,
+      resolution: RTC.RCResolution.W1920_H1080,
     }) as TrackResult
   } else {
     res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult
   }
 
   Track = res?.track as RTC.RCLocalTrack
+  if (trackType === 'camera' && !runtime.cameras.length) {
+    runtime.deviceStatus[trackType] = 'none'
+  } else if (trackType === 'microphone' && !runtime.microphones.length) {
+    runtime.deviceStatus[trackType] = 'none'
+  } else if (trackType === 'screen' && !runtime.screenShareStatus) {
+    runtime.deviceStatus[trackType] = 'none'
+  }
   if (res.code === RTC.RCRTCCode.PERMISSION_DENIED) {
     runtime.deviceStatus[trackType] = 'denied'
   } else {
@@ -311,6 +335,14 @@ export const removeTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackTy
 }
 
 export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
+  try {
+    await request.get('/api-web/imLiveBroadcastRoom/joinRoom', {
+      params: {
+        roomUid: runtime.roomUid,
+        userId: state.user?.speakerId,
+      }
+    })
+  } catch (error) {}
   await RongIMLib.joinChatRoom(roomId, {count: -1})
   const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)
   if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败')
@@ -330,14 +362,15 @@ export const startLive = async () => {
     console.log(microphoneAudioTrack)
     const cameraVideoTrack = await getTrack('camera')
     await setTrack([cameraVideoTrack], 'camera')
-    runtime.videoStatus = 'liveing'
     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())
 }
 
 /**
@@ -345,14 +378,15 @@ export const startLive = async () => {
  */
 export const closeLive = async () => {
   // removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
-  await request.post('/api-im/user/statusImUser', {
-    data: {
-      os: 'PC',
-      status: 2,
-      userId: state.user?.id,
-    }
-  })
-  runtime.videoStatus = 'stopped'
+  // await request.post('/api-im/user/statusImUser', {
+  //   data: {
+  //     os: 'PC',
+  //     status: 3,
+  //     userId: state.user?.id,
+  //   }
+  // })
+  sessionStorage.removeItem(START_LIVE_TIME)
+  runtime.videoStatus = 'stream'
   for (const key in runtime.activeTracks) {
     if (Object.prototype.hasOwnProperty.call(runtime.activeTracks, key)) {
       const track = runtime.activeTracks[key as TrackType] as RTC.RCLocalTrack
@@ -367,7 +401,7 @@ export const closeLive = async () => {
  * 同步点赞数量
  */
 export const loopSyncLike = async () => {
-  if (runtime.likeCount !== runtime.lastLikeCount || runtime.likeCount === 0) {
+  if ((runtime.likeCount !== runtime.lastLikeCount || runtime.likeCount === 0) && state.user) {
     try {
       await request.get('/api-web/imLiveBroadcastRoom/syncLike', {
         hideLoading: true,
@@ -454,8 +488,14 @@ export const toggleDevice = async (trackType: TrackType) => {
   const track = runtime.activeTracks[trackType]
   const needPublish = runtime.videoStatus === 'liveing'
   if (track) {
+    if (trackType === 'camera') {
+      runtime.deviceStatus.camera = 'closed'
+    }
     closeDevice(trackType, needPublish)
   } else {
+    if (trackType === 'camera') {
+      runtime.deviceStatus.camera = 'granted'
+    }
     openDevice(trackType, needPublish)
   }
 }

+ 14 - 0
src/components/live-broadcast/video-status.tsx

@@ -21,6 +21,20 @@ export default defineComponent({
             <p class={styles.tips}>请授权允许访问摄像头</p>
           </div>
         ) : null}
+        {runtime.deviceStatus.camera === 'closed' ? (
+          <div class={styles.closed}>
+            <SvgIcon name="camera-status" class={styles.icon}/>
+            <p class={styles.title}>摄像头已关闭</p>
+            <p class={styles.tips}>您已关闭摄像头</p>
+          </div>
+        ) : null}
+        {runtime.deviceStatus.camera === 'none' ? (
+          <div class={styles.none}>
+            <SvgIcon name="camera-status" class={styles.icon}/>
+            <p class={styles.title}>暂无摄像头</p>
+            <p class={styles.tips}>该设备无摄像头</p>
+          </div>
+        ) : null}
       </div>
     )
   }

+ 4 - 0
vite.config.ts

@@ -71,6 +71,10 @@ export default defineConfig({
   server: {
     cors: true,
     proxy: {
+      '/api-im': {
+        target: proxyUrl,
+        changeOrigin: true
+      },
       '/api-web': {
         target: proxyUrl,
         changeOrigin: true