123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- <template>
- <div class="warning-detail">
- <div class="warning-detail-head">
- <h2>预警详情</h2>
- <el-button size="mini" icon="el-icon-arrow-left" @click="goBack"
- >返回列表</el-button
- >
- <!-- <el-button
- @click="initSubscribeVideo"
- type="primary"
- size="mini"
- icon="el-icon-arrow-left"
- >开始视频</el-button
- >
- <el-button
- @click="closeSubscribeVideo"
- type="danger"
- size="mini"
- icon="el-icon-arrow-left"
- >关闭视频</el-button
- > -->
- </div>
- <div class="warning-detail-body">
- <div class="detail-body-head">
- <div class="detail-body-head-left">
- <p>
- <i class="icon icon-person"></i>
- <span>{{ detailInfo.examStudentName }}</span>
- </p>
- <p>
- <span>证件号:</span><span>{{ detailInfo.identity }}</span>
- </p>
- <p>
- <span>科目(代码):</span
- ><span>{{ detailInfo.courseNameCode }}</span>
- </p>
- </div>
- <div class="detail-body-head-right">
- <el-button
- type="primary"
- title="查看上一个"
- :disabled="holding"
- @click="changeStudent(0)"
- >
- <i class="icon icon-arrow-left"></i>
- </el-button>
- <el-button
- type="primary"
- title="查看下一个"
- :disabled="holding"
- @click="changeStudent(1)"
- >
- <i class="icon icon-arrow-right"></i>
- </el-button>
- </div>
- </div>
- <div class="warning-detail-main">
- <div class="warning-action">
- <div class="student-avatar">
- <img
- :src="detailInfo.basePhotoPath"
- :alt="detailInfo.examStudentName"
- v-if="detailInfo.basePhotoPath"
- />
- <div class="avatar-default" v-else>
- <i class="el-icon-user-solid"></i>
- </div>
- <div class="avatar-title">学生底照</div>
- </div>
- <div class="warning-summary">
- <div class="warning-summary-row">
- <p class="warning-summary-col">
- <i class="icon icon-bell"></i>
- <span class="line-name"
- >系统预警
- <em :class="{ 'color-danger': detailInfo.warningCount > 0 }"
- >{{ detailInfo.warningCount }}次</em
- ></span
- >
- </p>
- <p class="warning-summary-col">
- <i class="icon icon-face"></i>
- <span class="line-name"
- >陌生人脸
- <em>{{ detailInfo.multipleFaceCount }}次</em>
- </span>
- </p>
- </div>
- <div class="warning-summary-row">
- <p class="warning-summary-col">
- <i class="icon icon-info"></i>
- <span class="line-name"
- >异常处理
- <em>{{ detailInfo.exceptionCount }}次</em>
- </span>
- </p>
- <p class="warning-summary-col">
- <i class="icon icon-success"></i>
- <span class="line-name"
- >违纪状态
- <em :class="{ 'color-danger': isBreach }">
- {{ isBreach ? "违纪" : "正常" }}</em
- >
- </span>
- </p>
- </div>
- <div class="summary-bg">
- <div class="summary-bg-line"></div>
- <div class="summary-bg-line"></div>
- <div class="summary-bg-spin"></div>
- <div class="summary-bg-spin"></div>
- <div class="summary-bg-spin"></div>
- <div class="summary-bg-spin"></div>
- </div>
- </div>
- <div class="action-list">
- <div v-if="actionValid" class="action-item">
- <el-button
- icon="icon icon-text-message"
- size="mideum"
- @click="toSendTextMsg"
- >文字提醒</el-button
- >
- </div>
- <div v-if="actionValid" class="action-item">
- <el-button
- icon="icon icon-record"
- size="mideum"
- @click="toSendAudioMsg"
- >录音提醒</el-button
- >
- </div>
- <div
- v-if="detailInfo.monitorVideoSource && actionValid"
- class="action-item"
- >
- <el-button
- icon="icon icon-call"
- size="mideum"
- :loading="holding"
- @click="answer(0)"
- >语音通话</el-button
- >
- </div>
- <div
- v-if="detailInfo.monitorVideoSource && actionValid"
- class="action-item"
- >
- <el-button
- icon="icon icon-media"
- size="mideum"
- :loading="holding"
- @click="answer(1)"
- >视频通话</el-button
- >
- </div>
- <div class="action-item">
- <el-button
- icon="icon icon-info-danger"
- size="mideum"
- @click="toBreach"
- >{{ isBreach ? "撤销违纪" : "违纪处理" }}</el-button
- >
- </div>
- <div
- v-if="detailInfo.statusCode === 'ANSWERING'"
- class="action-item"
- >
- <el-button
- icon="icon icon-paper-danger"
- size="mideum"
- @click="toFinish"
- >强制收卷</el-button
- >
- </div>
- </div>
- </div>
- <div class="warning-content">
- <div class="warning-videos">
- <div
- v-for="item in viewVideos"
- :key="item.source"
- class="student-video-item"
- >
- <div class="student-video-container">
- <div class="student-video-tips">{{ item.name }}</div>
- <flv-media
- :ref="item.ref"
- :live-url="item.liveUrl"
- @muted-change="videoAllMuted"
- ></flv-media>
- </div>
- </div>
- </div>
- <!-- track -->
- <div class="warning-track">
- <h3 class="warning-track-title">
- <i class="icon icon-track"></i>考试轨迹
- </h3>
- <div
- class="warning-track-item"
- v-for="log in detailInfo.examStudentLogList"
- :key="log.id"
- >
- <div
- :class="[
- 'warning-track-type',
- log.viewType === 'common' ? 'type-common' : 'type-exception',
- ]"
- >
- <i :class="['icon', `icon-track-${log.viewType}`]"></i>
- </div>
- <div class="warning-track-body">
- <div class="warning-track-info">
- <h3>{{ log.title }}</h3>
- <p v-if="log.desc">{{ log.desc }}</p>
- <p>
- 时间段:
- <span v-if="log.startTime">{{ log.startTime }} ~ </span>
- <span>{{ log.endTime }}</span>
- </p>
- <p v-if="log.durationTime">
- 持续时长约:{{ log.durationTime }}
- </p>
- </div>
- <ul class="warning-track-media" v-if="log.photos">
- <li v-for="(photo, pindex) in log.photos" :key="pindex">
- <img :src="photo" @click="toViewImg(photo)" />
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- student-breach-dialog -->
- <student-breach-dialog
- :instance="curDetail"
- @modified="breachFinish"
- ref="StudentBreachDialog"
- ></student-breach-dialog>
- <!-- warning-text-message-dialog -->
- <warning-text-message-dialog
- :record-id="examRecordId"
- ref="WarningTextMessageDialog"
- ></warning-text-message-dialog>
- <!-- audio-record-dialog -->
- <audio-record-dialog
- :record-id="examRecordId"
- ref="AudioRecordDialog"
- ></audio-record-dialog>
- <!-- image-preview -->
- <simple-image-preview
- :cur-image="curImage"
- ref="SimpleImagePreview"
- ></simple-image-preview>
- <!-- 通话弹出层 -->
- <div
- v-if="dialogVisible"
- class="communication-dialog"
- v-move-ele.prevent.stop
- >
- <div class="communication-box" v-show="!isWaiting">
- <div class="communication-host" id="communication-host"></div>
- <div class="communication-guest" id="communication-guest"></div>
- <div class="communication-action" @mousedown.stop>
- <el-button round type="danger" @click.stop="hangup"
- >结束通话</el-button
- >
- </div>
- <div class="communication-info">
- <span>持续时长:<second-timer ref="SecondTimer"></second-timer></span>
- </div>
- </div>
- <div class="communication-wait" v-show="isWaiting">
- <p class="communication-wait-tips">等待接听…</p>
- <div class="communication-wait-avatar">
- <img
- :src="detailInfo.basePhotoPath"
- :alt="detailInfo.examStudentName"
- />
- </div>
- <p class="communication-wait-username">
- {{ detailInfo.examStudentName }}
- </p>
- <div class="communication-wait-action" @mousedown.stop>
- <el-button round type="danger" @click="hangup">取消通话</el-button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import {
- checkSystemRequirements,
- createClient,
- createStream,
- } from "@/plugins/trtc";
- import {
- invigilateDetail,
- invigilateFinish,
- communicationCalling,
- communicationOver,
- warningStudentDetail,
- getUserMonitorKey,
- } from "@/api/invigilation";
- import FlvMedia from "../common/FlvMedia";
- import StudentBreachDialog from "./StudentBreachDialog";
- import WarningTextMessageDialog from "./WarningTextMessageDialog";
- import AudioRecordDialog from "./audioRecord/AudioRecordDialog";
- import SimpleImagePreview from "@/components/imagePreview/SimpleImagePreview";
- import SecondTimer from "../common/SecondTimer";
- import {
- formatDate,
- timeNumberToText,
- objTypeOf,
- snakeToHump,
- } from "@/utils/utils";
- import MoveEle from "@/plugins/move-ele";
- import { mapState } from "vuex";
- const domEmpty = (dom) => {
- dom.childNodes.forEach((childNode) => {
- dom.removeChild(childNode);
- });
- };
- export default {
- name: "WarningDetail",
- components: {
- FlvMedia,
- StudentBreachDialog,
- WarningTextMessageDialog,
- AudioRecordDialog,
- SimpleImagePreview,
- SecondTimer,
- },
- directives: { MoveEle },
- data() {
- return {
- examRecordId: this.$route.params.examRecordId,
- autoAnswerInfo: null,
- detailInfo: {},
- curDetail: {},
- serialIds: [],
- exceptionSummary: [],
- viewVideos: [],
- viewVideoReady: false,
- holding: false,
- // communication
- userMonitor: {},
- client: null,
- localStream: null,
- dialogVisible: false,
- isWaiting: false,
- subscribeSetTs: [],
- loopRunning: false,
- loopSetTs: [],
- isHandup: false,
- curImage: { imgSrc: "" },
- };
- },
- computed: {
- ...mapState("invigilation", ["detailIds", "liveDomains"]),
- isBreach() {
- return (
- !this.detailInfo.breachStatus && this.detailInfo.breachStatus !== null
- );
- },
- actionValid() {
- return (
- this.detailInfo.statusCode &&
- ["FIRST_PREPARE", "ANSWERING"].includes(this.detailInfo.statusCode)
- );
- },
- },
- watch: {
- $route: {
- handler() {
- this.initData();
- },
- },
- },
- mounted() {
- const autoAnswerInfo = window.sessionStorage.getItem("autoAnswerInfo");
- this.autoAnswerInfo = autoAnswerInfo ? JSON.parse(autoAnswerInfo) : null;
- this.initData();
- },
- methods: {
- async initData() {
- this.examRecordId = this.$route.params.examRecordId;
- await this.getInvigilateDetail().catch(() => {});
- await this.getStudentVideo().catch(() => {});
- this.holding = false;
- // 学生正在考试,开启定时更新
- // 自动更新新增正在候考阶段
- if (["FIRST_PREPARE", "ANSWERING"].includes(this.detailInfo.statusCode)) {
- this.loopRunning = true;
- this.clearLoopSetTs();
- this.loopSetTs.push(
- setTimeout(() => {
- this.timerUpdatePage();
- }, 10 * 1000)
- );
- } else {
- this.loopRunning = false;
- this.clearLoopSetTs();
- }
- // 自动应答
- if (this.autoAnswerInfo) {
- this.autoAnswer();
- }
- },
- clearSubscribeSetTs() {
- if (!this.subscribeSetTs.length) return;
- this.subscribeSetTs.forEach((sett) => {
- clearTimeout(sett);
- });
- this.subscribeSetTs = [];
- },
- clearLoopSetTs() {
- if (!this.loopSetTs.length) return;
- this.loopSetTs.forEach((sett) => {
- clearTimeout(sett);
- });
- this.loopSetTs = [];
- },
- async timerUpdatePage() {
- this.clearLoopSetTs();
- if (!this.loopRunning) return;
- await this.getInvigilateDetail().catch(() => {});
- this.loopSetTs.push(
- setTimeout(() => {
- this.timerUpdatePage();
- }, 10 * 1000)
- );
- },
- async getStudentVideo() {
- const res = await warningStudentDetail({
- examRecordId: this.examRecordId,
- });
- const orderSources = [
- "CLIENT_CAMERA",
- "CLIENT_SCREEN",
- "MOBILE_FIRST",
- "MOBILE_SECOND",
- ];
- const sourceNames = {
- CLIENT_CAMERA: "电脑摄像头",
- CLIENT_SCREEN: "考生屏幕",
- MOBILE_FIRST: "手机主机位",
- MOBILE_SECOND: "手机辅机位",
- };
- let records = {};
- res.data.data.forEach((item, index) => {
- const domain = this.liveDomains[index] || this.liveDomains[0];
- item.liveUrl = item.liveUrl
- ? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
- : "";
- item.name = sourceNames[item.source];
- item.ref = snakeToHump(item.source) + "Video";
- records[item.source] = item;
- });
- this.viewVideos = [];
- orderSources.forEach((source) => {
- if (records[source]) {
- this.viewVideos.push(records[source]);
- }
- });
- // 展示所有
- // this.viewVideos = orderSources.map((source) => {
- // return (
- // records[source] || {
- // liveUrl: null,
- // source,
- // name: sourceNames[source],
- // ref: snakeToHump(source) + "Video",
- // }
- // );
- // });
- this.initSubscribeVideo();
- },
- async getInvigilateDetail() {
- const res = await invigilateDetail(this.examRecordId);
- this.detailInfo = res.data.data;
- this.detailInfo.examStudentLogList = this.parseStudentLogs(
- this.detailInfo.examStudentLogList
- );
- this.exceptionSummary = this.detailInfo.examStudentLogList
- .filter((item) => item.viewType === "warning")
- .slice(0, 3);
- },
- parseStudentLogs(examStudentLogList) {
- const statusTypes = {
- common: [
- "FIRST_START",
- "RESUME_START",
- "IN_PROCESS",
- "PREPARE",
- "ANSWERING",
- "BREAK_OFF",
- "RESUME_PREPARE",
- "FINISHED",
- "FIRST_PREPARE",
- ],
- warning: [
- "FACE_COUNT_ERROR",
- "FACE_COMPARE_ERROR",
- "EYE_CLOSE_ERROR",
- "LIVENESS_ACTION_ERROR",
- "REALNESS",
- "NONE",
- // exception:
- "NET_TIME_OUT",
- "MACHING_STOP",
- "NET_TIME_BREAK",
- "SOFTWARE_STOP",
- "POWER_CUT",
- "BREACH_HANDLE",
- "BREACH_REVOKE",
- ],
- };
- const transformInfo = {
- FIRST_START: "身份识别",
- ANSWERING: "进入考试",
- RESUME_START: "身份识别",
- };
- let statusTypeMap = {};
- Object.keys(statusTypes).map((k) => {
- statusTypes[k].map((item) => {
- statusTypeMap[item] = k;
- });
- });
- const logs = examStudentLogList.map((item) => {
- let info = { ...item };
- info.endTime = formatDate("HH:mm:ss", new Date(info.createTime));
- info.viewType = statusTypeMap[info.type] || "common";
- const content = info.info.split(/【|】/);
- if (content.length === 3) {
- info.title = content[1];
- info.desc = content[2];
- } else {
- info.title = transformInfo[info.type] || content[0];
- }
- if (info.remark && info.remark.includes('{"')) {
- info.remark = JSON.parse(info.remark);
- if (info.remark["MIN_CREATE_TIME"]) {
- info.startTime = formatDate(
- "HH:mm:ss",
- new Date(info.remark["MIN_CREATE_TIME"])
- );
- info.durationTime = timeNumberToText(
- info.createTime - info.remark["MIN_CREATE_TIME"]
- );
- }
- let photos = [];
- Object.keys(info.remark).map((key) => {
- if (key.includes("PHOTO")) {
- const kPhotos =
- objTypeOf(info.remark[key]) === "array"
- ? info.remark[key]
- : [info.remark[key]];
- photos = [...photos, ...kPhotos];
- }
- });
- info.photos = photos;
- } else if (info.updateTime) {
- info.startTime = formatDate("HH:mm:ss", new Date(info.createTime));
- info.endTime = formatDate("HH:mm:ss", new Date(info.updateTime));
- info.durationTime = timeNumberToText(
- info.updateTime - info.createTime
- );
- }
- return info;
- });
- return logs;
- },
- changeStudent(type) {
- let index = this.detailIds.indexOf(this.examRecordId);
- if (type) {
- if (index >= this.detailIds.length - 1) {
- this.$message.error("当前没有下一个学生了");
- return;
- }
- index++;
- } else {
- if (index <= 0) {
- this.$message.error("当前没有上一个学生了");
- return;
- }
- index--;
- }
- if (this.holding) return;
- this.holding = true;
- this.closeSubscribeVideo();
- console.log(this.detailIds[index]);
- this.$router.replace({
- name: "WarningDetail",
- params: {
- examRecordId: this.detailIds[index],
- },
- });
- },
- toBreach() {
- this.curDetail = {
- examStudentName: this.detailInfo.examStudentName,
- identity: this.detailInfo.identity,
- courseNameCode: this.detailInfo.courseNameCode,
- description: "",
- examRecordId: [this.detailInfo.examRecordId],
- breachStatus: this.detailInfo.breachStatus,
- status: this.detailInfo.breachStatus ? 0 : 1,
- // 状态,0:新建,1:撤销
- // 违纪状态:正常(1)=>新建,违纪(0)=>撤销
- type: "",
- };
- this.$refs.StudentBreachDialog.open();
- },
- async toFinish() {
- const result = await this.$confirm(
- "试卷若被强制回收,考试再无法重置继续完成考试,请您慎重选择!您确定要强制回收改考试试卷吗?",
- "强制收卷确认提醒",
- {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- iconClass: "el-icon-warning",
- customClass: "el-message-box__error",
- }
- ).catch(() => {});
- if (!result) return;
- await invigilateFinish({
- examRecordId: [this.detailInfo.examRecordId],
- type: "INTERRUPT",
- });
- this.$message.success("强制收卷成功!");
- this.goBack();
- },
- toSendTextMsg() {
- this.$refs.WarningTextMessageDialog.open();
- },
- toSendAudioMsg() {
- this.$refs.AudioRecordDialog.open();
- },
- breachFinish() {
- this.getInvigilateDetail();
- },
- // video relative
- notifyError(content) {
- this.$notify({
- type: "error",
- message: content,
- });
- },
- async initClient(examRecordId) {
- const res = await getUserMonitorKey(examRecordId);
- this.userMonitor = res.data.data;
- this.client = createClient({
- mode: "live",
- sdkAppId: this.userMonitor.appId * 1,
- userId: this.userMonitor.monitorUserId,
- userSig: this.userMonitor.monitorUserSig,
- useStringRoomId: true,
- });
- },
- 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 autoAnswer() {
- await this.answer(this.autoAnswerInfo.isVideo);
- // 更改学生的通话申请状态
- await communicationCalling({
- recordId: this.examRecordId,
- source: this.autoAnswerInfo.source,
- });
- },
- async answer(isVideo) {
- const result = await checkSystemRequirements().catch(() => {
- this.$message.error(
- `您的浏览器不支持当前音视频通讯版本。建议使用最新版的chrome浏览器!`
- );
- });
- if (!result) return;
- // 客户端两路视频公用一个userId:
- // main:有音频,有视频
- // auxiliary:无音频,有视频
- // 手机端userId各不同
- if (this.holding) return;
- this.holding = true;
- this.videoAllMuted();
- await this.initClient(this.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);
- console.log(event.stream.getUserId(), this.userMonitor.sourceUserId);
- const remoteStream = event.stream;
- if (remoteStream.getUserId() !== this.userMonitor.sourceUserId) return;
- if (remoteStream.getType() !== "main") return;
- console.log(`有效视频${remoteStream.getUserId()},准备订阅`);
- if (this.autoAnswerInfo) {
- // 存在自动应答信息时,不再延迟订阅学生音视频流
- this.client
- .subscribe(remoteStream, { audio: true, video: true })
- .catch((error) => {
- console.log(`${remoteStream.getUserId()}视频订阅失败!`, error);
- this.notifyError("学生视频获取失败!");
- });
- } else {
- // 延迟订阅视频
- this.subscribeSetTs.push(
- setTimeout(() => {
- this.client
- .subscribe(remoteStream, { audio: true, video: true })
- .catch((error) => {
- console.log(
- `${remoteStream.getUserId()}视频订阅失败!`,
- error
- );
- this.notifyError("学生视频获取失败!");
- });
- }, 5000)
- );
- }
- });
- this.client.on("stream-subscribed", (event) => {
- const remoteStream = event.stream;
- console.log(event);
- console.log(`${remoteStream.getUserId()}视频已订阅!`);
- this.isWaiting = false;
- this.$nextTick(() => {
- if (!this.$refs.SecondTimer.recoding) this.$refs.SecondTimer.start();
- domEmpty(document.getElementById("communication-host"));
- remoteStream.play("communication-host", { objectFit: "contain" });
- });
- });
- this.client.on("stream-removed", (event) => {
- const remoteStream = event.stream;
- if (
- remoteStream.getUserId() !== this.userMonitor.sourceUserId ||
- remoteStream.getType() !== "main" ||
- this.isHandup
- )
- return;
- console.log(event);
- console.log(`${remoteStream.getUserId()}已退出房间!`);
- this.notifyError("对方已挂断!");
- this.hangup();
- });
- // 加入房间
- let roomJoinResult = true;
- await this.client
- .join({
- roomId: this.userMonitor.monitorKey,
- role: "audience",
- })
- .catch((error) => {
- roomJoinResult = false;
- console.log("加入房间失败!", error);
- this.notifyError("发起通信失败!");
- });
- if (!roomJoinResult) return;
- console.log("加入房间成功!");
- // 切换角色,连麦互动
- let switchResult = true;
- await this.client.switchRole("anchor").catch((error) => {
- console.log("切换角色失败!", error);
- this.notifyError("角色错误!");
- switchResult = false;
- });
- if (!switchResult) return;
- // 发布本地视频
- let publishStreamResult = true;
- this.client.publish(this.localStream).catch((error) => {
- console.log("发布本地视频失败!", error);
- this.notifyError("本地音视频推送失败!");
- publishStreamResult = false;
- });
- if (!publishStreamResult) return;
- console.log("发布本地音视频成功!");
- // 播放本地视频
- this.localStream.play("communication-guest", { muted: true });
- this.isHandup = false;
- },
- async hangup() {
- if (this.isHandup) return;
- this.isHandup = true;
- this.clearSubscribeSetTs();
- this.$refs.SecondTimer.end();
- // 取消发布本地视频
- await this.client.unpublish(this.localStream).catch((error) => {
- console.log("取消发布本地视频失败!", error);
- });
- this.localStream.close();
- this.localStream = null;
- // 离开房间
- let result = true;
- await this.client.leave().catch((error) => {
- console.log("离开房间失败!", error);
- this.notifyError("操作异常,请重新尝试!");
- result = false;
- });
- if (!result) return;
- this.client.off("*");
- this.client = null;
- this.userMonitor = {};
- this.dialogVisible = false;
- this.isWaiting = true;
- // this.initSubscribeVideo();
- if (this.autoAnswerInfo) {
- // 结束学生的通话
- await communicationOver({
- recordId: this.examRecordId,
- source: this.autoAnswerInfo.source,
- }).catch(() => {
- console.log("结束通话状态异常!");
- });
- this.goBack();
- }
- },
- initSubscribeVideo() {
- this.viewVideoReady = true;
- },
- closeSubscribeVideo() {
- this.viewVideoReady = false;
- },
- videoAllMuted() {
- this.viewVideos
- .filter((vv) => vv.liveUrl)
- .forEach((vv) => {
- this.$refs[vv.ref][0].mutedPlayer(true);
- });
- },
- toViewImg(photo) {
- this.curImage = { imgSrc: photo };
- this.$refs.SimpleImagePreview.open();
- },
- goBack() {
- window.history.go(-1);
- },
- },
- beforeDestroy() {
- window.sessionStorage.removeItem("autoAnswerInfo");
- this.loopRunning = false;
- this.clearLoopSetTs();
- this.clearSubscribeSetTs();
- if (this.client) {
- this.client.leave();
- this.client.off("*");
- }
- },
- };
- </script>
|