浏览代码

直播部分调整

wolyshaw 3 年之前
父节点
当前提交
1e83dea992

文件差异内容过多而无法显示
+ 5 - 420
package-lock.json


+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
     "element-plus": "^2.0.2",
+    "mitt": "^3.0.0",
     "nprogress": "^0.2.0",
     "umi-request": "^1.4.0",
     "vue": "^3.2.25",

+ 39 - 0
src/components/live-broadcast/action-bar.tsx

@@ -0,0 +1,39 @@
+import { defineComponent  } from 'vue'
+import { ElButton, ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
+import runtime, * as RuntimeUtils from './runtime'
+
+export default defineComponent({
+  name: 'LiveBroadcast-ActionBar',
+  render() {
+    return (
+      <div>
+        <ElButton onClick={RuntimeUtils.closeLive}>关闭直播</ElButton>
+        <ElButton onClick={RuntimeUtils.shareScreenVideo}>屏幕共享</ElButton>
+        <ElDropdown
+          onCommand={RuntimeUtils.setSelectCamera}
+          vSlots={{
+            dropdown: () => (
+              <ElDropdownMenu>
+                {runtime.cameras.map(item => (<ElDropdownItem command={item}>{item.label}</ElDropdownItem>))}
+              </ElDropdownMenu>
+            )
+          }}
+        >
+          <ElButton type="primary">视频设备</ElButton>
+        </ElDropdown>
+        <ElDropdown
+          onCommand={RuntimeUtils.setSelectMicrophone}
+          vSlots={{
+            dropdown: () => (
+              <ElDropdownMenu>
+                {runtime.microphones.map(item => (<ElDropdownItem command={item}>{item.label}</ElDropdownItem>))}
+              </ElDropdownMenu>
+            )
+          }}
+        >
+          <ElButton type="primary">音频设备</ElButton>
+        </ElDropdown>
+      </div>
+    )
+  }
+})

+ 7 - 0
src/components/live-broadcast/event.ts

@@ -0,0 +1,7 @@
+import mitt from 'mitt'
+
+export const LIVE_EVENT_MESSAGE = {
+
+}
+
+export default mitt()

+ 14 - 0
src/components/live-broadcast/header.tsx

@@ -0,0 +1,14 @@
+import { defineComponent } from 'vue'
+import { ElButton } from 'element-plus'
+
+export default defineComponent({
+  name: 'LiveBroadcastHeader',
+  render() {
+    return (
+      <div>
+        <h3>直播内容:如何在校管乐团中锻炼孩子的自我管理及团队协作意识</h3>
+        <ElButton type="danger">关闭直播</ElButton>
+      </div>
+    )
+  }
+})

+ 48 - 94
src/components/live-broadcast/index.tsx

@@ -1,117 +1,71 @@
 import { defineComponent, ref } from 'vue'
-import * as RongIMLib from '@rongcloud/imlib-next'
-import { installer, RCRTCCode, RCRTCClient, RCLocalTrack } from '@rongcloud/plugin-rtc'
-import runtime from './runtime'
-import { requireMedia, removeMedia } from './helpers'
-
-const RONG_IM_TOKEN = 'c9kqb3rdc451j'
-
-RongIMLib.init({
-  appkey: RONG_IM_TOKEN,
-})
-
-/**
- * 监听消息通知
- */
- const Events = RongIMLib.Events;
- RongIMLib.addEventListener(Events.MESSAGES, (event) => {
-   console.log('received messages', event.messages)
- })
-
- /**
-  * 监听 IM 连接状态变化
-  */
- RongIMLib.addEventListener(Events.CONNECTING, () => {
-   runtime.imConnectStatus = 'connecting'
- })
-
- RongIMLib.addEventListener(Events.CONNECTED, () => {
-   runtime.imConnectStatus = 'connected'
- })
-
- RongIMLib.addEventListener(Events.DISCONNECT, () => {
-   runtime.imConnectStatus = 'disconnect'
- })
-
- let rtcClient: RCRTCClient | null
-
- RongIMLib.connect('hXAfqkZDs146m6MjMukBMEHvmRoZQV7Hgh8ZG4iscyE=@n56a.cn.rongnav.com;n56a.cn.rongcfg.com').then((user) => {
-  console.log('connect success', user.data?.userId);
-  rtcClient = RongIMLib.installPlugin(installer, { /*初始化参数请参考下方参数说明*/ })
-})
-.catch((error) => {
-  console.log(`连接失败: ${error}`);
-});
-
+import * as RTC from '@rongcloud/plugin-rtc'
+import Header from './header'
+import ActionBar from './action-bar'
+import runtime, * as RuntimeUtils from './runtime'
+import { removeMedia } from './helpers'
 
 const videoRef = ref<HTMLVideoElement | null>(null)
 
-type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
+const mediaStreams = new MediaStream()
 
 export default defineComponent({
   name: 'LiveBroadcast',
-  data () {
-    return {
-      mediaStreamTrack: [] as MediaStreamTrack[],
-      mediaStreams: null as MediaStream | null,
-      videoStatus: 'init' as VideoStatus,
-      screenShareStatus: false,
-    }
-  },
   async mounted() {
-    try {
-      const mediaStreams = await requireMedia({
-        audio: true,
-        video: true
-      })
-      this.videoStatus = 'stream'
-      this.mediaStreams = mediaStreams
-      this.mediaStreamTrack = mediaStreams.getTracks()
-      this.setVideoSrcObject(mediaStreams)
-    } catch (error) {
-      this.videoStatus = 'error'
-      console.log(error)
-    }
+    this.initializeRoom()
   },
   methods: {
-    setVideoSrcObject (mediaStreams: MediaStream | null) {
-      const video = videoRef.value
-      if (video && mediaStreams) {
-        video.srcObject = mediaStreams
-        video.onloadedmetadata = () => {
-          video.play()
+    async initializeRoom () {
+      try {
+        await RuntimeUtils.connectIM()
+        runtime.videoRef = videoRef.value
+        await RuntimeUtils.getMicrophones()
+        await RuntimeUtils.getCameras()
+        RuntimeUtils.setSelectCamera(runtime.cameras[0])
+        RuntimeUtils.setSelectMicrophone(runtime.microphones[0])
+        const createTracks = await runtime.rtcClient?.createMicrophoneAndCameraTracks('RongCloudRTC', {
+          audio: {
+            micphoneId: runtime.selectedMicrophone?.deviceId,
+          },
+          video: {
+            cameraId: runtime.selectedCamera?.deviceId,
+            faceMode: 'user',
+            frameRate: RTC.RCFrameRate.FPS_15,
+            resolution: RTC.RCResolution.W640_H360,
+          }
+        })
+        if (createTracks?.code === RTC.RCRTCCode.SUCCESS) {
+          for (const track of createTracks.tracks) {
+            // @ts-ignore
+            await mediaStreams.addTrack(track._msTrack)
+          }
+        } else {
+          runtime.videoStatus = 'error'
         }
+        runtime.videoStatus = 'stream'
+        runtime.mediaStreams = mediaStreams
+        runtime.mediaStreamTrack = mediaStreams.getTracks()
+        RuntimeUtils.setVideoSrcObject(runtime.videoRef, mediaStreams)
+        const join = await RuntimeUtils.joinRoom('1', RTC.RCLivingType.VIDEO, {
+
+        })
+      } catch (error) {
+        runtime.videoStatus = 'error'
+        console.log(error)
       }
     },
     closeLive() {
-      removeMedia(this.mediaStreams, this.mediaStreamTrack)
-      this.videoStatus = 'stopped'
+      removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
+      runtime.videoStatus = 'stopped'
     },
-    async screenVideo() {
-      if (rtcClient) {
-        const { code, track: screenTrack } = await rtcClient.createScreenVideoTrack()
-        if (code !== RCRTCCode.SUCCESS) {
-          this.screenShareStatus = false
-        } else {
-          this.screenShareStatus = true
-          // @ts-ignore
-          this.setVideoSrcObject(screenTrack?._msStream as MediaStream)
-        }
-        screenTrack?.on(RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RCLocalTrack) => {
-          console.log('screen track end', track)
-          this.screenShareStatus = false
-          this.setVideoSrcObject(this.mediaStreams)
-        })
-      }
-    }
   },
   render() {
     return (
       <div>
+        <Header/>
         <video ref={videoRef}></video>
-        <button onClick={this.closeLive}>关闭直播</button>
-        <button onClick={this.screenVideo}>屏幕共享</button>
-        <div>video: {this.videoStatus}, imStatus: {runtime.imConnectStatus}</div>
+        <ActionBar/>
+        <div>video: {runtime.videoStatus}, imStatus: {runtime.imConnectStatus}</div>
       </div>
     )
   }

+ 161 - 3
src/components/live-broadcast/runtime.ts

@@ -1,9 +1,167 @@
-import { reactive } from 'vue'
+import { reactive, ref, Ref } from 'vue'
+import * as RongIMLib from '@rongcloud/imlib-next'
+import { installer, device, RCRTCCode, RCRTCClient, RCLocalTrack, RCLivingType, IRoomEventListener } from '@rongcloud/plugin-rtc'
+import event, { LIVE_EVENT_MESSAGE } from './event'
+import { removeMedia } from './helpers'
 
 type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
 
-const LiveRuntime = reactive({
+type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
+
+const runtime = reactive({
+  // IM连接状态
   imConnectStatus: 'connecting' as imConnectStatus,
+  // 屏幕分享状态
+  screenShareStatus: false,
+  // 视频节点
+  videoRef: ref<HTMLVideoElement | null>(null),
+  // RTC实例
+  rtcClient: null as RCRTCClient | null,
+  // Tracks
+  mediaStreamTrack: [] as MediaStreamTrack[],
+  // 媒体流
+  mediaStreams: null as MediaStream | null,
+  // 视频状态
+  videoStatus: 'init' as VideoStatus,
+  // 麦克风设备列表
+  microphones: [] as MediaDeviceInfo[],
+  // 摄像头设备列表
+  cameras: [] as MediaDeviceInfo[],
+  // 摄像头设备
+  selectedCamera: null as MediaDeviceInfo | null,
+  // 麦克风设备
+  selectedMicrophone: null as MediaDeviceInfo | null,
+})
+
+export default runtime
+
+
+const RONG_IM_TOKEN = 'c9kqb3rdc451j'
+
+RongIMLib.init({
+  appkey: RONG_IM_TOKEN,
 })
 
-export default LiveRuntime
+/**
+ * 监听消息通知
+ */
+ const Events = RongIMLib.Events;
+ RongIMLib.addEventListener(Events.MESSAGES, (event) => {
+   console.log('received messages', event.messages)
+ })
+
+ /**
+  * 监听 IM 连接状态变化
+  */
+ RongIMLib.addEventListener(Events.CONNECTING, () => {
+   runtime.imConnectStatus = 'connecting'
+ })
+
+ RongIMLib.addEventListener(Events.CONNECTED, () => {
+   runtime.imConnectStatus = 'connected'
+ })
+
+ RongIMLib.addEventListener(Events.DISCONNECT, () => {
+   runtime.imConnectStatus = 'disconnect'
+ })
+
+ export const connectIM = async () => {
+   try {
+    const user = await RongIMLib.connect('hXAfqkZDs146m6MjMukBMEHvmRoZQV7Hgh8ZG4iscyE=@n56a.cn.rongnav.com;n56a.cn.rongcfg.com')
+    runtime.rtcClient = RongIMLib.installPlugin(installer, {})
+    console.log('connect success', user.data?.userId);
+    return user
+   } catch (error) {
+     throw error
+   }
+ }
+
+/**
+ * 设置video视频流
+ */
+
+export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams: MediaStream | null) => {
+  if (video && mediaStreams) {
+    video.srcObject = mediaStreams
+    video.onloadedmetadata = () => {
+      video.play()
+    }
+  }
+}
+
+/**
+ * 发起屏幕共享
+ */
+export const shareScreenVideo = async () => {
+  if (runtime.rtcClient) {
+    const { code, track: screenTrack } = await runtime.rtcClient.createScreenVideoTrack()
+    if (code !== RCRTCCode.SUCCESS) {
+      runtime.screenShareStatus = false
+    } else {
+      runtime.screenShareStatus = true
+      console.log(runtime.videoRef)
+      // @ts-ignore
+      // setVideoSrcObject(runtime.videoRef, screenTrack?._msStream as MediaStream)
+    }
+    screenTrack?.on(RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RCLocalTrack) => {
+      console.log('screen track end', track)
+      runtime.screenShareStatus = false
+      // setVideoSrcObject(runtime.videoRef, this.mediaStreams)
+    })
+  }
+}
+
+/**
+ *
+ * 获取所有音频输入设备
+ * @returns {Promise<void>}
+ */
+export const getMicrophones = async () => {
+  const microphones = await device.getMicrophones()
+  runtime.microphones = microphones
+  return microphones
+}
+
+/**
+ *
+ * 获取所有视频输入设备
+ * @returns {Promise<void>}
+ */
+export const getCameras = async () => {
+  const cameras = await device.getCameras()
+  runtime.cameras = cameras
+  return cameras
+}
+
+/**
+ *
+ * 设置当前视频设备
+ * @param camera MediaDeviceInfo
+ */
+export const setSelectCamera = (camera: MediaDeviceInfo) => {
+  runtime.selectedCamera = camera
+}
+
+/**
+ *
+ * 设置当前麦克风设备
+ * @param microphone MediaDeviceInfo
+ */
+export const setSelectMicrophone = (microphone: MediaDeviceInfo) => {
+  runtime.selectedMicrophone = microphone
+}
+
+export const joinRoom = async (roomId: string, type: RCLivingType, listenEvents: IRoomEventListener | null) => {
+  const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)
+  if (join?.code != RCRTCCode.SUCCESS) throw Error('加入房间失败')
+  join.room?.registerRoomEventListener(listenEvents)
+  return join
+}
+
+/**
+ * 关闭直播
+ */
+export const closeLive = async () => {
+  removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
+  runtime.videoStatus = 'stopped'
+}

+ 3 - 0
src/main.ts

@@ -1,8 +1,11 @@
 import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
 import App from './App.vue'
 import router from './router'
 
 import './permission'
 
 createApp(App)
+  .use(ElementPlus)
   .use(router).mount('#app')

+ 1 - 1
src/permission.ts

@@ -10,7 +10,7 @@ import { getToken } from "./utils/auth";
 
 // NProgress.configure({ showSpinner: false }); // NProgress Configuration
 
-const whiteList = ["/login"]; // no redirect whitelist
+const whiteList = ["/login", '/']; // no redirect whitelist
 
 router.beforeEach(async (to, from, next) => {
   // from.query = to.query

+ 1 - 1
vite.config.ts

@@ -1,5 +1,5 @@
 const { resolve } = require('path')
-import { defineConfig } from "vite";
+import { defineConfig } from 'vite'
 import vue from "@vitejs/plugin-vue";
 import { VitePWA } from 'vite-plugin-pwa'
 const vueJsx = require('@vitejs/plugin-vue-jsx')

部分文件因为文件数量过多而无法显示