Эх сурвалжийг харах

监考端视频通话视频流筛选展示

zhangjie 4 жил өмнө
parent
commit
32b2ae0e04

+ 2 - 2
src/features/invigilation/ExamReport/ExamReport.vue

@@ -64,11 +64,11 @@
             <el-button type="primary" @click="search">查询</el-button>
           </el-form-item>
         </el-form>
-        <div class="part-filter-form-action">
+        <!-- <div class="part-filter-form-action">
           <el-button type="primary" icon="icon icon-download" @click="toExport"
             >导出统计报表</el-button
           >
-        </div>
+        </div> -->
       </div>
       <div class="exam-report-tabs">
         <el-button

+ 72 - 29
src/features/invigilation/RealtimeMonitoring/VideoCommunication.vue

@@ -35,11 +35,19 @@
             </div>
             <h4 class="student-name">{{ item.examStudentName }}</h4>
             <div v-if="callStatus === 'START'">
-              <el-button round type="success" @click="answer(item, 0)"
+              <el-button
+                round
+                type="success"
+                @click="answer(item, 0)"
+                :loading="holding"
                 >语音通话</el-button
               >
               <br />
-              <el-button round type="primary" @click="answer(item, 1)"
+              <el-button
+                round
+                type="primary"
+                @click="answer(item, 1)"
+                :loading="holding"
                 >视频通话</el-button
               >
             </div>
@@ -117,6 +125,7 @@ export default {
       examId: this.$route.params.examId,
       callStatus: "START",
       dialogVisible: false,
+      holding: false,
       students: [],
       curStudent: {},
       current: 1,
@@ -171,6 +180,12 @@ export default {
       this.current = page;
       this.getCommunicationList();
     },
+    notifyError(content) {
+      this.$notify({
+        type: "error",
+        message: content,
+      });
+    },
     async initClient(examRecordId) {
       const res = await getUserMonitorKey(examRecordId);
       this.userMonitor = res.data.data;
@@ -181,33 +196,68 @@ export default {
         userSig: this.userMonitor.monitorUserSig,
       });
     },
