Browse Source

update 直播

dusenyao 3 years ago
parent
commit
3a726f82c3

+ 24 - 2
src/store/modules/app.js

@@ -1,6 +1,28 @@
-const state = {};
+import { app } from '@/store/mutation-types';
 
-const mutations = {};
+let liveStorage = 'liveDevice';
+
+const getDefaultSate = () => {
+  let device = localStorage.getItem(liveStorage);
+  let defaultDevice = {
+    video: [],
+    audio: []
+  };
+  device = device ? JSON.parse(device) : defaultDevice;
+
+  return {
+    liveDevice: device
+  };
+};
+
+const state = getDefaultSate();
+
+const mutations = {
+  [app.SET_DEVICE]: (state, device) => {
+    state.liveDevice = device;
+    localStorage.setItem(liveStorage, JSON.stringify(device));
+  }
+};
 
 const actions = {};
 

+ 3 - 1
src/store/mutation-types.js

@@ -4,6 +4,8 @@ const user = {
   UPDATE_LANGUAGE_TYPE: 'UPDATE_LANGUAGE_TYPE'
 };
 
-const app = {};
+const app = {
+  SET_DEVICE: 'SET_DEVICE'
+};
 
 export { user, app };

+ 96 - 4
src/views/live/SelectDevice.vue

@@ -1,5 +1,44 @@
 <template>
-  <el-dialog :visible="dialogVisibleDevice"></el-dialog>
+  <el-dialog
+    :visible="dialogVisibleDevice"
+    :close-on-click-modal="false"
+    :show-close="false"
+    :close-on-press-escape="false"
+    title="设置设备"
+  >
+    <div class="device">
+      <div>
+        <div class="title">视频</div>
+        <div class="device-video">
+          <template v-if="device.video.length > 0">
+            <div v-for="item in device.video" :key="item.deviceId">
+              <el-radio v-model="video" :label="item.deviceId"> {{ item.label }}</el-radio>
+            </div>
+          </template>
+          <template v-else>
+            <div>暂无视频设备</div>
+          </template>
+        </div>
+      </div>
+      <div>
+        <div class="title">音频</div>
+        <div class="device-audio">
+          <template v-if="device.audio.length > 0">
+            <div v-for="item in device.audio" :key="item.deviceId">
+              <el-radio v-model="audio" :label="item.deviceId"> {{ item.label }}</el-radio>
+            </div>
+          </template>
+          <template v-else>
+            <div>暂无音频设备</div>
+          </template>
+        </div>
+      </div>
+    </div>
+
+    <div slot="footer">
+      <el-button type="primary" @click="dialogDeviceClose">完成</el-button>
+    </div>
+  </el-dialog>
 </template>
 
 <script>
@@ -8,13 +47,66 @@ export default {
     dialogVisibleDevice: {
       default: false,
       type: Boolean
+    },
+    device: {
+      default: () => {
+        return {
+          video: [],
+          audio: []
+        };
+      },
+      type: Object
     }
   },
   data() {
-    return {};
+    return {
+      audio: '',
+      video: ''
+    };
   },
-  methods: {}
+  watch: {
+    dialogVisibleDevice(newValue) {
+      if (newValue) {
+        let device = this.$store.state.app.liveDevice;
+        this.video =
+          this.device.video.length === 0 ? '' : this.selectValue(this.device.video, device.video);
+
+        this.audio =
+          this.device.audio.length === 0 ? '' : this.selectValue(this.device.audio, device.audio);
+      }
+    }
+  },
+  methods: {
+    selectValue(arr, value) {
+      return arr.some(item => item.deviceId === value) ? value : arr[0].deviceId;
+    },
+    dialogDeviceClose() {
+      this.$emit('dialogDeviceClose', { audio: this.audio, video: this.video });
+    }
+  }
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.device {
+  font-size: 16px;
+
+  > div + div {
+    margin-top: 24px;
+  }
+
+  .title {
+    font-weight: bold;
+    margin-bottom: 12px;
+  }
+
+  &-video,
+  &-audio {
+    margin-left: 24px;
+
+    > div + div {
+      margin-top: 8px;
+    }
+  }
+}
+</style>

+ 2 - 9
src/views/live/common.js

