Commit c21f5e58 authored by liujiaxin's avatar liujiaxin

version 1.2

parent bfcbbc6a
......@@ -4,10 +4,11 @@ import {Dialog, DialogContent, DialogContentText} from '@material-ui/core';
import './dialog.scss';
import { useGlobalState } from '../../containers/root-container';
import { roomStore } from '../../stores/room';
import { globalStore } from '../../stores/global';
import {roomStore, windowMonitor} from '../../stores/room';
import {globalStore} from '../../stores/global';
import { useHistory } from 'react-router-dom';
import { RoomMessage } from '../../utils/agora-rtm-client';
import {getApiServerHost} from "../../services/agora-end-points";
interface RoomProps {
onConfirm: (type: string) => void
......@@ -84,6 +85,13 @@ const DialogContainer = () => {
const onConfirm = (type: string) => {
if (type === 'exitRoom') {
// debugger
// const data = roomStore.getUserUsage();
// const host = getApiServerHost();
// var blob = new Blob([JSON.stringify(data)], {
// type: 'application/x-www-form-urlencoded'
// });
// navigator.sendBeacon(`${host}/api/classroom/finish`, blob);
windowMonitor.leave()
globalStore.showLoading();
roomStore.exitAll().then(() => {
globalStore.stopLoading();
......
......@@ -26,10 +26,17 @@ export default function (props: any) {
const val = evt.target.value.match(ALPHABETICAL)[0];
props.onChange(val ? val : '');
}
const onBlur = (evt: any) => {
const val = evt.target.value.match(ALPHABETICAL)[0];
if (props.onBlur) {
props.onBlur(val ? val : '');
}
}
return (
<>
<InputLabel>{props.Label}</InputLabel>
<Input className={classes.formInput} type={props.type} value={props.value}
onBlur={onBlur}
onChange={onChange}>
</Input>
{props.requiredText ? <Typography className={classes.required}>{props.requiredText}</Typography> : null}
......
......@@ -33,4 +33,4 @@ const FormSelect: React.FC<FormSelect> = ({
);
}
export default React.memo(FormSelect);
\ No newline at end of file
export default React.memo(FormSelect);
......@@ -10,7 +10,7 @@ import { SketchPicker, RGBColor } from 'react-color';
import { AgoraElectronClient } from '../utils/agora-electron-client';
import { UploadBtn } from './whiteboard/upload/upload-btn';
import { ResourcesMenu } from './whiteboard/resources-menu';
import ScaleController from './whiteboard/scale-controller';
// import ScaleController from './whiteboard/scale-controller';
import { PPTProgressPhase } from '../utils/upload-manager';
import { UploadNoticeView } from '../components/whiteboard/upload/upload-notice';
import Progress from '../components/progress/progress';
......@@ -234,6 +234,7 @@ const MediaBoard: React.FC<MediaBoardProps> = ({
whiteboard.updateRoomState();
}
/*
const showControl: boolean = useMemo(() => {
if (me.role === 'teacher') return true;
if (location.pathname.match(/big-class/)) {
......@@ -242,6 +243,10 @@ const MediaBoard: React.FC<MediaBoardProps> = ({
}
}
return false;
}, []);*/
const showControl: boolean = useMemo(() => {
if (me.role === 'teacher') return true;
return false;
}, []);
const items = [
......@@ -569,6 +574,8 @@ const items = [
room={room}
/>
}
{ showControl ?
<div className="layer">
{!sharedStream ?
<>
......@@ -584,7 +591,10 @@ const items = [
</> : null}
<UploadPanel />
{children ? children : null}
</div>
</div>: null}
{/*me.role === 'teacher' && room ?
<ScaleController
zoomScale={scale}
......@@ -603,6 +613,7 @@ const items = [
/>
:
null*/}
{ showControl ?
<Control
notice={globalState.notice}
......@@ -612,7 +623,9 @@ const items = [
currentPage={currentPage}
totalPage={totalPage}
isHost={isHost}
onClick={handlePageTool}/> : null }
onClick={handlePageTool}/> : null
}
{tool === 'folder' && whiteboardState.room ?
<ResourcesMenu
active={whiteboardState.activeDir}
......
......@@ -10,6 +10,27 @@
flex: 1;
}
}
.hands-btn{
width: 16px!important;
height: 40px!important;
color: #dd4814;
}
@keyframes blink{
0%{opacity: 1;}
25%{opacity: .5;}
50%{opacity: .0;}
75%{opacity: .5;}
100%{opacity: 1;}
}
.blink{
color: #dd4814;
animation: blink 1s linear infinite;
-webkit-animation: blink 1s linear infinite;
-moz-animation: blink 1s linear infinite;
-ms-animation: blink 1s linear infinite;
-o-animation: blink 1s linear infinite;
}
.holder-avatar{
width: 40px!important;
height: 40px!important;
......
......@@ -6,6 +6,8 @@ import { useRoomState } from '../containers/root-container';
import { platform } from '../utils/platform';
import AccountCircle from '@material-ui/icons/AccountCircle';
import Link from '@material-ui/core/Link';
import PanToolIcon from '@material-ui/icons/PanTool';
import {roomStore} from "../stores/room";
const contentMode = 0;
interface MemberHolderProps {
......@@ -111,6 +113,11 @@ const MemberHolder: React.FC<MemberHolderProps> = ({
}, [domId, stream]);
*/
const isHandUp = useMemo(() => {
const hu = roomStore.state.handsUpStudents[streamID];
console.log(streamID, hu)
return hu
}, [roomStore.state.handsUpStudents]);
useEffect(() => {
if (stream && platform === 'web') {
......@@ -234,6 +241,12 @@ const MemberHolder: React.FC<MemberHolderProps> = ({
{/*me.uid === id ||*/}
{me.role === 'teacher' ?
<span className="media-btn">
{
isHandUp ?
<PanToolIcon className='hands-btn blink'></PanToolIcon>:
<div className='hands-btn'></div>
}
{/*<Link component="button" onClick={onAudioClick}>{audio ? "静音" : "开麦"}</Link>*/}
{me.role === 'teacher' ? <Link component="button" onClick={onStageUp}>上台</Link> : null}
{/*<Icon onClick={onAudioClick} className={audio ? "icon-speaker-on" : "icon-speaker-off"} data={"audio"} />*/}
......
......@@ -75,13 +75,14 @@
flex-direction: column;
justify-content: flex-end;
box-sizing: border-box;
min-width: 280px;
/*min-width: 280px;
max-width: 280px;
min-height: 159px;
max-height: 159px;
max-height: 159px;*/
position: relative;
overflow: hidden;
flex: 1;
canvas {
z-index: 1;
}
......@@ -96,6 +97,7 @@
height: 100%;
width: 100%;
}
// &.show-placeholder > div:last-child {
// display: none !important;
// }
......@@ -104,10 +106,10 @@
.student-placeholder {
display: flex;
top: 0;
min-width: 280px;
max-width: 280px;
min-height: 135px;
max-height: 135px;
//min-width: 280px;
//max-width: 280px;
//min-height: 135px;
//max-height: 135px;
position: absolute;
z-index: 2;
}
......
This diff is collapsed.
......@@ -27,6 +27,10 @@
flex: 1;
position: relative;
padding: 0 2px;
display: grid;
> *{
grid-area: 1 / 1 / 2 / 2;
}
.agora-video-view{
max-width: 100%;
min-width: 100%;
......@@ -135,10 +139,10 @@
}
}
.agora-video-view {
min-width: 187px;
min-height: 106px;
max-width: 187px;
max-height: 106px;
//min-width: 187px;
//min-height: 106px;
//max-width: 187px;
//max-height: 106px;
border-radius: 2px;
box-sizing: border-box;
.student-placeholder,
......@@ -147,18 +151,21 @@
background-color: #DBE2E5;
justify-content: center;
box-sizing: border-box;
background-size: 66px;
min-width: 187px;
min-height: 82px;
max-width: 187px;
max-height: 82px;
//background-size: 66px;
//min-width: 187px;
//min-height: 82px;
//max-width: 187px;
//max-height: 82px;
width: 100%;
height: 70%;
border-radius: 2px;
box-sizing: border-box;
position: absolute;
}
.student-placeholder::before,
.teacher-placeholder::before {
background-size: 63px;
//background-size: 63px;
background-size: calc( 40% );
}
}
}
......
......@@ -42,6 +42,7 @@ function VideoShowList() {
<div className="on-show">
{teacher ?
<div className="camera">
<svg viewBox="0 0 7 5"></svg>
<VideoPlayer
role="teacher"
domId={`dom-${teacher.streamID}`}
......@@ -57,6 +58,7 @@ function VideoShowList() {
</div>
:
<div className="camera">
<svg viewBox="0 0 7 5"></svg>
<VideoPlayer role="teacher" account={'teacher'} video audio streamID={0} />
</div>
}
......@@ -68,6 +70,7 @@ function VideoShowList() {
const student = students && students.slice(0,STAGE_NUM)[i];
return <div className="camera" key={`stage_video${i}`}>
<svg viewBox="0 0 7 5"></svg>
{student ? <VideoPlayer
role="student"
domId={`dom-${student.streamID}`}
......
import React, { useEffect, useRef } from 'react';
import { GlobalState, globalStore} from '../stores/global';
import { RoomState, roomStore} from '../stores/room';
import {GlobalState, globalStore} from '../stores/global';
import { RoomState, roomStore, windowMonitor} from '../stores/room';
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";
import {Map as immuMap} from "immutable";
export type IRootProvider = {
globalState: GlobalState
......@@ -56,6 +57,8 @@ export const useWhiteboardState = () => {
return useStore().whiteboardState;
}
export const RootProvider: React.FC<any> = ({children}) => {
const globalState = useObserver<GlobalState>(globalStore);
const roomState = useObserver<RoomState>(roomStore);
......@@ -102,6 +105,7 @@ export const RootProvider: React.FC<any> = ({children}) => {
console.log('MessageFromPeer', text);
const body = resolvePeerMessage(text);
resolveMessage(peerId, body);
body.cmd = +body.cmd
roomStore.handlePeerMessage(body.cmd, peerId)
.then(() => {
}).catch(console.warn);
......@@ -164,18 +168,23 @@ export const RootProvider: React.FC<any> = ({children}) => {
useEffect(() => {
// console.log(11223344, location.pathname)
if (location.pathname === '/') {
// alert('clear1')
// GlobalStorage.clear('agora_room');
windowMonitor.removeUnloadEvent();
return;
}
windowMonitor.addUnloadEvent();
const room = value.roomState;
GlobalStorage.save('agora_room', {
me: room.me,
course: room.course,
mediaDevice: room.mediaDevice,
cwLink: endPoint.userToken,
studentsOrder: room.studentsOrder
studentsOrder: room.studentsOrder,
userUsage: room.userUsage,
handsUpStudents: room.handsUpStudents
});
// WARN: DEBUG ONLY MUST REMOVED IN PRODUCTION
//@ts-ignore
......
......@@ -23,7 +23,6 @@ export default function useStream () {
if (!course.teacherId || !me.uid) return;
const teacherInfo = roomStore.state.users.get(''+course.teacherId);
if (!teacherInfo) return;
if (me.uid === course.teacherId) {
return {
...roomState.rtc.localStream,
......@@ -186,8 +185,8 @@ export default function useStream () {
}
orderIdx++
}
console.log(222222, userAttrs.toArray());
console.log(333333, sids);
// console.log(222222, userAttrs.toArray());
// console.log(333333, sids);
......@@ -493,6 +492,10 @@ export default function useStream () {
// }
return;
}
if (type === 'fixUserStream') {
await roomStore.unPublishMeStream();
await roomStore.publishMeStream();
}
}
}
return value;
......
......@@ -33,7 +33,7 @@ export default function BigClass() {
const rtmLock = useRef<boolean>(false);
const lock = useRef<boolean>(false);
useEffect(() => {
rtmLock.current = false;
return () => {
......@@ -49,7 +49,9 @@ export default function BigClass() {
if (roomStore.state.course.teacherId) {
rtmLock.current = true;
roomStore.rtmClient.sendPeerMessage(roomStore.state.course.teacherId,
{cmd: RoomMessage.applyCoVideo})
// {cmd: RoomMessage.applyCoVideo}
{cmd: RoomMessage.handsUp}
)
.then((result: any) => {
console.log("peerMessage result ", result);
})
......@@ -59,7 +61,7 @@ export default function BigClass() {
})
}
}
if (type === 'hands_up_end') {
if (roomStore.state.course.teacherId) {
rtmLock.current = true;
......@@ -157,7 +159,7 @@ export default function BigClass() {
handleClick={handleClick}
>
<div className="video-container">
{currentHost ?
{currentHost ?
<VideoPlayer
role="teacher"
streamID={currentHost.streamID}
......@@ -212,4 +214,4 @@ export default function BigClass() {
</div>
</div>
)
}
\ No newline at end of file
}
......@@ -24,6 +24,7 @@ export function RoomPage({ children }: any) {
const lock = useRef<boolean>(false);
useEffect(() => {
const me = roomStore.state.me;
......@@ -83,6 +84,68 @@ export function RoomPage({ children }: any) {
});
}, []);
/*
const loginFn = () => {
const me = roomStore.state.me;
const rid = roomStore.state.course.rid;
const roomType = roomStore.state.course.roomType;
const roomName = roomStore.state.course.roomName;
if (!rid || !me.uid) {
history.push('/');
}
const uid = me.uid;
const payload = {
uid,
rid,
role: me.role,
roomName,
roomType,
video: me.video,
audio: me.audio,
chat: me.chat,
account: me.account,
token: '',
boardId: me.boardId,
linkId: me.linkId,
sharedId: me.sharedId,
lockBoard: me.lockBoard,
grantBoard: me.grantBoard,
}
lock.current = true;
if (roomStore.state.rtm.joined) return;
globalStore.showLoading();
roomStore.loginAndJoin(payload, true).then(() => {
roomStore.updateMe(payload).then(() => {
lock.current && roomStore.updateSessionInfo(payload);
}).catch((err: any) => {
globalStore.showToast({
type: 'rtmClient',
message: 'login failure'
});
history.push('/');
console.warn(err)
}).finally(() => {
globalStore.stopLoading();
lock.current = false;
})
}).catch((err: any) => {
globalStore.showToast({
type: 'rtmClient',
message: 'login failure'
});
history.push('/');
console.warn(err)
})
.finally(() => {
lock.current = false;
});
}
*/
const roomType = roomTypes[roomStore.state.course.roomType];
const location = useLocation();
......@@ -160,18 +223,21 @@ export function RoomPage({ children }: any) {
useEffect( () => {
const webClient = roomStore.rtcClient as AgoraWebClient;
console.log('rtcJoined', rtcJoined, rtc.current);
if (!rtcJoined) {
// return;
webClient.unpublishLocalStream()
}
if (!rtcJoined || rtc.current) return;
if (platform === 'web') {
roomStore.rtmClient.getChannelAttributeBy(roomStore.state.course.rid).then((channelMeta: any) => {
console.log('check user publish stream, channelMeta', channelMeta);
console.log('check user publish stream, user orders', roomStore.state.studentsOrder);
console.log('check user publish stream, user info', roomStore.state.me);
const studentsOrder = channelMeta.studentsOrder || roomStore.state.studentsOrder;
const fn = () => {
// console.log('check user publish stream, channelMeta', channelMeta);
// console.log('check user publish stream, user orders', roomStore.state.studentsOrder);
// console.log('check user publish stream, user info', roomStore.state.me);
// const studentsOrder = channelMeta.studentsOrder || roomStore.state.studentsOrder;
const studentsOrder = [...roomStore.state.studentsOrder];
// const webClient = roomStore.rtcClient as AgoraWebClient;
const uid = +roomStore.state.me.uid as number;
......@@ -202,7 +268,28 @@ export function RoomPage({ children }: any) {
publishLock.current = false;
})
}
});
}
/*
roomStore.rtmClient.getChannelAttributeBy(roomStore.state.course.rid).then((channelMeta: any) => {
}).catch(err => {
console.error(err)
globalStore.showToast({
type: 'rtmClient',
message: err.message
});
history.push('/');
});*/
try{
fn()
} catch (err) {
console.error(err)
globalStore.showToast({
type: 'rtmClient',
message: err.message
});
history.push('/');
}
}
......@@ -232,7 +319,29 @@ export function RoomPage({ children }: any) {
return;
}
console.log("[agora-rtc] add event listener");
/*
webClient.rtc.on('stream-reconnect-start', (evt: any) => {
console.log('[agora-web] stream-reconnect-start', evt);
});
webClient.rtc.on('connection-state-change', (evt: any) => {
console.log('[agora-web] connection-state-change', evt);
});
webClient.rtc.on('reconnect', (evt: any) => {
console.log('[agora-web] reconnect', evt);
});
webClient.rtc.on('connected', (evt: any) => {
console.log('[agora-web] connected', evt);
});
webClient.rtc.on('stream-fallback', (evt: any) => {
console.log('[agora-web] stream-fallback', evt);
});
webClient.rtc.on('stream-updated', (evt: any) => {
console.log('[agora-web] stream-updated', evt);
});
webClient.rtc.on('exception', (evt: any) => {
console.log('[agora-web] exception', evt);
});
webClient.rtc.on('onTokenPrivilegeWillExpire', (evt: any) => {
// you need obtain the `newToken` token from server side
const newToken = '';
......@@ -253,10 +362,11 @@ export function RoomPage({ children }: any) {
roomStore.addLocalStream(_stream);
});
webClient.rtc.on('stream-subscribed', ({ stream }: any) => {
console.log('stream', 2, 'stream-subscribed')
const streamID = stream.getId();
console.log('stream', 2, 'stream-subscribed', streamID)
// when streamID is not share_id use switch high or low stream in dual stream mode
if (location.pathname.match(/small-class/) && streamID !== SHARE_ID) {
if (/*location.pathname.match(/small-class/) && */streamID !== SHARE_ID) {
if (roomStore.state.course.teacherId
&& roomStore.state.course.teacherId === `${streamID}`) {
webClient.setRemoteVideoStreamType(stream, 0);
......@@ -267,8 +377,8 @@ export function RoomPage({ children }: any) {
console.log("[agora-web] dual stream set low for student");
}
}
console.log('stream', 3, 'addRemoteStream to play')
const _stream = new AgoraStream(stream, stream.getId(), false);
console.log('stream step:', 3,'uid', streamID, 'addRemoteStream to play')
const _stream = new AgoraStream(stream, streamID, false);
console.log("[agora-web] subscribe remote stream, id: ", stream.getId());
roomStore.addRemoteStream(_stream);
});
......@@ -282,7 +392,7 @@ export function RoomPage({ children }: any) {
const id = stream.getId();
if (id === roomStore.applyUid) {
globalStore.removeNotice();
me.role === 'teacher' &&
roomStore.state.me.role === 'teacher' &&
roomStore.updateCourseLinkUid(0).then(() => {
console.log("update teacher link_uid to 0");
}).catch(console.warn);
......@@ -309,13 +419,13 @@ export function RoomPage({ children }: any) {
const msg = attr === 0 ? 'resume to a&v mode' : 'fallback to audio mode';
console.info(`[agora-web] stream: ${uid} fallback: ${msg}`);
})
*/
rtc.current = true;
// 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') {
console.info( 'i m teacher should rtc join')
// if (roomState.me.role == 'teacher' || roomState.studentsOrder.includes(+roomState.me.uid)) {
// console.info( 'i m teacher should rtc join')
webClient
.joinChannel({
uid: +roomState.me.uid,
......@@ -323,8 +433,12 @@ export function RoomPage({ children }: any) {
token: '',
dual: true
}).then(() => {
console.info(2, 'rtc join ok')
roomStore.publishMeStream();
console.info(2, 'rtc join ok', roomState.me.uid, [...studentsOrder], [...roomStore.state.studentsOrder], [...roomState.studentsOrder])
if (roomState.me.role == 'teacher' || roomStore.state.studentsOrder.includes(+roomState.me.uid)) {
roomStore.publishMeStream();
}
// // 如果当前用户是教师,直接发布,
// if(roomStore.state.me.role == 'teacher') {
// roomStore.publishMeStream();
......@@ -425,8 +539,8 @@ export function RoomPage({ children }: any) {
}, [roomState.me.uid, roomState.course.rid]);
useEffect(() => {
console.log('studentsOrder changed');
const webClient = roomStore.rtcClient as AgoraWebClient;
console.log('studentsOrder changed', webClient.joined, studentsOrder);
if (!webClient.joined) {
return;
}
......@@ -440,10 +554,6 @@ export function RoomPage({ children }: any) {
}
console.log(1.2 ,studentsOrder, 'studentsOrder changed publish stream');
}
}, [studentsOrder]);
return (
......
......@@ -10,7 +10,42 @@
min-width: 280px;
}
}
.hands_up{
position: absolute;
top: 0;
right: 0;
cursor: pointer;
display: flex;
height: 30px;
width: 30px;
margin: 2px;
border-radius: 4px;
box-sizing: border-box;
pointer-events: auto;
margin-top: 10px;
padding: 4px;
display: flex;
box-sizing: border-box;
align-items: center;
height: 42px;
width: 42px;
background: #fff;
box-shadow: 0 2px 4px 0 rgba(61,30,30,.1);
border-radius: 6px;
border: 1px solid #dbe2e5;
}
.hands_up:after{
width: 100%;
height: 100%;
background-position: 50%;
background-repeat: no-repeat;
background-size: contain;
content: " ";
background-image: url("data:image/png;base64,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");
}
.hands_up.disabled{
background: #dbe2e5;
}
.video-marquee-container {
display: flex;
min-width: 990px;
......@@ -119,4 +154,4 @@
}
}
}
}
\ No newline at end of file
}
......@@ -12,9 +12,12 @@ import {TOOL_TYPE} from "../../utils/types";
// import AgoraWebClient from "../../utils/agora-rtc-client";
// import {AgoraElectronClient} from "../../utils/agora-electron-client";
import {useRoomState} from "../../containers/root-container";
import {RoomMessage} from "../../utils/agora-rtm-client";
import Icon from "../../components/icon";
// import RoomToolsBar from "../../components/room-tools-bar";
export default function SmallClass() {
const [tool, setTool] = useState(true);
const roomState = useRoomState();
const changeTool = async () => {
......@@ -31,6 +34,56 @@ export default function SmallClass() {
}
}, [roomState]);
const rtmLock = useRef<boolean>(false);
const handleClick = (evt: any) => {
if (rtmLock.current) return;
console.log('handleClick hands_up', evt.currentTarget);
const elem = evt.currentTarget;
if (elem.classList.contains('disabled')) {
return;
}
// if (type === 'hands_up') {
if (roomStore.state.course.teacherId) {
elem.classList.add('disabled');
setTimeout(() => {
console.log('remove disabled class');
elem.classList.remove('disabled');
}, 10 * 1000)
rtmLock.current = true;
roomStore.rtmClient.sendPeerMessage(roomStore.state.course.teacherId,
// {cmd: RoomMessage.applyCoVideo}
{cmd: RoomMessage.handsUp}
)
.then((result: any) => {
console.log("peerMessage result ", result);
if (!result) {
evt.currentTarget.classList.remove('disabled');
}
})
.catch(console.warn)
.finally(() => {
rtmLock.current = false;
})
}
// }
/*
if (type === 'hands_up_end') {
if (roomStore.state.course.teacherId) {
rtmLock.current = true;
roomStore.rtmClient.sendPeerMessage(roomStore.state.course.teacherId,
{cmd: RoomMessage.cancelCoVideo})
.then((result: any) => {
console.log("peerMessage result ", result);
})
.catch(console.warn)
.finally(() => {
rtmLock.current = false;
})
}
}
*/
}
useEffect(() => {
}, [roomStore.state.course.currentTool]);
......@@ -47,6 +100,15 @@ export default function SmallClass() {
? <MediaBoard />
: <CourseWareFrame></CourseWareFrame>}
{roomStore.state.me.role === 'student' && !roomState.studentsOrder.includes(+roomStore.state.me.uid) ?
<>
<Icon
data='hands_up'
onClick={handleClick}
className={`items hands_up ${false ? 'active' : ''}`}
/>
</>
:null}
{/*{ roomStore.state.me.role == 'teacher' ? <Roomboard currentActive={'media'} /> : null}*/}
</div>
......
......@@ -2,18 +2,24 @@ import React, { useState, useEffect, useRef } from 'react';
import { Theme, FormControl } from '@material-ui/core';
import {makeStyles} from '@material-ui/core/styles';
import Button from '../components/custom-button';
import RoleRadio from '../components/role-radio';
// import RoleRadio from '../components/role-radio';
import Icon from '../components/icon';
import FormInput from '../components/form-input';
import FormSelect from '../components/form-select';
// import FormSelect, {FormSelectItems} from '../components/form-select';
import { isElectron } from '../utils/platform';
import { usePlatform } from '../containers/platform-container';
import {useHistory} from 'react-router-dom';
import { roomStore } from '../stores/room';
import { genUid } from '../utils/helper';
import MD5 from 'js-md5';
// import { genUid } from '../utils/helper';
// import MD5 from 'js-md5';
import { globalStore } from '../stores/global';
import { endPoint } from '../services/agora-end-points';
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import InputLabel from "@material-ui/core/InputLabel";
import {useLocation} from "react-router-dom";
import { Base64 } from 'js-base64';
export const roomTypes = [
{value: 0, text: 'One-to-One', path: 'one-to-one'},
......@@ -25,6 +31,9 @@ const useStyles = makeStyles ((theme: Theme) => ({
formControl: {
minWidth: '240px',
maxWidth: '240px',
},
hide:{
display: 'none'
}
}));
......@@ -34,6 +43,7 @@ type SessionInfo = {
yourName: string
yourPass: string
role: string
classId: string
}
const defaultState: SessionInfo = {
......@@ -41,11 +51,12 @@ const defaultState: SessionInfo = {
roomType: 1,
role: '',
yourName: '',
yourPass: ''
yourPass: '',
classId: ''
}
function HomePage() {
const classes = useStyles();
const style = useStyles();
const history = useHistory();
......@@ -68,7 +79,134 @@ function HomePage() {
const [session, setSessionInfo] = useState<SessionInfo>(defaultState);
const [required, setRequired] = useState<any>({} as any);
const [classes, setClasses] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const handleClassChange = (event: any) => {
setSessionInfo({
...session,
classId: event.target.value
});
// console.log('handleClassChange', event.target.value);
};
const fetchClasses = async (val: any) => {
console.log('fetchClasses', val, session.yourName);
if (!session.yourName) {
setSessionInfo({
...session,
classId: ''
});
setClasses([])
return
}
// setLoading(true);
globalStore.showLoading();
const data = await endPoint.fetchClasses(session.yourName);
console.log(data);
// setLoading(false);
globalStore.stopLoading();
setSessionInfo({
...session,
classId: ''
});
setClasses(data)
}
const doCheckWithToken = (userInfo: any) => {
loginLive(userInfo);
}
const doCheckWithUser = () => {
endPoint.login({username: session.yourName, password: session.yourPass, classid: session.classId}).then((userInfo: any) => {
loginLive(userInfo);
}).catch((err: any) => {
globalStore.showToast({
type: 'rtmClient',
message: err
})
});
}
const loginLive = (userInfo: any) => {
console.log(userInfo);
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"]}`, // 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,
roomType: session.roomType,
video: 0,
audio: 0,
chat: 1,
account: `${userInfo["nick_name"]}`,
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();
})
}
const location = useLocation();
// console.log(1111111111111, location );
const params = new URLSearchParams(location.search);
let token = params.get('token');
let hasLogin = params.get('login') && params.get('login') == '1' ? true : false;
// console.log('params', token)
// debugger
if (token) {
const tokenParts = token.split('.')
token = tokenParts[1];
token = token.replace(/[\+]/g, '-');
token = token.replace(/[/]/g, '_');
const eqc = token.length % 4;
const ea: any = [];
if ( eqc != 0 ) {
ea.length = 4 - eqc;
token = `${token}${Array.from(ea).fill('=').join('')}`
}
let userInfo = Base64.decode(token);
userInfo = JSON.parse(userInfo)
console.log(userInfo)
// const userInfo = {
// id: `${data.username}`,
// class_id: "test_03__123",
// classRole: `${data.username}`==="1"?"tea":"",
// roomType: 1,
// nick_name: `${data.username}`,
// // data.username: session.yourName,
// // data.password: session.yourPass
// };
doCheckWithToken(userInfo);
}
const handleSubmit = () => {
if (!session.yourName) {
setRequired({...required, yourName: 'missing your name'});
......@@ -82,8 +220,9 @@ function HomePage() {
///////////////////////////////
/*
if (window.location.origin.includes('://127.0.0.1') || window.location.origin.includes('://localhost')) {
session.roomName = "test_01__123";// userInfo["class_id"];
session.roomName = "test_03__123";// userInfo["class_id"];
// session.yourName = userInfo["nick_name"];
session.role = `${session.yourName}`==="1"?"teacher":"student";
session.roomType = 1;
......@@ -129,7 +268,7 @@ function HomePage() {
})
} else {
endPoint.login({username: session.yourName, password: session.yourPass}).then((userInfo: any) => {
endPoint.login({username: session.yourName, password: session.yourPass, classid: session.classId}).then((userInfo: any) => {
console.log(userInfo);
session.roomName = userInfo["class_id"];// userInfo["class_id"];
// session.yourName = userInfo["nick_name"];
......@@ -186,6 +325,8 @@ function HomePage() {
}
*/
doCheckWithUser();
}
......@@ -202,7 +343,7 @@ function HomePage() {
</div>
</div>
}
<div className="custom-card">
<div className={`custom-card`}>
<div className="flex-item cover">
{isElectron ?
<>
......@@ -219,20 +360,43 @@ function HomePage() {
<div className="position-top card-menu">
<HomeBtn handleSetting={handleSetting}/>
</div>
<div className="position-content flex-direction-column">
<FormControl className={classes.formControl}>
<FormInput Label={"User Name"} value={session.yourName} onChange={
<div className={`position-content flex-direction-column ${hasLogin ? '' : style.hide}`}>
<FormControl className={style.formControl}>
<FormInput Label={"账号"} value={session.yourName} onChange={
(val: string) => {
setSessionInfo({
...session,
yourName: val
});
}}
onBlur={fetchClasses}
requiredText={required.yourName}
/>
</FormControl>
<FormControl className={classes.formControl}>
<FormInput Label={"Password"} type="password" value={session.yourPass} onChange={
<FormControl className={style.formControl}>
<InputLabel>请选择班级</InputLabel>
<Select
value={session.classId}
onChange={handleClassChange}
>
{!classes.length ?
<MenuItem value="0">
<em>请正确输入账号</em>
</MenuItem>
:
classes.map((item: any, key: number) =>
<MenuItem key={key} value={item.id}>{item.name}</MenuItem>)
}
{/*<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>*/}
</Select>
</FormControl>
<FormControl className={style.formControl}>
<FormInput Label={"密码"} type="password" value={session.yourPass} onChange={
(val: string) => {
setSessionInfo({
...session,
......@@ -242,7 +406,7 @@ function HomePage() {
requiredText={required.yourPass}
/>
</FormControl>
<Button name={"Join"} onClick={handleSubmit}/>
<Button disables={loading} name={"Join"} onClick={handleSubmit}/>
</div>
</div>
</div>
......
......@@ -42,7 +42,31 @@ export interface RoomParams {
enableAudio: number
linkUsers: number[]
}
export function getApiServerHost(online = false) {
let host = `https://ac.iplayabc.com`;
if (window.location.host.includes('s-live.')) {
host = 'https://staging-ac.ireadabc.com';
} else if (window.location.host.includes('127.0.0.1')
|| window.location.host.includes('localhost')) {
if (online) {
host = 'https://staging-ac.ireadabc.com';
} else {
host = 'http://127.0.0.1:1338'
}
}
return host;
}
export function getTeachServerHost() {
let host = `https://teach.ireadabc.com`;
if (window.location.host.includes('s-live.')) {
host = 'https://staging-teach.ireadabc.com';
} else if (window.location.host.includes('127.0.0.1')
|| window.location.host.includes('localhost')) {
host = 'http://127.0.0.1:1338'
}
return host;
}
export class EndPoint {
appID: string = '';
......@@ -57,9 +81,39 @@ export class EndPoint {
let json = await response.json();
return json.data;
}
async fetchClasses(username: string) {
const host = getApiServerHost(true);
let url = `${host}/api/classes?username=${username}`;
let json = await AgoraFetchJson({
url: url,
method: 'GET'
});
if(json.msg==="error"){
throw json.data;
}
console.log('json.data', json.data);
if (json.data && json.data.length) {
return json.data
}
return [];
}
async login(data: any) {
if (window.location.origin.includes('://127.0.0.1')
|| window.location.origin.includes('://localhost')) {
return {
id: `${data.username}`,
class_id: "test_03__123",
classRole: (`${data.username}`==="1" || `${data.username}`==="11")?"tea":"",
roomType: 1,
nick_name: `${data.username}`,
// data.username: session.yourName,
// data.password: session.yourPass
}
}
const host = getApiServerHost(true);
let url = `${host}/api/login`;
let json = await AgoraFetchJson({
url: `${ENDPOINT}/api/login`,
url: url,
method: 'POST',
data: data
});
......@@ -90,7 +144,8 @@ export class EndPoint {
this.userToken = roomStore.state.cwLink;
}
console.log('getCourseUrl', this.userToken)
return `${ENDPOINT}/airclass_ol?token=${this.userToken}`;
const host = getApiServerHost();
return `${host}/airclass_ol?token=${this.userToken}`;
}
async config() {
......
......@@ -200,4 +200,6 @@ export class Root {
}
}
export const globalStore = new Root();
\ No newline at end of file
export const globalStore = new Root();
This diff is collapsed.
......@@ -47,7 +47,7 @@ export const APP_ID = process.env.REACT_APP_AGORA_APP_ID as string;
export const APP_TOKEN = process.env.REACT_APP_AGORA_APP_TOKEN as string;
export const ENABLE_LOG = process.env.REACT_APP_AGORA_LOG as string === "true";
export const SHARE_ID = 9999999999999;
const BindEventMap: any = {};
class AgoraRTCClient {
private streamID: any;
......@@ -145,9 +145,15 @@ class AgoraRTCClient {
this.unsubscribeClientEvents();
this._bus.removeAllListeners();
}
offAll() {
for (const evtName of Object.keys(BindEventMap)) {
const cb = BindEventMap[evtName];
this._bus.off(evtName, cb);
}
}
// subscribe
on(evtName: string, cb: (args: any) => void) {
BindEventMap[evtName] = cb;
this._bus.on(evtName, cb);
}
......@@ -418,7 +424,11 @@ export default class AgoraWebClient {
async unpublishLocalStream() { console.log('unpublish LocalStream' , 4);
console.log("[agora-web] invoke unpublishStream");
await this.rtc.unpublish();
try{
await this.rtc.unpublish();
} catch (e) {
}
this.published = false;
}
......
......@@ -22,6 +22,7 @@ export enum RoomMessage {
unmuteBoard = 201,
stageUp = 301,
stageDown = 302,
handsUp = 401,
}
export interface MessageBody {
......@@ -101,6 +102,7 @@ export default class AgoraRTMClient {
return
}
async clearChannelAttributesByKeys(uids: string[]) {
if (!this._logged) return
await this._client.deleteChannelAttributesByKeys(this._currentChannelName, uids, {enableNotificationToChannelMembers: true});
}
......
......@@ -3,7 +3,7 @@ export class CustomStorage {
private storage: Storage;
constructor() {
this.storage = window.localStorage; // window.sessionStorage;
this.storage = window.sessionStorage; // window.localStorage; //
}
read(key: string): any {
......
......@@ -171,6 +171,9 @@ export function resolveMessage(peerId: string, { cmd, text }: { cmd: number, tex
case RoomMessage.stageUp:
type = 'stage up'
break;
case RoomMessage.handsUp:
type = 'hands up'
break;
default:
return console.warn(`[RoomMessage] unknown type, from peerId: ${peerId}`);
}
......
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