+    async getLocalMedia(isVideo) {
+      const localStream = createStream({
+        userId: this.userMonitor.monitorUserId,
+        audio: true,
+        video: isVideo,
+      });
+      const errorTips = {
+        NotFoundError: "找不到硬件设备,请确保硬件设备已经正常插入。",
+        NotAllowedError: "不授权摄像头/麦克风访问无法进行音视频通话。",
+        NotReadableError:
+          "暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试。",
+        OverConstrainedError: "设备异常",
+        AbortError: "设备异常",
+      };
+
+      let initLocalStreamResult = true;
+      await localStream.initialize().catch((error) => {
+        console.log(errorTips[error.name]);
+        this.notifyError(errorTips[error.name]);
+        initLocalStreamResult = false;
+        localStream.close();
+      });
+      return initLocalStreamResult && localStream;
+    },
     async answer(student, isVideo) {
+      if (this.holding) return;
+      this.holding = true;
+
       this.curStudent = student;
-      await this.initClient(student.examRecordId);
-      // 更改学生的通话申请状态
-      await communicationCalling({
-        recordId: student.examRecordId,
-        source: student.source,
-      });
+      await this.initClient(student.examRecordId).catch(() => {});
+      if (!this.client) {
+        this.holding = false;
+        return;
+      }
+      this.localStream = await this.getLocalMedia(isVideo);
+      if (!this.localStream) {
+        this.holding = false;
+        return;
+      }
 
       this.dialogVisible = true;
+      this.holding = false;
       // 添加远程用户视频发布监听
       this.client.on("stream-added", (event) => {
         console.log(event);
-        const remoteStream = event.stream;
+        if (event.stream.userId_ !== student.sourceUserId) return;
 
+        const remoteStream = event.stream;
         this.client
           .subscribe(remoteStream, { audio: true, video: true })
           .then(() => {
-            console.log("订阅视频成功!");
+            console.log("开始订阅视频成功!");
           })
           .catch((error) => {
             console.log("订阅视频失败!", error);
+            this.notifyError("学生视频获取失败!");
           });
       });
       this.client.on("stream-subscribed", (event) => {
+        console.log("视频已订阅!");
         const remoteStream = event.stream;
-        this.$refs.SecondTimer.start();
+        if (!this.$refs.SecondTimer.recoding) this.$refs.SecondTimer.start();
         remoteStream.play("communication-host", { objectFit: "contain" });
       });
 
@@ -222,6 +272,7 @@ export default {
         .catch((error) => {
           roomJoinResult = false;
           console.log("加入房间失败!", error);
+          this.notifyError("接通通信失败!");
         });
       if (!roomJoinResult) return;
 
@@ -229,34 +280,28 @@ export default {
       let switchResult = true;
       await this.client.switchRole("anchor").catch((error) => {
         console.log("切换角色失败!", error);
+        this.notifyError("接通通信失败!");
         switchResult = false;
       });
       if (!switchResult) return;
 
-      // 初始化stream
-      this.localStream = createStream({
-        userId: this.userMonitor.monitorUserId,
-        audio: true,
-        video: isVideo,
-      });
-
-      let initLocalStreamResult = true;
-      await this.localStream.initialize().catch((error) => {
-        console.log("初始化视频失败!", error);
-        initLocalStreamResult = false;
-      });
-      if (!initLocalStreamResult) return;
-
       // 发布本地视频
       let publishStreamResult = true;
       this.client.publish(this.localStream).catch((error) => {
         console.log("发布本地视频失败!", error);
+        this.notifyError("本地音视频推送失败!");
         publishStreamResult = false;
       });
       if (!publishStreamResult) return;
 
       // 播放本地视频
       this.localStream.play("communication-guest", { muted: true });
+
+      // 更改学生的通话申请状态
+      await communicationCalling({
+        recordId: student.examRecordId,
+        source: student.source,
+      });
     },
     async hangup() {
       this.$refs.SecondTimer.end();
@@ -268,12 +313,9 @@ export default {
       this.curStudent = {};
 
       // 取消发布本地视频
-      let unpublishStreamResult = true;
-      this.client.unpublish(this.localStream).catch((error) => {
+      await this.client.unpublish(this.localStream).catch((error) => {
         console.log("取消发布本地视频失败!", error);
-        unpublishStreamResult = false;
       });
-      if (!unpublishStreamResult) return;
 
       this.localStream.close();
       this.localStream = null;
@@ -281,6 +323,7 @@ export default {
       let result = true;
       await this.client.leave().catch((error) => {
         console.log("离开房间失败!", error);
+        this.notifyError("操作异常,请重新尝试!");
         result = false;
       });
       if (!result) return;

+ 69 - 24
src/features/invigilation/RealtimeMonitoring/WarningDetail.vue

@@ -124,21 +124,30 @@
             icon="icon icon-text"
             @click="toSendTextMsg"
             title="发送文字提醒"
+            v-if="detailInfo.statusCode === 'ANSWERING'"
           ></el-button>
           <el-button
             class="el-icon-btn"
             type="primary"
             icon="icon icon-audio"
             @click="toSendAudioMsg"
+            v-if="detailInfo.statusCode === 'ANSWERING'"
           ></el-button>
           <el-popover
             class="warning-body-head-call"
             placement="bottom-start"
-            trigger="click"
-            v-if="detailInfo.monitorVideoSource"
+            v-model="popoverVisible"
+            v-if="
+              detailInfo.monitorVideoSource &&
+              detailInfo.statusCode === 'ANSWERING'
+            "
           >
-            <el-button type="success" @click="answer(0)">语音通话</el-button>
-            <el-button type="primary" @click="answer(1)">视频通话</el-button>
+            <el-button type="success" @click="answer(0)" :loading="holding"
+              >语音通话</el-button
+            >
+            <el-button type="primary" @click="answer(1)" :loading="holding"
+              >视频通话</el-button
+            >
             <el-button type="primary" slot="reference">实时通话</el-button>
           </el-popover>
         </div>
@@ -327,6 +336,7 @@ export default {
       secondViewVideoReady: false,
       holding: false,
       // communication
+      popoverVisible: false,
       userMonitor: {},
       client: null,
       localStream: null,
@@ -585,6 +595,12 @@ export default {
       this.firstViewVideoReady = false;
       this.secondViewVideoReady = false;
     },
+    notifyError(content) {
+      this.$notify({
+        type: "error",
+        message: content,
+      });
+    },
     async initClient(examRecordId) {
       const res = await getUserMonitorKey(examRecordId);
       this.userMonitor = res.data.data;
@@ -595,15 +611,54 @@ export default {
         userSig: this.userMonitor.monitorUserSig,
       });
     },
+    async getLocalMedia(isVideo) {
+      const localStream = createStream({
+        userId: this.userMonitor.monitorUserId,
+        audio: true,
+        video: isVideo,
+      });
+      const errorTips = {
+        NotFoundError: "找不到硬件设备,请确保硬件设备正常。",
+        NotAllowedError: "不授权摄像头/麦克风访问无法进行音视频通话。",
+        NotReadableError:
+          "暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试。",
+        OverConstrainedError: "设备异常",
+        AbortError: "设备异常",
+      };
+
+      let initLocalStreamResult = true;
+      await localStream.initialize().catch((error) => {
+        console.log(errorTips[error.name]);
+        this.notifyError(errorTips[error.name]);
+        initLocalStreamResult = false;
+        localStream.close();
+      });
+      return initLocalStreamResult && localStream;
+    },
     async answer(isVideo) {
+      if (this.holding) return;
+      this.holding = true;
       this.closeSubscribeVideo();
 
-      await this.initClient(this.recordId);
+      await this.initClient(this.recordId).catch(() => {});
+      if (!this.client) {
+        this.holding = false;
+        return;
+      }
+      this.localStream = await this.getLocalMedia(isVideo);
+      if (!this.localStream) {
+        this.holding = false;
+        return;
+      }
 
       this.dialogVisible = true;
+      this.popoverVisible = false;
+      this.holding = false;
       // 添加远程用户视频发布监听
       this.client.on("stream-added", (event) => {
         console.log(event);
+        console.log(event.stream.userId_, this.userMonitor.sourceUserId);
+        if (event.stream.userId_ !== this.userMonitor.sourceUserId) return;
         const remoteStream = event.stream;
 
         // 延迟订阅视频
@@ -611,14 +666,17 @@ export default {
           this.client
             .subscribe(remoteStream, { audio: true, video: true })
             .then(() => {
-              console.log("订阅视频成功!");
+              console.log("开始订阅视频成功!");
             })
             .catch((error) => {
               console.log("订阅视频失败!", error);
+              this.notifyError("学生视频获取失败!");
             });
         }, 5000);
       });
       this.client.on("stream-subscribed", (event) => {
+        console.log("视频已订阅!");
+        console.log(event);
         const remoteStream = event.stream;
         this.isWaiting = false;
         this.$nextTick(() => {
@@ -637,6 +695,7 @@ export default {
         .catch((error) => {
           roomJoinResult = false;
           console.log("加入房间失败!", error);
+          this.notifyError("发起通信失败!");
         });
       if (!roomJoinResult) return;
 
@@ -644,28 +703,16 @@ export default {
       let switchResult = true;
       await this.client.switchRole("anchor").catch((error) => {
         console.log("切换角色失败!", error);
+        this.notifyError("发起通信失败!");
         switchResult = false;
       });
       if (!switchResult) return;
 
-      // 初始化stream
-      this.localStream = createStream({
-        userId: this.userMonitor.monitorUserId,
-        audio: true,
-        video: isVideo,
-      });
-
-      let initLocalStreamResult = true;
-      await this.localStream.initialize().catch((error) => {
-        console.log("初始化视频失败!", error);
-        initLocalStreamResult = false;
-      });
-      if (!initLocalStreamResult) return;
-
       // 发布本地视频
       let publishStreamResult = true;
       this.client.publish(this.localStream).catch((error) => {
         console.log("发布本地视频失败!", error);
+        this.notifyError("本地音视频推送失败!");
         publishStreamResult = false;
       });
       if (!publishStreamResult) return;
@@ -679,12 +726,9 @@ export default {
       this.$refs.SecondTimer.end();
 
       // 取消发布本地视频
-      let unpublishStreamResult = true;
-      this.client.unpublish(this.localStream).catch((error) => {
+      await this.client.unpublish(this.localStream).catch((error) => {
         console.log("取消发布本地视频失败!", error);
-        unpublishStreamResult = false;
       });
-      if (!unpublishStreamResult) return;
 
       this.localStream.close();
       this.localStream = null;
@@ -693,6 +737,7 @@ export default {
       let result = true;
       await this.client.leave().catch((error) => {
         console.log("离开房间失败!", error);
+        this.notifyError("操作异常,请重新尝试!");
         result = false;
       });
       if (!result) return;

+ 4 - 0
src/features/invigilation/ReexamApply/ReexamApply.vue

@@ -99,8 +99,12 @@
             type="primary"
             icon="icon icon-seal"
             @click="toEdit(scope.row)"
+            v-if="!scope.row.reexamCount"
             >重考</el-button
           >
+          <el-button :disabled="scope.row.reexamCount" v-else>
+            已申请
+          </el-button>
         </template>
       </el-table-column>
     </el-table>