Commit 6236ec53 authored by liujiaxin's avatar liujiaxin

台上位置固定6人。下台为空闲状态

parent da303d2d
......@@ -109,7 +109,7 @@
"electron:copy:web": "cross-env REACT_APP_RUNTIME_PLATFORM=electron react-app-rewired build",
"electron:copy:electron": "cpx ./app/**/*.js ./build",
"electron:build": "npm run electron:copy:web && npm run electron:copy:electron",
"dev": "cross-env REACT_APP_RUNTIME_PLATFORM=web react-app-rewired start",
"dev": "cross-env REACT_APP_RUNTIME_PLATFORM=web react-app-rewired start ",
"build": "cross-env REACT_APP_RUNTIME_PLATFORM=web react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"analyze": "source-map-explorer 'build/static/js/*.js'",
......
......@@ -37,7 +37,7 @@ const useStyles = makeStyles({
});
const CourseWareFrame: React.FC = () => {
const classes = useStyles();
// `${ENDPOINT}/airclass_ol?token=${this.userToken}`;
const src = endPoint.getCourseUrl();
return (
<div className={classes.style1}>
......
......@@ -83,8 +83,14 @@ const DialogContainer = () => {
const onConfirm = (type: string) => {
if (type === 'exitRoom') {
// debugger
globalStore.showLoading();
roomStore.exitAll().then(() => {
globalStore.stopLoading();
globalStore.removeDialog();
history.push('/');
})
}
else if (type === 'apply') {
Promise.all([
......
......@@ -231,7 +231,8 @@ const MemberHolder: React.FC<MemberHolderProps> = ({
{account ?
<div className="user-profile">
<span className="account">{account}</span>
{me.uid === id || me.role === 'teacher' ?
{/*me.uid === id ||*/}
{me.role === 'teacher' ?
<span className="media-btn">
<Link component="button" onClick={onAudioClick}>{audio ? "静音" : "开麦"}</Link>
{me.role === 'teacher' ? <Link component="button" onClick={onStageUp}>上台</Link> : null}
......
......@@ -13,7 +13,9 @@ import { isElectron, platform } from '../utils/platform';
import { useRoomState } from '../containers/root-container';
import { roomStore } from '../stores/room';
import { globalStore } from '../stores/global';
import VoiceOverOffIcon from '@material-ui/icons/VoiceOverOff';
import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver';
import RoomToolsBar from "./room-tools-bar";
interface NavProps {
delay: string
network: string
......@@ -68,14 +70,17 @@ export function Nav ({
<span className="time">{moment.utc(time).format('HH:mm:ss')}</span>
</div>
<span className="menu-split" />
<div className={platform === 'web' ? "btn-group" : 'electron-btn-group' }>
{ roomStore.state.me.role == 'teacher' ? <RoomToolsBar onClick={handleClick} /> : null}
{/*<div className={platform === 'web' ? "btn-group" : 'electron-btn-group' }>
{platform === 'web' ? <Icon className="icon-setting" onClick={(evt: any) => {
handleClick("setting");
}}/> : null}
<Icon className="icon-exit" onClick={(evt: any) => {
handleClick("exit");
}} />
</div>
</div> */}
<NavBtn />
</div>
</div>
......
.room-tool-box{
right: 10px;
width: 46px;
border: 1px solid #dbe2e5;
bottom: 10px;
cursor: pointer;
display: flex;
z-index: 9;
position: absolute;
//background: #fff;
box-shadow: 0 2px 4px 0 rgba(0,0,0,.1);
box-sizing: border-box;
align-items: center;
line-height: 42;
user-select: none;
border-radius: 6px;
top: 10px;
flex-direction: column;
height: 90px;
padding: 3px;
background-color: rgba(51, 51, 51, 0.4);
flex-direction: row;
.tool-btn{
cursor: pointer;
display: flex;
height: 38px;
width: 38px;
margin: 2px;
height: 32px;
width: 32px;
margin: 4px;
border-radius: 4px;
background: #565656;
//background: #565656;
box-sizing: border-box;
color: #eee;
//color: #eee;
svg {
width: 38px;
height: 38px;
width: 30px;
height: 30px;
display: block;
}
}
.tool-btn:hover{
background-color: rgba(0, 0, 0, 0.07);
border-radius: 4px;
}
}
......@@ -6,9 +6,38 @@ import { VolumeOff, VolumeUp, ImportExport } from '@material-ui/icons';
import {roomStore} from "../stores/room";
import {useRoomState} from "../containers/root-container";
import './room-tools-bar.scss';
import VoiceOverOffIcon from "@material-ui/icons/VoiceOverOff";
import RecordVoiceOverIcon from "@material-ui/icons/RecordVoiceOver";
import Category from "@material-ui/icons/Category";
import ImportContacts from "@material-ui/icons/ImportContacts";
import SettingsApplicationsIcon from '@material-ui/icons/SettingsApplications';
import SettingsPowerIcon from '@material-ui/icons/SettingsPower';
import {TOOL_TYPE} from "../utils/types";
type Props = {
// zoomScale: number
// zoomChange: (scale: number) => void
onClick: (type: string) => void
// onClickBoardLock: () => void
};
export default function RoomToolsBar (props: any) {
// export const RoomToolsBar: React.FC<Props> = ({onClick}) => {
const roomState = useRoomState();
const changeTool = async () => {
console.log(roomStore.state.course.currentTool);
if (roomStore.state.course.currentTool == TOOL_TYPE.WHITEBOARD) {
await roomStore.setCurrentTool(TOOL_TYPE.COURSE_WARE)
} else {
await roomStore.setCurrentTool(TOOL_TYPE.WHITEBOARD)
}
}
const {currentTool} = useMemo(() => {
return {
currentTool: roomStore.state.course.currentTool
}
}, [roomState]);
const {muteAudio} = useMemo(() => {
return {
muteAudio: roomState.course.muteAudio
......@@ -25,11 +54,27 @@ export default function RoomToolsBar (props: any) {
const randomStageUp = async () => {
await roomStore.randomStageUp()
}
return (
<>
<div className={'room-tool-box'}>
{muteAudio ? <div className="tool-btn" onClick={audioOn}><VolumeUp></VolumeUp></div> : <div onClick={audioOff} className="tool-btn"><VolumeOff></VolumeOff></div> }
<div className="tool-btn"><ImportExport onClick={randomStageUp}></ImportExport></div>
{muteAudio
? <div className="tool-btn" onClick={audioOn}><RecordVoiceOverIcon/></div>
: <div onClick={audioOff} className="tool-btn"><VoiceOverOffIcon/></div>
}
{currentTool == TOOL_TYPE.WHITEBOARD
? <div className="tool-btn" onClick={changeTool}><ImportContacts/></div>
: <div onClick={changeTool} className="tool-btn"><Category/></div>
}
<div className="tool-btn" onClick={(evt: any) => {
props.onClick("setting");
}}><SettingsApplicationsIcon/></div>
<div className="tool-btn" onClick={(evt: any) => {
props.onClick("exit");
}}><SettingsPowerIcon/></div>
{/*<div className="tool-btn"><ImportExport onClick={randomStageUp}></ImportExport></div>*/}
</div>
</>
)
......
......@@ -18,7 +18,30 @@
z-index: 9;
}
}
.menu-close {
cursor: pointer;
width: 18px;
top: 15px;
right: 15px;
position: absolute;
background-size: 18px;
height: 18px;
border-radius: 8px;
&:hover {
background: rgba(0, 0, 0, 0.07);
}
&::after {
border-radius: 8px;
display: block;
width: 18px;
height: 18px;
content: ' ';
background-position: center;
background-repeat: no-repeat;
background-size: 18px;
background-image: url('../assets/icon-resource-menu-close.png');
}
}
.video-profile {
z-index: 3;
width: 100%;
......
......@@ -4,6 +4,21 @@ import './video-player.scss';
import { AgoraElectronStream, StreamType, nativeRTCClient as nativeClient } from '../utils/agora-electron-client';
import { useRoomState } from '../containers/root-container';
import { platform } from '../utils/platform';
import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
import {makeStyles} from '@material-ui/core/styles';
const useStyles = makeStyles({
stageDown : {
display: 'flex',
width: 18,
height: 24,
color: '#fff',
lineHeight: 24,
opacity: .8,
cursor:'pointer'
}
});
const contentMode = 0;
......@@ -41,6 +56,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
handleClose,
close
}) => {
const style = useStyles();
const loadVideo = useRef<boolean>(false);
const loadAudio = useRef<boolean>(false);
......@@ -219,6 +235,12 @@ const onAudioClick = (evt: any) => {
handleClick('audio', streamID, id);
}
}
const onStageDownClick = (evt: any) => {
if (handleClick && id) {
handleClick('stageDown', streamID, id);
}
}
const onVideoClick = (evt: any) => {
if (handleClick && id) {
......@@ -242,17 +264,30 @@ return (
account ?
<div className="video-profile">
<span className="account">{account}</span>
{me.uid === id || me.role === 'teacher' ?
<span className="media-btn">
<Icon onClick={onAudioClick} className={audio ? "icon-speaker-on" : "icon-speaker-off"} data={"audio"} />
<Icon onClick={onVideoClick} className={video ? "icons-camera-unmute-s" : "icons-camera-mute-s"} data={"video"} />
</span> :
<span className="media-btn">
<Icon className={audio ? "icon-speaker-on disabled" : "icon-speaker-off disabled"} data={"audio"} />
<Icon className={video ? "icons-camera-unmute-s disabled" : "icons-camera-mute-s disabled"} data={"video"} />
{streamID <= 0 ? null :
<div>
{
me.uid === id || me.role === 'teacher'
? <span className="media-btn">
{me.role === 'teacher' && me.uid !== id ?
<ArrowDownwardIcon onClick={onStageDownClick} className={style.stageDown}></ArrowDownwardIcon>
: null}
<Icon onClick={onAudioClick} className={audio ? "icon-speaker-on" : "icon-speaker-off"}
data={"audio"}/>
<Icon onClick={onVideoClick} className={video ? "icons-camera-unmute-s" : "icons-camera-mute-s"}
data={"video"}/>
</span>
: <span className="media-btn">
<Icon className={audio ? "icon-speaker-on disabled" : "icon-speaker-off disabled"} data={"audio"}/>
<Icon className={video ? "icons-camera-unmute-s disabled" : "icons-camera-mute-s disabled"}
data={"video"}/>
</span>
}</div>
}
</div>
: null
}
......
......@@ -5,6 +5,7 @@ import { WhiteboardState, whiteboard } from '../stores/whiteboard';
import { useHistory, useLocation } from 'react-router-dom';
import { resolveMessage, resolvePeerMessage, resolveChannelAttrs, jsonParse } from '../utils/helper';
import GlobalStorage from '../utils/custom-storage';
import {endPoint} from "../services/agora-end-points";
export type IRootProvider = {
globalState: GlobalState
......@@ -105,9 +106,17 @@ export const RootProvider: React.FC<any> = ({children}) => {
}).catch(console.warn);
});
rtmClient.on("AttributesUpdated", (attributes: object) => {
if (Object.keys(attributes).length == 0) {
globalStore.showToast({
type: 'rtmClient',
message: 'teacher close classroom'
});
history.push('/');
return;
}
const channelAttrs = resolveChannelAttrs(attributes);
// console.log('[rtm-client] updated resolved attrs', channelAttrs);
// console.log('[rtm-client] updated origin attributes', attributes);
console.log('[rtm-client] updated origin attributes', attributes);
roomStore.updateRoomAttrs(channelAttrs)
});
rtmClient.on("MemberJoined", (memberId: string) => {
......@@ -152,6 +161,8 @@ export const RootProvider: React.FC<any> = ({children}) => {
me: room.me,
course: room.course,
mediaDevice: room.mediaDevice,
cwLink: endPoint.userToken,
studentsOrder: room.studentsOrder
});
// WARN: DEBUG ONLY MUST REMOVED IN PRODUCTION
//@ts-ignore
......
......@@ -143,17 +143,67 @@ export default function useStream () {
console.log(100000, studentsOrder);
console.log(11111111, studentIds.toArray());
studentIds = studentIds.sort(function(a, b){
if (studentsOrder.indexOf(+a) < 0) {return 0}
if (studentsOrder.indexOf(+b) < 0) {return 0}
return studentsOrder.indexOf(+a) - studentsOrder.indexOf(+b);
});
console.log(222222, studentIds.toArray());
// studentIds = studentIds.sort(function(a, b){
// if (studentsOrder.indexOf(+a) < 0) {return 0}
// if (studentsOrder.indexOf(+b) < 0) {return 0}
// return studentsOrder.indexOf(+a) - studentsOrder.indexOf(+b);
// });
const sids: any = [];
for (const ouid of studentsOrder) {
if (ouid == 0) {
sids.push(0)
} else {
// find in peer
const uid = studentIds.find((it: number) => `${ouid}` == `${it}`);
if (uid) {
sids.push(uid);
} else {
// find in channel
const usr = userAttrs.get(`${uid}`)
if (usr) {
sids.push(+usr.uid);
} else {
sids.push(-1);
}
}
}
}
console.log(222222, userAttrs.toArray());
console.log(333333, sids);
// capture all remote streams
for (let studentId of studentIds) {
for (let studentId of /*studentIds*/sids) {
console.log('*****************', studentId)
if (studentId == undefined) {
continue
}
if (studentId == 0) {
const _tmpStream = {
account: '空闲',
streamID: studentId,
video: 0,
audio: 0,
local: false,
}
studentStreams.push(_tmpStream);
continue
}
if (studentId == -1) {
const _tmpStream = {
account: '等待中...',
streamID: studentId,
video: 0,
audio: 0,
local: false,
}
studentStreams.push(_tmpStream);
continue
}
if (me.role === 'student' && +me.uid == studentId) {
if (myAttr && roomState.rtc.localStream) {
console.log('use local stream')
......@@ -168,6 +218,7 @@ export default function useStream () {
continue
}
}
const studentAttr = userAttrs.get(''+studentId);
const stream = roomState.rtc.remoteStreams.get(+studentId);
if (studentAttr) {
......@@ -190,7 +241,7 @@ export default function useStream () {
}
}
userAttrs.forEach((v, k) => {
if (!studentIds.includes(+v.uid)) {
if (!sids.includes(+v.uid)) {
if (`${v.uid}` != `${teacherUid}`) {
const s: any = {
streamID: +v.uid,
......@@ -210,7 +261,7 @@ export default function useStream () {
}, [
course,
me.uid,
roomStore.state.studentsOrder,
// roomStore.state.studentsOrder,
roomState.rtc.users,
roomState.rtc.remoteStreams,
roomState.rtc.localStream
......
......@@ -288,13 +288,13 @@ body {
@media screen and (max-width: 780px) {
.home-cover-web {
min-width: 780px;
//min-width: 780px;
}
}
@media screen and (max-height: 768px) {
.home-cover-web {
min-height: 768px;
//min-height: 768px;
}
}
......
......@@ -213,7 +213,7 @@ export function RoomPage({ children }: any) {
]);
useEffect(() => {
debugger
// debugger
if (!roomState.me.uid || !roomState.course.rid) return;
if (classroom) {
if (platform === 'web') {
......@@ -301,7 +301,7 @@ export function RoomPage({ children }: any) {
})
*/
rtc.current = true;
debugger
// debugger
// WARN: IF YOU ENABLED APP CERTIFICATE, PLEASE SIGN YOUR TOKEN IN YOUR SERVER SIDE AND OBTAIN IT FROM YOUR OWN TRUSTED SERVER API
console.info(1.1, 'rtc join')
// if (roomState.me.role == 'teacher') {
......@@ -311,7 +311,7 @@ export function RoomPage({ children }: any) {
uid: +roomState.me.uid,
channel: roomState.course.rid,
token: '',
dual: isSmallClass
dual: true
}).then(() => {
console.info(2, 'rtc join ok')
roomStore.publishMeStream();
......@@ -452,10 +452,11 @@ export function RoomPage({ children }: any) {
console.log(1.15,'stagedown?', 222222 );
roomStore.unPublishMeStream();
}
console.log(1.2 ,studentsOrder, 'studentsOrder changed publish stream');
}
console.log(1.2 ,studentsOrder, 'studentsOrder changed publish stream');
}, [studentsOrder]);
......
import React, {useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
// import VideoMarquee from '../../components/video-marquee';
import MediaBoard from '../../components/mediaboard';
import Roomboard from '../../components/roomboard';
// import Roomboard from '../../components/roomboard';
import './small-class.scss';
import CourseWareFrame from "../../components/courseware-frame";
import VideoShowList from "../../components/video-stage-list";
import {ToolsSwitcherController} from "../../components/tools-switcher-controller";
import {roomStore} from "../../stores/room";
import {TOOL_TYPE} from "../../utils/types";
import {platform} from "../../utils/platform";
import AgoraWebClient from "../../utils/agora-rtc-client";
import {AgoraElectronClient} from "../../utils/agora-electron-client";
// import {platform} from "../../utils/platform";
// import AgoraWebClient from "../../utils/agora-rtc-client";
// import {AgoraElectronClient} from "../../utils/agora-electron-client";
import {useRoomState} from "../../containers/root-container";
import RoomToolsBar from "../../components/room-tools-bar";
// import RoomToolsBar from "../../components/room-tools-bar";
export default function SmallClass() {
const [tool, setTool] = useState(true);
......@@ -39,15 +39,15 @@ export default function SmallClass() {
{/*<VideoMarquee />*/}
<VideoShowList/>
<div className="container">
{roomStore.state.me.role == 'teacher' ?<ToolsSwitcherController onClick={() => {
changeTool();
}}/> : null}
{/*{roomStore.state.me.role == 'teacher' ?<ToolsSwitcherController onClick={() => {*/}
{/* changeTool();*/}
{/*}}/> : null}*/}
{currentTool == TOOL_TYPE.WHITEBOARD
? <MediaBoard />
: <CourseWareFrame></CourseWareFrame>}
{ roomStore.state.me.role == 'teacher' ? <RoomToolsBar /> : null}
{/*{ roomStore.state.me.role == 'teacher' ? <Roomboard currentActive={'media'} /> : null}*/}
</div>
</div>
......
......@@ -79,17 +79,70 @@ function HomePage() {
setRequired({...required, yourPass: 'missing your password'});
return;
}
///////////////////////////////
/*
session.roomName = "test_01__123";// userInfo["class_id"];
// session.yourName = userInfo["nick_name"];
session.role = `${session.yourName}`==="1"?"teacher":"student";
session.roomType = 1;
const path = roomTypes[session.roomType].path
const payload = {
uid: `${session.yourName}`, // genUid(),
rid: `${session.roomName}`, // `${session.roomType}${MD5(session.roomName)}`,
role: session.role,
roomName: session.roomName,
roomType: session.roomType,
video: 0,
audio: 0,
chat: 1,
account: session.yourName,
token: '',
boardId: '',
linkId: 0,
sharedId: 0,
lockBoard: 0,
}
ref.current = true;
globalStore.showLoading();
roomStore.loginAndJoin(payload).then(() => {
roomStore.updateSessionInfo(payload);
history.push(`/classroom/${path}`);
}).catch((err: any) => {
if (err.reason) {
globalStore.showToast({
type: 'rtmClient',
message: err.reason
})
} else {
globalStore.showToast({
type: 'rtmClient',
message: 'login failure, please checkout ur network'
})
}
console.warn(err);
})
.finally(() => {
ref.current = false;
globalStore.stopLoading();
})
*/
///////////////////////////////
endPoint.login({username: session.yourName, password: session.yourPass}).then((userInfo: any) => {
console.log(userInfo);
session.roomName = userInfo["class_id"];
session.yourName = userInfo["nick_name"];
session.roomName = userInfo["class_id"];// userInfo["class_id"];
// session.yourName = userInfo["nick_name"];
session.role = userInfo["classRole"]==="tea"?"teacher":"student";
session.roomType = 1;
if (!roomTypes[session.roomType]) return;
const path = roomTypes[session.roomType].path
const payload = {
uid: `${userInfo["id"]}`, // genUid(),
uid: userInfo["classRole"]==="tea"?"100":`${session.yourName.substr(-2)}`, // `${userInfo["id"]}`, // genUid(),
rid: `${session.roomName}`, // `${session.roomType}${MD5(session.roomName)}`,
role: session.role,
roomName: session.roomName,
......@@ -133,6 +186,8 @@ function HomePage() {
message: err
})
});
}
return (
......
import { AgoraFetch } from "../utils/fetch";
import jwt_decode from 'jwt-decode';
import {roomStore} from "../stores/room";
const ENDPOINT: string = process.env.REACT_APP_AGORA_ENDPOINT as string;
......@@ -78,6 +79,10 @@ export class EndPoint {
}
getCourseUrl(){
if (!this.userToken) {
this.userToken = roomStore.state.cwLink;
}
console.log('getCourseUrl', this.userToken)
return `${ENDPOINT}/airclass_ol?token=${this.userToken}`;
}
......
This diff is collapsed.
......@@ -98,9 +98,12 @@ export default class AgoraRTMClient {
this._logged = true;
return
}
async clearChannelAttributesByKeys(uids: string[]) {
await this._client.deleteChannelAttributesByKeys(this._currentChannelName, uids, {enableNotificationToChannelMembers: true});
}
async clearChannelAttributes() {
await this._client.clearChannelAttributes(this._currentChannelName);
await this._client.clearChannelAttributes(this._currentChannelName, {enableNotificationToChannelMembers: true});
}
async logout () {
......@@ -155,7 +158,11 @@ export default class AgoraRTMClient {
this.destroyChannel(channel);
}
}
async leaveChannel() {
// await this._client.clearLocalUserAttributes();
await this.leave(this._currentChannelName);
await this.logout();
}
async exit() {
await this.deleteChannelAttributesByKey();
await this.leave(this._currentChannelName);
......
export const STAGE_NUM: number = 1;
export const STAGE_NUM: number = 6;
......@@ -256,6 +256,7 @@ export function resolveChannelAttrs(json: object) {
let students: any[] = [];
for (let key of Object.keys(json)) {
if (key === 'teacher') continue;
if (!/^[0-9]+$/.test(key)) continue;
const student = jsonParse(_.get(json, `${key}.value`));
if (student && Object.keys(student).length) {
student.uid = key;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment