|
@@ -0,0 +1,546 @@
|
|
|
+<template>
|
|
|
+ <BaseLive>
|
|
|
+ <div class="teacher">
|
|
|
+ <div class="middle">
|
|
|
+ <!-- 教师学员列表 -->
|
|
|
+ <div :class="['live-list', { 'only-teacher': studentList.length <= 0 }]">
|
|
|
+ <div class="live-list-item">
|
|
|
+ <div class="portrait">
|
|
|
+ <span>{{ roomInfo.teacher_name }}</span>
|
|
|
+ </div>
|
|
|
+ <span class="teacher-card">教师</span>
|
|
|
+ <span class="name">
|
|
|
+ {{ roomInfo.teacher_name }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="{ student_id, student_name, student_image_url } in studentList"
|
|
|
+ :key="student_id"
|
|
|
+ class="live-list-item"
|
|
|
+ >
|
|
|
+ <el-avatar :size="122" :src="student_image_url" />
|
|
|
+ <span class="student-card">学生</span>
|
|
|
+ <span class="name">
|
|
|
+ {{ student_name }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 直播视频 -->
|
|
|
+ <div id="live" class=""></div>
|
|
|
+
|
|
|
+ <!-- 聊天 -->
|
|
|
+ <div v-show="chatShow" class="chat">
|
|
|
+ <div class="title">聊天</div>
|
|
|
+ <div class="chat-main"></div>
|
|
|
+ <div class="msg"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 成员列表 -->
|
|
|
+ <div v-show="memberShow" class="member-list">
|
|
|
+ <div class="title">成员管理</div>
|
|
|
+ <div
|
|
|
+ v-for="{ student_id, student_name, student_image_url } in studentList"
|
|
|
+ :key="student_id"
|
|
|
+ :title="student_image_url"
|
|
|
+ >
|
|
|
+ <span>{{ student_name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="bottom">
|
|
|
+ <div class="operation">
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/voice-off.png" alt="" />
|
|
|
+ <span>解除静音</span>
|
|
|
+ </div>
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/monitor-off.png" alt="" />
|
|
|
+ <span>开启视频</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="operation">
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/laptop-computer.png" alt="" />
|
|
|
+ <span>共享屏幕</span>
|
|
|
+ </div>
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/folder-upload.png" alt="" />
|
|
|
+ <span>课件推送</span>
|
|
|
+ </div>
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/every-user.png" alt="" />
|
|
|
+ <span>分组讨论</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span class="line"></span>
|
|
|
+
|
|
|
+ <div class="operation-item" @click="toggle">
|
|
|
+ <img src="@/assets/live/communication.png" alt="" />
|
|
|
+ <span>聊天</span>
|
|
|
+ </div>
|
|
|
+ <div class="operation-item" @click="memberToggle">
|
|
|
+ <img src="@/assets/live/peoples.png" alt="" />
|
|
|
+ <span>成员列表</span>
|
|
|
+ </div>
|
|
|
+ <div class="operation-item">
|
|
|
+ <img src="@/assets/live/recording.png" alt="" />
|
|
|
+ <span>录制</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="operation">
|
|
|
+ <button class="live-button" @click="() => (liveStat ? closeLiveRoom() : startLive())">
|
|
|
+ {{ liveStat ? '结束直播' : '开始直播' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </BaseLive>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'TeacherLive'
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {
|
|
|
+ CloseLiveRoom,
|
|
|
+ StudentExitLiveRoom,
|
|
|
+ StartGroup,
|
|
|
+ GetGroupStatus,
|
|
|
+ DealStudentConnection,
|
|
|
+ GetStudentInfo_Connection,
|
|
|
+ GetLiveRoomData_DRTD,
|
|
|
+ GetLiveRoomInfo
|
|
|
+} from '@/api/live';
|
|
|
+import { ref, provide, inject, onMounted, onBeforeUnmount } from 'vue';
|
|
|
+import { Loading, Message, MessageBox } from 'element-ui';
|
|
|
+import { useRoute, useRouter } from 'vue-router/composables';
|
|
|
+import { useChat, useMemberList, useDownloadSDK, useLive, rtc } from '../index';
|
|
|
+import { useTeacherLive } from './live';
|
|
|
+import store from '@/store';
|
|
|
+
|
|
|
+import BaseLive from '../index.vue';
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+const route = useRoute();
|
|
|
+const task_id = route.query.task_id;
|
|
|
+provide('task_id', task_id);
|
|
|
+const $t = inject('$t');
|
|
|
+
|
|
|
+const { msg, chatShow, sendMsg, toggle } = useChat(rtc);
|
|
|
+const { memberShow, toggle: memberToggle } = useMemberList(rtc);
|
|
|
+const { startLive, stopLive } = useTeacherLive(rtc);
|
|
|
+
|
|
|
+const isTeacher = store.getters.isTeacher;
|
|
|
+
|
|
|
+// 连麦
|
|
|
+let connect = ref(false);
|
|
|
+// 连线学员信息
|
|
|
+let connectStudent = ref({});
|
|
|
+// 等待接通
|
|
|
+let callLoading = ref(false);
|
|
|
+let dialogVisible = ref(false);
|
|
|
+// 学员完成
|
|
|
+let dialogVisibleComplete = ref(false);
|
|
|
+// 定时器
|
|
|
+let timer = ref(null);
|
|
|
+let remoteStreamType = ref(-1);
|
|
|
+let roomData = ref({
|
|
|
+ desc: '直播间标题',
|
|
|
+ name: '姓名',
|
|
|
+ user: {
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ role: 'talker',
|
|
|
+ rommid: ''
|
|
|
+ },
|
|
|
+ max_users: 1,
|
|
|
+ allow_chat: true,
|
|
|
+ allow_audio: true,
|
|
|
+ allow_speak: true
|
|
|
+});
|
|
|
+let roomInfo = ref({
|
|
|
+ room_id: '',
|
|
|
+ video_mode: 1,
|
|
|
+ task_name: '',
|
|
|
+ cs_item_name: '',
|
|
|
+ course_name: '',
|
|
|
+ teacher_name: '',
|
|
|
+ student_count: 0,
|
|
|
+ student_connection_info: {}
|
|
|
+});
|
|
|
+let loadedNumber = ref(0);
|
|
|
+let speakData = ref({});
|
|
|
+let roomContext = ref({});
|
|
|
+let chatList = ref([]);
|
|
|
+let isDrawSetting = ref(false);
|
|
|
+let curColor = ref('#343434');
|
|
|
+let drawColorList = ['#FF4747', '#343434', '#628EFF', '#FFCA0E'];
|
|
|
+let drawThicknessList = ['1', '3', '5'];
|
|
|
+// 直播间学员列表
|
|
|
+let student_list = ref([]);
|
|
|
+// 直播状态
|
|
|
+let liveStat = ref(false);
|
|
|
+let liveMenuShow = ref(false);
|
|
|
+// 分组讨论
|
|
|
+let groupNumList = ref([]);
|
|
|
+let group_count = ref(1);
|
|
|
+// 网络整体情况
|
|
|
+let netStatus = ref(0);
|
|
|
+let dialogVisibleGroup = ref(false);
|
|
|
+let dialogVisibleDevice = ref(false);
|
|
|
+let isRecreate = ref(false);
|
|
|
+let device = ref({
|
|
|
+ video: [],
|
|
|
+ audio: []
|
|
|
+});
|
|
|
+// 本地视频流画面、声音
|
|
|
+let hasVideo = ref(false);
|
|
|
+let hasAudio = ref(false);
|
|
|
+let material_list = ref([]);
|
|
|
+let materialId = ref('');
|
|
|
+let materialType = ref('');
|
|
|
+let docListInfo = ref(null);
|
|
|
+
|
|
|
+const { downloadWebSDK } = useDownloadSDK();
|
|
|
+
|
|
|
+const { init, getLiveStat, closeVideo, pauseVideo, playVideo, pauseAudio, playAudio } = useLive();
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getLiveRoomData_DRTDPolling();
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ clearInterval(timer.value);
|
|
|
+ closeVideo('main');
|
|
|
+});
|
|
|
+
|
|
|
+/**
|
|
|
+ * 得到分组状态
|
|
|
+ */
|
|
|
+let loadingInstance = null;
|
|
|
+GetGroupStatus({ task_id }).then(({ is_enable_group }) => {
|
|
|
+ if (is_enable_group === 'true') {
|
|
|
+ router.push({
|
|
|
+ path: '/live/teacher/group',
|
|
|
+ query: {
|
|
|
+ task_id
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ loadingInstance = Loading.service({
|
|
|
+ text: $t('Key425'),
|
|
|
+ background: '#fff'
|
|
|
+ });
|
|
|
+ loadingInstance;
|
|
|
+ downloadWebSDK(initSDK);
|
|
|
+ getLiveRoomData_DRTD();
|
|
|
+ getLiveRoomInfo();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+function initSDK() {
|
|
|
+ const { live_room_sys_user_id, room_id, session_id } = route.query;
|
|
|
+ init({
|
|
|
+ userid: live_room_sys_user_id,
|
|
|
+ roomid: room_id,
|
|
|
+ sessionid: session_id
|
|
|
+ });
|
|
|
+ // common.initListener(this); // 注册监听事件
|
|
|
+ getLiveStat({
|
|
|
+ success: (data) => {
|
|
|
+ liveStat.value = data.started;
|
|
|
+ },
|
|
|
+ fail: (str) => {
|
|
|
+ liveStat.value = false;
|
|
|
+ console.log('直播关闭状态或查询直播失败', str);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ loadingInstance.close();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取直播间学员列表
|
|
|
+ */
|
|
|
+function getLiveRoomData_DRTD() {
|
|
|
+ GetLiveRoomData_DRTD({ task_id }).then(({ student_list: sList, material_list: mList }) => {
|
|
|
+ student_list.value = sList;
|
|
|
+ material_list.value = mList;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function getLiveRoomData_DRTDPolling() {
|
|
|
+ timer.value = setInterval(() => {
|
|
|
+ getLiveRoomData_DRTD();
|
|
|
+ }, 5000);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取直播间信息
|
|
|
+ */
|
|
|
+function getLiveRoomInfo() {
|
|
|
+ GetLiveRoomInfo({ task_id }).then(
|
|
|
+ ({
|
|
|
+ room_id,
|
|
|
+ video_mode,
|
|
|
+ task_name,
|
|
|
+ cs_item_name,
|
|
|
+ course_name,
|
|
|
+ teacher_name,
|
|
|
+ student_count,
|
|
|
+ student_connection_info
|
|
|
+ }) => {
|
|
|
+ roomInfo.value = {
|
|
|
+ room_id,
|
|
|
+ video_mode,
|
|
|
+ task_name,
|
|
|
+ cs_item_name,
|
|
|
+ course_name,
|
|
|
+ teacher_name,
|
|
|
+ student_count,
|
|
|
+ student_connection_info
|
|
|
+ };
|
|
|
+ connectStudent.value = student_connection_info;
|
|
|
+ if (student_connection_info.connection_status === 1) {
|
|
|
+ callLoading.value = true;
|
|
|
+ }
|
|
|
+ if (student_connection_info.connection_status === 2) {
|
|
|
+ connect.value = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 本地流视频开启、关闭
|
|
|
+ */
|
|
|
+function playOrPauseVideo() {
|
|
|
+ if (device.value.video.length === 0) {
|
|
|
+ return Message.warning($t('Key399'));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasVideo.value) {
|
|
|
+ pauseVideo({
|
|
|
+ streamName: 'main',
|
|
|
+ success: () => {
|
|
|
+ Message.success($t('Key433'));
|
|
|
+ hasVideo.value = false;
|
|
|
+ },
|
|
|
+ fail: (str) => {
|
|
|
+ Message.warning(str);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ playVideo({
|
|
|
+ streamName: 'main',
|
|
|
+ success: () => {
|
|
|
+ Message.success($t('Key434'));
|
|
|
+ hasVideo.value = true;
|
|
|
+ },
|
|
|
+ fail: (str) => {
|
|
|
+ Message.warning(str);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 本地流音频开启、关闭
|
|
|
+ */
|
|
|
+function playOrPauseAudio() {
|
|
|
+ if (device.value.audio.length === 0) {
|
|
|
+ return Message.warning($t('Key401'));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasAudio.value) {
|
|
|
+ pauseAudio({
|
|
|
+ streamName: 'main',
|
|
|
+ success: () => {
|
|
|
+ Message.success($t('Key435'));
|
|
|
+ hasAudio.value = false;
|
|
|
+ },
|
|
|
+ fail: (str) => {
|
|
|
+ Message.warning(str);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ playAudio({
|
|
|
+ streamName: 'main',
|
|
|
+ success: () => {
|
|
|
+ Message.success($t('Key436'));
|
|
|
+ hasAudio.value = true;
|
|
|
+ },
|
|
|
+ fail: (str) => {
|
|
|
+ Message.warning(str);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 关闭直播间
|
|
|
+ */
|
|
|
+function closeLiveRoom() {
|
|
|
+ MessageBox.confirm('是否关闭当前直播?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ CloseLiveRoom({ task_id }).then(() => {
|
|
|
+ router.push('/');
|
|
|
+ Message.success($t('Key426'));
|
|
|
+ });
|
|
|
+ stopLive();
|
|
|
+ });
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.teacher {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ .middle {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .live-list {
|
|
|
+ display: flex;
|
|
|
+ gap: 40px;
|
|
|
+ height: 100%;
|
|
|
+ padding: 53px 65px 32px;
|
|
|
+
|
|
|
+ &.only-teacher {
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ &-item {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 16px;
|
|
|
+
|
|
|
+ .portrait {
|
|
|
+ width: 122px;
|
|
|
+ height: 122px;
|
|
|
+ line-height: 122px;
|
|
|
+ color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #7a9fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ %card,
|
|
|
+ .teacher-card {
|
|
|
+ position: absolute;
|
|
|
+ top: 105px;
|
|
|
+ left: 34px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ background-color: #ffb342;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .student-card {
|
|
|
+ @extend %card;
|
|
|
+
|
|
|
+ background-color: #c2ff42;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ %right-fixed {
|
|
|
+ position: fixed;
|
|
|
+ top: 96px;
|
|
|
+ right: 0;
|
|
|
+ width: 227px;
|
|
|
+ height: calc(100% - 192px);
|
|
|
+ background-color: #f3f3f3;
|
|
|
+ border-left: 1px solid #e5e5e5;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ height: 38px;
|
|
|
+ padding: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ border-bottom: 1px solid #e5e5e5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat {
|
|
|
+ @extend %right-fixed;
|
|
|
+
|
|
|
+ &-main {
|
|
|
+ height: calc(100% - 156px);
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .msg {
|
|
|
+ height: 118px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .member-list {
|
|
|
+ @extend %right-fixed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 96px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background-color: #f8f8f8;
|
|
|
+
|
|
|
+ .operation {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 16px;
|
|
|
+
|
|
|
+ &-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 4px;
|
|
|
+ align-items: center;
|
|
|
+ width: 80px;
|
|
|
+ height: 70px;
|
|
|
+ padding: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background-color: #e5e5e5;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .line {
|
|
|
+ width: 2px;
|
|
|
+ height: 70px;
|
|
|
+ background-color: #d0d0d0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .live-button {
|
|
|
+ height: 48px;
|
|
|
+ padding: 8px 24px;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #e52e2e;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 2px solid #e52e2e;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|