@@ -188,15 +188,8 @@ export function playVideo() {
 /**
  * 获取设备列表
  */
-export function getDevice() {
-  rtc.getDevice({
-    success: function (data) {
-      console.log('获取成功', data);
-    },
-    fail: function (str) {
-      console.log('直播关闭状态或查询直播失败', str);
-    }
-  });
+export function getDevice(options) {
+  rtc.getDevice(options);
 }
 
 /**

+ 0 - 1
src/views/live/student/group.js

@@ -4,7 +4,6 @@ export {
   initSDK,
   sendMsg,
   getLiveStat,
-  getDevice,
   createScript,
   downloadWebSDK,
   closeVideo,

+ 0 - 4
src/views/live/student/group.vue

@@ -260,10 +260,6 @@ export default {
       this.msg = '';
     },
 
-    getDevice() {
-      common.getDevice();
-    },
-
     // 分组讨论
     GetGroupStatus() {
       this.timer = setInterval(() => {

+ 54 - 6
src/views/live/student/index.vue

@@ -6,6 +6,7 @@
         <div class="live-title-name">{{ roomInfo.cs_item_name }} {{ roomInfo.task_name }}</div>
         <div>
           <el-button @click="exitRoom">退出房间</el-button>
+          <el-button @click="setDevice()">设置设备</el-button>
         </div>
       </div>
       <div class="live-course-name">{{ roomInfo.course_name }}</div>
@@ -97,7 +98,9 @@
             <div>
               {{ roomInfo.teacher_name }}
             </div>
-            <div></div>
+            <div>
+              <span :style="{ color: netStatusColor }">{{ netStatus }}</span>
+            </div>
           </div>
         </div>
         <div class="student-list">
@@ -124,6 +127,13 @@
       :material-picture-url="material_picture_url"
       @dialogMaterialClose="dialogMaterialClose"
     />
+
+    <!-- 选择设备 -->
+    <select-device
+      :dialog-visible-device="dialogVisibleDevice"
+      :device="device"
+      @dialogDeviceClose="dialogDeviceClose"
+    />
   </div>
 </template>
 
@@ -136,13 +146,15 @@ import {
   GetGroupStatus,
   CreateEnterLiveRoomSession
 } from '@/api/live';
+import { app } from '@/store/mutation-types';
+import SelectDevice from '../SelectDevice.vue';
 import CurMaterial from '@/components/live/CurMaterial.vue';
 import * as common from './live';
 
 export default {
-  name: 'Live',
   components: {
-    CurMaterial
+    CurMaterial,
+    SelectDevice
   },
   data() {
     return {
@@ -198,13 +210,33 @@ export default {
       student_list: [],
       // 直播状态
       liveStat: false,
-      liveMenuShow: false
+      // 网络整体状况
+      netStatus: 0,
+      liveMenuShow: false,
+      dialogVisibleGroup: false,
+      dialogVisibleDevice: false,
+      device: {
+        video: [],
+        audio: []
+      }
     };
   },
   computed: {
     // 画板模式
     isDraw() {
       return !this.connect && !this.callLoading;
+    },
+    netStatusColor() {
+      if (this.netStatus >= 1000) {
+        return '#f00';
+      }
+      if (this.netStatus >= 500) {
+        return '#d6e91a';
+      }
+      if (this.netStatus >= 200) {
+        return '#6aee4c';
+      }
+      return '#38d514';
     }
   },
   watch: {
@@ -332,8 +364,14 @@ export default {
       this.msg = '';
     },
 
-    getDevice() {
-      common.getDevice();
+    // 设置音视频设备
+    setDevice() {
+      this.dialogVisibleDevice = true;
+    },
+
+    dialogDeviceClose(device) {
+      this.$store.commit(`app/${app.SET_DEVICE}`, device);
+      this.dialogVisibleDevice = false;
     },
 
     inviteAccept() {
@@ -725,6 +763,7 @@ $live-bc: #3d3938;
         }
 
         .live-wrapper {
+          display: flex;
           position: absolute;
           height: 40px;
           width: 100%;
@@ -734,6 +773,15 @@ $live-bc: #3d3938;
           line-height: 40px;
           padding: 0 16px;
           transition: all 300ms ease-in 0s;
+
+          > div:first-child {
+            flex: 6;
+          }
+
+          > div:last-child {
+            flex: 4;
+            text-align: right;
+          }
         }
       }
 

+ 57 - 7
src/views/live/student/live.js

@@ -1,11 +1,11 @@
 import { Message } from 'element-ui';
+import store from '@/store';
 import { GetLiveRoomInfo } from '@/api/live';
-import { rtc, publishStream, closeVideo } from '@/views/live/common';
+import { rtc, publishStream, closeVideo, getDevice } from '@/views/live/common';
 export {
   initSDK,
   sendMsg,
   getLiveStat,
-  getDevice,
   downloadWebSDK,
   createScript,
   handsDown,
@@ -86,11 +86,41 @@ export function initListener(vue) {
   // 教师 必须在加入房间成功的事件回调里创建本地流
   rtc.on('conference_join', () => {
     console.log('加入房间成功');
+
+    getDevice({
+      success(data) {
+        console.log('获取音视频设备成功', data);
+        let device = store.state.app.liveDevice;
+        vue.device = data;
+
+        let isVideo =
+          data.video.some(item => item.deviceId === device.video) ||
+          (data.video.length === 0 && device.video.length === 0);
+        let isAudio =
+          data.audio.some(item => item.deviceId === device.audio) ||
+          (data.audio.length === 0 && device.audio.length === 0);
+
+        if (!isVideo || !isAudio) {
+          vue.setDevice();
+        }
+      },
+      fail(str) {
+        console.log('直播关闭状态或查询直播失败: ', str);
+        Message({
+          type: 'error',
+          message: `直播关闭状态或查询直播失败: ${str}`
+        });
+      }
+    });
   });
 
   rtc.on('conference_join_failed', err => {
     // 加入房间失败  err为错误原因
     console.log('加入房间失败', err);
+    Message({
+      message: `加入房间失败:${err}`,
+      type: 'warning'
+    });
   });
 
   // 新增订阅流事件
@@ -172,6 +202,18 @@ export function initListener(vue) {
   rtc.on('publishStreamErr', data => {
     console.log('推流意外终止:' + data.streamName);
     // 直播开启状态下,尝试重推这条流
+    publishStream('picture');
+  });
+
+  // 网络整体状态事件
+  rtc.on('netStatus', data => {
+    console.log(data.netStatus);
+    vue.netStatus = data.netStatus;
+  });
+
+  // 单条流状态通知事件
+  rtc.on('streamStatus', data => {
+    console.log(data);
   });
 
   rtc.on('kick_out', function () {
@@ -224,14 +266,22 @@ export function initListener(vue) {
     GetLiveRoomInfo({ task_id: vue.task_id }).then(({ video_mode }) => {
       console.log('创建本地流推流');
       vue.roomInfo.video_mode = video_mode;
+      let device = store.state.app.liveDevice;
+      let video =
+        device.video.length > 0
+          ? { device: 'camera', resolution: 'vga', deviceId: device.video }
+          : false;
+      let audio = device.audio.length > 0 ? { deviceId: device.audio } : false;
+
       const createData = {
-        video: video_mode === 1,
-        audio: true
+        video: video_mode === 1 ? video : false,
+        audio
       };
+
       rtc.createLocalStream({
         streamName: 'picture',
         createData,
-        success: function (stream) {
+        success(stream) {
           vue.connect = true;
           vue.callLoading = false;
           console.log('创建本地流成功', stream);
@@ -239,12 +289,12 @@ export function initListener(vue) {
           stream.show('student');
           publishStream('picture'); // 如果需要立即推流,执行 publish 方法
         },
-        fail: function (data) {
+        fail(data) {
           console.log('创建本地流失败,应用层处理', data);
           // 创建本地流失败,应用层处理
           Message({
             type: 'error',
-            message: '创建本地流失败:' + data
+            message: '创建本地流失败:' + data.err
           });
         }
       });

+ 0 - 1
src/views/live/teacher/group.js

@@ -3,7 +3,6 @@ import { rtc, updateMcResult, createScript } from '@/views/live/common';
 export {
   initSDK,
   sendMsg,
-  getDevice,
   sendPublishMessage,
   closeVideo,
   roomUpdate,

+ 67 - 7
src/views/live/teacher/index.vue

@@ -10,7 +10,7 @@
             结束直播
           </el-button>
           <el-button v-if="liveStat" @click="reconnection">重连</el-button>
-          <el-button @click="getDevice">设置设备</el-button>
+          <el-button @click="setDevice(true)">设置设备</el-button>
         </div>
       </div>
       <div class="live-course-name">{{ roomInfo.course_name }}</div>
@@ -135,7 +135,9 @@
             <div>
               {{ roomInfo.teacher_name }}
             </div>
-            <div></div>
+            <div>
+              <span :style="{ color: netStatusColor }">{{ netStatus }}</span>
+            </div>
           </div>
         </div>
         <div class="student-list">
@@ -171,6 +173,12 @@
       @dialogCompleteClose="dialogCompleteClose"
     />
 
+    <select-device
+      :dialog-visible-device="dialogVisibleDevice"
+      :device="device"
+      @dialogDeviceClose="dialogDeviceClose"
+    />
+
     <el-dialog
       title="分组讨论组数"
       top="30vh"
@@ -199,15 +207,17 @@ import {
   StartGroup,
   GetGroupStatus
 } from '@/api/live';
+import { app } from '@/store/mutation-types';
 import SelectMaterial from '@/components/live/SelectMaterial.vue';
 import CompleteList from './CompleteList.vue';
+import SelectDevice from '../SelectDevice.vue';
 import * as common from './live';
 
 export default {
-  name: 'Live',
   components: {
     SelectMaterial,
-    CompleteList
+    CompleteList,
+    SelectDevice
   },
   data() {
     return {
@@ -263,13 +273,33 @@ export default {
       // 分组讨论
       groupNumList: [],
       group_count: 1,
-      dialogVisibleGroup: false
+      // 网络整体情况
+      netStatus: 0,
+      dialogVisibleGroup: false,
+      dialogVisibleDevice: false,
+      isRecreate: false,
+      device: {
+        video: [],
+        audio: []
+      }
     };
   },
   computed: {
     // 画板模式
     isDraw() {
       return !this.connect && !this.callLoading;
+    },
+    netStatusColor() {
+      if (this.netStatus >= 1000) {
+        return '#f00';
+      }
+      if (this.netStatus >= 500) {
+        return '#d6e91a';
+      }
+      if (this.netStatus >= 200) {
+        return '#6aee4c';
+      }
+      return '#38d514';
     }
   },
   watch: {
@@ -510,8 +540,28 @@ export default {
       common.drawChange(action, value);
     },
 
-    getDevice() {
-      common.getDevice();
+    // 设置音视频设备
+    setDevice(isRecreate) {
+      this.isRecreate = isRecreate;
+      this.dialogVisibleDevice = true;
+    },
+
+    dialogDeviceClose(device) {
+      this.$store.commit(`app/${app.SET_DEVICE}`, device);
+      this.dialogVisibleDevice = false;
+      if (this.isRecreate) {
+        common.closeVideoTeacher({
+          streamName: 'main',
+          success: () => {
+            common.createLocalStream();
+          },
+          fail: str => {
+            console.log(str);
+          }
+        });
+      } else {
+        common.createLocalStream();
+      }
     },
 
     showDrawSetting() {
@@ -899,6 +949,7 @@ $live-bc: #3d3938;
         }
 
         .live-wrapper {
+          display: flex;
           position: absolute;
           height: 40px;
           width: 100%;
@@ -908,6 +959,15 @@ $live-bc: #3d3938;
           line-height: 40px;
           padding: 0 16px;
           transition: all 300ms ease-in 0s;
+
+          > div:first-child {
+            flex: 6;
+          }
+
+          > div:last-child {
+            flex: 4;
+            text-align: right;
+          }
         }
       }
 

+ 66 - 7
src/views/live/teacher/live.js

@@ -1,5 +1,6 @@
 import { Message } from 'element-ui';
-import { rtc, updateMcResult } from '@/views/live/common';
+import store from '@/store';
+import { rtc, updateMcResult, getDevice } from '@/views/live/common';
 export {
   initSDK,
   downloadWebSDK,
@@ -8,7 +9,6 @@ export {
   roomUpdate,
   sendMsg,
   drawChange,
-  getDevice,
   publishStream,
   handsDown,
   sendPublishMessage,
@@ -40,10 +40,17 @@ function publishStream(streamName) {
 /**
  * 创建本地流
  */
-function createLocalStream() {
+export function createLocalStream() {
+  let device = store.state.app.liveDevice;
+  let video =
+    device.video.length > 0
+      ? { device: 'camera', resolution: 'vga', deviceId: device.video }
+      : false;
+  let audio = device.audio.length > 0 ? { deviceId: device.audio } : false;
+
   const createData = {
-    video: true,
-    audio: true
+    video,
+    audio
   };
   rtc.createLocalStream({
     streamName: 'main',
@@ -55,16 +62,24 @@ function createLocalStream() {
       publishStream('main'); // 如果需要立即推流,执行 publish 方法
     },
     fail(data) {
+      console.log('创建本地流失败:', data);
       // 创建本地流失败,应用层处理
       Message({
         type: 'error',
-        message: '创建本地流失败:' + data
+        message: '创建本地流失败:' + data.err
       });
     }
   });
 }
 
 /**
+ * 结束本地流
+ */
+export function closeVideoTeacher(options) {
+  rtc.closeVideo(options);
+}
+
+/**
  * 初始化监听事件
  */
 export function initListener(vue) {
@@ -97,12 +112,49 @@ export function initListener(vue) {
   // 教师 必须在加入房间成功的事件回调里创建本地流
   rtc.on('conference_join', () => {
     console.log('加入房间成功');
-    createLocalStream();
+    getDevice({
+      success(data) {
+        console.log('获取音视频设备成功', data);
+        let device = store.state.app.liveDevice;
+        vue.device = data;
+
+        if (data.video.length === 0 && data.audio.length === 0) {
+          Message({
+            type: 'warning',
+            message: '无音视频设备,无法创建本地流'
+          });
+        }
+
+        let isVideo =
+          data.video.some(item => item.deviceId === device.video) ||
+          (data.video.length === 0 && device.video.length === 0);
+        let isAudio =
+          data.audio.some(item => item.deviceId === device.audio) ||
+          (data.audio.length === 0 && device.audio.length === 0);
+
+        if (!isVideo || !isAudio) {
+          vue.setDevice(false);
+        } else {
+          createLocalStream();
+        }
+      },
+      fail(str) {
+        console.log('直播关闭状态或查询直播失败: ', str);
+        Message({
+          type: 'error',
+          message: `直播关闭状态或查询直播失败: ${str}`
+        });
+      }
+    });
   });
 
   rtc.on('conference_join_failed', err => {
     // 加入房间失败  err为错误原因
     console.log('加入房间失败', err);
+    Message({
+      message: `加入房间失败:${err}`,
+      type: 'warning'
+    });
   });
 
   // 新增订阅流事件
@@ -184,6 +236,12 @@ export function initListener(vue) {
 
   // 网络整体状态事件
   rtc.on('netStatus', data => {
+    console.log(data.netStatus);
+    vue.netStatus = data.netStatus;
+  });
+
+  // 单条流状态通知事件
+  rtc.on('streamStatus', data => {
     console.log(data);
   });
 
@@ -191,6 +249,7 @@ export function initListener(vue) {
   rtc.on('publishStreamErr', data => {
     // 直播开启状态下,尝试重推这条流
     console.log('推流意外终止:' + data.streamName);
+    publishStream('main');
   });
 
   // 视频无法自动播放

+ 1 - 1
src/views/main/curricula_list/student.vue

@@ -3,7 +3,7 @@
     <!-- 查询条件 -->
     <div class="curricula-student-search">
       <div class="curricula-student-search-condition">
-        <el-input v-model="search" prefix-icon="el-icon-search">
+        <el-input v-model="search" prefix-icon="el-icon-search" @change="queryMyCourseList">
           <el-button slot="append" @click="queryMyCourseList">搜索</el-button>
         </el-input>
       </div>

+ 1 - 1
src/views/main/curricula_list/teacher.vue

@@ -3,7 +3,7 @@
     <!-- 查询条件 -->
     <div class="curricula-teacher-search">
       <div class="curricula-teacher-search-condition">
-        <el-input v-model="search" prefix-icon="el-icon-search">
+        <el-input v-model="search" prefix-icon="el-icon-search" @change="queryMyCourseList">
           <el-button slot="append" @click="queryMyCourseList">搜索</el-button>
         </el-input>
       </div>