Compare commits
2 Commits
d494abf7ae
...
65bc4a011a
| Author | SHA1 | Date | |
|---|---|---|---|
| 65bc4a011a | |||
| 4d9bd5c98b |
@@ -24,5 +24,7 @@
|
||||
"exit-control-btn": "Exit",
|
||||
"popup-control-exit-title": "Are you sure you want to end the demo?",
|
||||
"popup-control-yes": "Finish",
|
||||
"popup-control-no": "Stay"
|
||||
"popup-control-no": "Stay",
|
||||
"popup-loading": "Please, wait",
|
||||
"sidebar-hide": "Hide menu"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"exit-control-btn": "Выйти",
|
||||
"popup-control-exit-title": "Вы уверены, что хотите закончить демонстрацию?",
|
||||
"popup-control-yes": "Закончить",
|
||||
"popup-control-no": "Остаться"
|
||||
|
||||
"popup-control-no": "Остаться",
|
||||
"popup-loading": "Пожалуйста, подождите",
|
||||
"sidebar-hide": "Скрыть меню"
|
||||
}
|
||||
|
||||
+30
-15
@@ -25,6 +25,14 @@
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.card-title-container {
|
||||
margin-top: 136px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
@@ -35,14 +43,19 @@
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 28px 0 40px 0;
|
||||
margin: 0;
|
||||
width: 496px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 38px;
|
||||
font-weight: 300;
|
||||
font-size: 56px;
|
||||
line-height: 100%;
|
||||
/* identical to box height, or 38px */
|
||||
background: linear-gradient(180deg, #BC75FF 0%, #798FFF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.demos_container {
|
||||
@@ -58,9 +71,7 @@
|
||||
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
.card-title {
|
||||
margin: 22px 0 40px 0;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
gap: 24px;
|
||||
@@ -69,14 +80,15 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
.card-title-container {
|
||||
margin-top: 96px;
|
||||
}
|
||||
.card-container {
|
||||
gap: 20px;
|
||||
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 42px 0 40px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
@@ -88,12 +100,15 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
.card-title {
|
||||
margin: 36px 0 32px 0;
|
||||
|
||||
.card-title-container {
|
||||
margin-bottom: 40px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 28px;
|
||||
font-size: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
@@ -106,4 +121,4 @@
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-6
@@ -5,6 +5,8 @@ import { useEffect } from "react";
|
||||
import { Redirect, Route, Switch, useHistory } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useQuery from "hooks/useQuery";
|
||||
import cookies from "js-cookie";
|
||||
|
||||
import { Header } from "components/shared/Header/Header";
|
||||
import { Card } from "components/pages/Main/Card/Card";
|
||||
@@ -15,12 +17,15 @@ import { PlanComponent } from "components/pages/Plan/PlanComponent";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { fetchCards } from "store/reducers/ActionCreator";
|
||||
import { cardSlice } from "store/reducers/cardSlice";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
import { languageSlice } from "store/reducers/languageSlice";
|
||||
import { createSession } from "store/reducers/ActionCreator";
|
||||
|
||||
|
||||
import { ICards } from "models/ICards";
|
||||
import useQuery from "hooks/useQuery";
|
||||
import { closeStream, load as loadStream } from "utils/app";
|
||||
|
||||
|
||||
import cookies from "js-cookie";
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +34,12 @@ const App: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const { handleCurrentCard } = cardSlice.actions;
|
||||
const { handleChangeLanguage } = languageSlice.actions;
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
|
||||
|
||||
const { cards, currentCard, error } = useAppSelector((state) => state.cardReducer);
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
const { isLoading } = useAppSelector((state) => state.sessionReducer);
|
||||
const query = useQuery()
|
||||
|
||||
const langQuery = query.get('lang')
|
||||
@@ -64,7 +72,14 @@ const App: React.FC = () => {
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const handleConnect = (title: string) => {
|
||||
dispatch(createSession(title)).unwrap().then((res) => {
|
||||
history.push(`/stream/${res.session_id}`);
|
||||
}).catch((res) => {
|
||||
alert(res);
|
||||
history.push("/");
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,6 +95,11 @@ const App: React.FC = () => {
|
||||
history.push("/connect-page");
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
closeStream()
|
||||
history.push('/')
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -87,7 +107,9 @@ const App: React.FC = () => {
|
||||
<Route exact path="/">
|
||||
<Header></Header>
|
||||
<div className="main">
|
||||
<h2 className="card-title">{error ? error : t("demo-title")}</h2>
|
||||
<div className="card-title-container">
|
||||
<h2 className="card-title">{error ? error : t("demo-title")}</h2>
|
||||
</div>
|
||||
<div className="card-container">
|
||||
{cards.map((i: ICards) => (
|
||||
<Card onClick={() => handleCards(i)} key={i._id} item={i}></Card>
|
||||
@@ -100,7 +122,7 @@ const App: React.FC = () => {
|
||||
<div className="background">
|
||||
<div className="popup-container">
|
||||
<div className="content__container">
|
||||
<PopupComponent></PopupComponent>
|
||||
<PopupComponent cleanErrors={cleanErrors} handleConnect={handleConnect} isLoading={isLoading} currentCard={currentCard}></PopupComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +131,7 @@ const App: React.FC = () => {
|
||||
)}
|
||||
</Route>
|
||||
<Route exact path="/stream/:id">
|
||||
<PlayerComponent ></PlayerComponent>
|
||||
<PlayerComponent handleDisconnect={handleDisconnect} loadStream={loadStream} cleanErrors={cleanErrors} ></PlayerComponent>
|
||||
</Route>
|
||||
<Route path="/plan">
|
||||
<Header></Header>
|
||||
|
||||
@@ -2,8 +2,6 @@ import "./LoadingPopup.css";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
|
||||
|
||||
export const LoadingPopup: React.FC<any> = ({ logo }) => {
|
||||
|
||||
@@ -17,7 +15,7 @@ export const LoadingPopup: React.FC<any> = ({ logo }) => {
|
||||
<div className="popup-img-container">
|
||||
<img className="popup-logo" src={logo} alt="лого" />
|
||||
</div>
|
||||
<span className="loading-caption">Пожалуйста подождите</span>
|
||||
<span className="loading-caption">{t('popup-loading')}</span>
|
||||
</div>
|
||||
<span className="loader"></span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
import { popupAnimation } from "utils/animationProps";
|
||||
@@ -6,16 +6,13 @@ import { popupAnimation } from "utils/animationProps";
|
||||
import { PopupConnect } from "components/pages/ConnectPage/PopupConnect/PopupConnect";
|
||||
import { LoadingPopup } from "components/pages/ConnectPage/LoadingPopup/LoadingPopup";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { useAppDispatch } from "hooks/redux";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
|
||||
export const PopupComponent: React.FC = () => {
|
||||
export const PopupComponent: React.FC<any> = ({isLoading, handleConnect, currentCard, cleanErrors}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { currentCard } = useAppSelector((state) => state.cardReducer);
|
||||
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
|
||||
const { isLoading } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +36,7 @@ export const PopupComponent: React.FC = () => {
|
||||
title={currentCard.app_title}
|
||||
isLoading={isLoading}
|
||||
logo={currentCard.logo}
|
||||
handleConnect={handleConnect}
|
||||
></PopupConnect>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -7,24 +7,9 @@ import { useAppDispatch } from "hooks/redux";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PopupConnect: React.FC<any> = ({ onConnect, logo, isLoading, title }) => {
|
||||
export const PopupConnect: React.FC<any> = ({ logo, isLoading, title, handleConnect }) => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
const handleConnect = () => {
|
||||
dispatch(createSession(title)).unwrap().then((res) => {
|
||||
history.push(`/stream/${res.payload.session_id}`);
|
||||
}).catch((res) => {
|
||||
alert(res);
|
||||
history.push("/");
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
console.log(isLoading)
|
||||
|
||||
return (
|
||||
<div className="popup">
|
||||
@@ -32,7 +17,7 @@ export const PopupConnect: React.FC<any> = ({ onConnect, logo, isLoading, title
|
||||
<img className="popup-logo" src={logo} alt="лого" />
|
||||
</div>
|
||||
<div className="popup-button-container">
|
||||
<button disabled={isLoading} onClick={handleConnect} className="button button-primary">
|
||||
<button disabled={isLoading} onClick={() => handleConnect(title)} className="button button-primary">
|
||||
{t("popup-main-btn-start")}
|
||||
</button>
|
||||
<div className="line"></div>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import "./PlayerStyles.css";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import { connectSession } from "store/reducers/ActionCreator";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
import useWindowDimensions from "hooks/useWindowDimensions";
|
||||
import { load as loadStream, usersArray } from "utils/app";
|
||||
import useMobile from "hooks/useMobile";
|
||||
|
||||
import { Sidebar } from "components/pages/Stream/Sidebar/Sidebar";
|
||||
@@ -18,7 +16,7 @@ type link = {
|
||||
|
||||
|
||||
|
||||
export const PlayerComponent: React.FC<any> = ({ }) => {
|
||||
export const PlayerComponent: React.FC<any> = ({ cleanErrors, handleDisconnect, loadStream }) => {
|
||||
const { isMobile } = useMobile();
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const width = windowDimensions.width;
|
||||
@@ -32,18 +30,19 @@ export const PlayerComponent: React.FC<any> = ({ }) => {
|
||||
const { id } = useParams<link>();
|
||||
const [click, setClick] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
const history = useHistory()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(connectSession(id)).unwrap().then(() => {
|
||||
loadStream()
|
||||
}).catch((res) => {
|
||||
alert(res);
|
||||
}).finally(() => {
|
||||
loadStream()
|
||||
});
|
||||
history.push('/')
|
||||
})
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
handleDisconnect()
|
||||
window.removeEventListener("change ", (event: any) => {
|
||||
setPopup(false);
|
||||
});
|
||||
@@ -65,7 +64,6 @@ export const PlayerComponent: React.FC<any> = ({ }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>{playerCount}</span>
|
||||
{popup && (
|
||||
<div className="popup-screen" style={{ height: height }}>
|
||||
<h2>Переверните устройство</h2>
|
||||
@@ -73,6 +71,7 @@ export const PlayerComponent: React.FC<any> = ({ }) => {
|
||||
)}
|
||||
<Player></Player>
|
||||
<Sidebar
|
||||
handleDisconnect={handleDisconnect}
|
||||
players={playerCount}
|
||||
heightDevice={height}
|
||||
isMobile={isMobile}
|
||||
|
||||
@@ -9,10 +9,9 @@ import { SidebarMobile } from "../SidebarMobile/SidebarMobile";
|
||||
import { closeStream } from "utils/app";
|
||||
import { useAppSelector } from "hooks/redux";
|
||||
|
||||
export const Sidebar: React.FC<any> = ({ exitPopup, isMobile, heightDevice, players }) => {
|
||||
export const Sidebar: React.FC<any> = ({ exitPopup, isMobile, heightDevice, players, handleDisconnect }) => {
|
||||
const [isMuted, setMuted] = useState(true);
|
||||
const [isControl, setControl] = useState(false);
|
||||
const history = useHistory()
|
||||
|
||||
const { playerCount } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
@@ -22,8 +21,7 @@ export const Sidebar: React.FC<any> = ({ exitPopup, isMobile, heightDevice, play
|
||||
|
||||
|
||||
const handleCloseStream = () => {
|
||||
closeStream()
|
||||
history.push('/')
|
||||
handleDisconnect()
|
||||
}
|
||||
|
||||
const handleMuteClick = () => {
|
||||
@@ -46,11 +44,11 @@ export const Sidebar: React.FC<any> = ({ exitPopup, isMobile, heightDevice, play
|
||||
handleMuteClick={handleMuteClick}
|
||||
handleControlClick={handleControlClick}
|
||||
closeStream={handleCloseStream}
|
||||
handleOpenExitPopup={handleCloseStream}
|
||||
></SidebarMobile>
|
||||
) : (
|
||||
<SidebarDesktop
|
||||
userArr={playerCount}
|
||||
|
||||
height={heightDevice}
|
||||
isMobile={isMobile}
|
||||
isMuted={isMuted}
|
||||
|
||||
@@ -86,7 +86,8 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
onHoverEnd={() => closeSideBar()}
|
||||
onHoverStart={() => setWideSidebar(true)}
|
||||
onHoverEnd={() => setWideSidebar(false)}
|
||||
initial={false}
|
||||
animate={open ? "open" : "closed"}
|
||||
variants={wideSidebar ? setAnimation() : sidebarVariants}
|
||||
@@ -110,7 +111,6 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
></UserList>
|
||||
</div>
|
||||
<motion.div
|
||||
onHoverStart={() => setWideSidebar(true)}
|
||||
className="toolbar-field-part"
|
||||
>
|
||||
<WideSidebarButton
|
||||
|
||||
@@ -3,8 +3,6 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
sidebarVariants,
|
||||
popupAnimation,
|
||||
wideSidebarVariants,
|
||||
wideSidebarAdminVariants,
|
||||
} from "utils/animationProps";
|
||||
import { useState } from "react";
|
||||
import { WideSidebarButton } from "../WideSidebarButton/WideSidebarButton";
|
||||
@@ -13,11 +11,12 @@ import { ControlButton } from "../ControlButton/ControlButton";
|
||||
import { MicroButton } from "../MicroButton/MicroButton";
|
||||
import { AdditionalButton } from "../AdditionalButton/AdditionalButton";
|
||||
import { UserListMobile } from "../UserListMoblie/UserListMobile";
|
||||
import { ExitButton } from "../ExitButton/ExitButton";
|
||||
|
||||
export const SidebarMobile: React.FC<any> = ({ height, isMobile }) => {
|
||||
export const SidebarMobile: React.FC<any> = ({ height, isMobile, handleOpenExitPopup }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [userList, setUserList] = useState(false);
|
||||
const [popupAdditional, setPopupAdditipnal] = useState(false);
|
||||
const [popupAdditional, setPopupAdditipnal] = useState(false); //should be insted of exit button, but popup doesn't ready yet
|
||||
console.log(userList, 'popup')
|
||||
return (
|
||||
<> <motion.div
|
||||
@@ -41,10 +40,7 @@ export const SidebarMobile: React.FC<any> = ({ height, isMobile }) => {
|
||||
<ControlButton></ControlButton>
|
||||
<MicroButton></MicroButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<AdditionalButton
|
||||
active={popupAdditional}
|
||||
onClick={() => setPopupAdditipnal(true)}
|
||||
></AdditionalButton>
|
||||
<ExitButton isSidebarWide={false} onClick={handleOpenExitPopup}></ExitButton>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{!open && (
|
||||
|
||||
@@ -10,8 +10,8 @@ export const WideSidebarButton: React.FC<any> = ({ close, isSidebarWide }) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const [button, setButton] = useState({
|
||||
icon: wideButton,
|
||||
inactive: "Скрыть меню",
|
||||
active: "Скрыть меню",
|
||||
inactive: "sidebar-hide",
|
||||
active: "sidebar-hide",
|
||||
type: "fullscreen",
|
||||
noHover: true,
|
||||
});
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { ACTIONS } from "../socket/actions";
|
||||
import socketInit from "../socket";
|
||||
import freeice from "freeice";
|
||||
import { useStateWithCallback } from "./useStateWithCallback";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
export const useWebRTC = (roomId, user) => {
|
||||
const history = useHistory();
|
||||
const [clients, setClients] = useStateWithCallback([]);
|
||||
const audioElements = useRef({});
|
||||
const [message, setMessages] = useState("");
|
||||
const [control, setControl] = useState('')
|
||||
const [warning, setWarning] = useState("");
|
||||
const connections = useRef({});
|
||||
const socket = useRef(null);
|
||||
const localMediaStream = useRef(null);
|
||||
const clientsRef = useRef(null);
|
||||
|
||||
const addNewClient = useCallback(
|
||||
(newClient, cb) => {
|
||||
const lookingFor = clients.find((client) => client.id === newClient.id);
|
||||
|
||||
if (lookingFor === undefined) {
|
||||
setClients((existingClients) => [...existingClients, newClient], cb);
|
||||
}
|
||||
},
|
||||
[clients, setClients]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
clientsRef.current = clients;
|
||||
}, [clients]);
|
||||
|
||||
useEffect(() => {
|
||||
const initChat = async () => {
|
||||
socket.current = socketInit();
|
||||
console.log(localMediaStream.current)
|
||||
await captureMedia();
|
||||
addNewClient(
|
||||
{
|
||||
...user,
|
||||
muted: true,
|
||||
admin: false,
|
||||
control: false,
|
||||
},
|
||||
() => {
|
||||
const localElement = audioElements.current[user.id];
|
||||
if (localElement) {
|
||||
localElement.volume = 0;
|
||||
localElement.srcObject = localMediaStream.current;
|
||||
}
|
||||
}
|
||||
);
|
||||
socket.current.on(ACTIONS.MUTE_INFO, ({ userId, isMute, isAdmin }) => {
|
||||
handleSetMute(isMute, userId);
|
||||
});
|
||||
|
||||
socket.current.on(ACTIONS.ADD_PEER, handleNewPeer);
|
||||
socket.current.on(ACTIONS.REMOVE_PEER, handleRemovePeer);
|
||||
socket.current.on(ACTIONS.ICE_CANDIDATE, handleIceCandidate);
|
||||
socket.current.on(ACTIONS.SESSION_DESCRIPTION, setRemoteMedia);
|
||||
socket.current.on(ACTIONS.MUTE, ({ peerId, userId }) => {
|
||||
handleSetMute(true, userId);
|
||||
});
|
||||
|
||||
socket.current.on("ADMIN-LEFT", ({ isLeft }) => {
|
||||
setWarning(isLeft);
|
||||
});
|
||||
|
||||
socket.current.on("ADMIN-SET", ({ admin }) => {
|
||||
handleSetAdmin(true, admin);
|
||||
setControl(admin)
|
||||
});
|
||||
|
||||
socket.current.on(ACTIONS.UNMUTE, ({ peerId, userId }) => {
|
||||
handleSetMute(false, userId);
|
||||
});
|
||||
socket.current.emit(ACTIONS.JOIN, {
|
||||
roomId,
|
||||
user,
|
||||
});
|
||||
|
||||
socket.current.on("CONTROL-SET", ({ userId }) => {
|
||||
handleSetControl(userId);
|
||||
setControl(userId)
|
||||
});
|
||||
|
||||
async function captureMedia() {
|
||||
// Start capturing local audio stream.
|
||||
localMediaStream.current = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
}).catch(() => {
|
||||
alert('Подключите микрофон!')
|
||||
});
|
||||
}
|
||||
|
||||
socket.current.on("REQUEST-CONTROL", ({ userID }) => {
|
||||
setMessages(userID);
|
||||
});
|
||||
|
||||
async function handleNewPeer({ peerId, createOffer, user: remoteUser }) {
|
||||
if (peerId in connections.current) {
|
||||
return console.warn(
|
||||
`You are already connected with ${peerId} (${user.name})`
|
||||
);
|
||||
}
|
||||
|
||||
// Store it to connections
|
||||
connections.current[peerId] = new RTCPeerConnection({
|
||||
iceServers: freeice(),
|
||||
});
|
||||
|
||||
// Handle new ice candidate on this peer connection
|
||||
connections.current[peerId].onicecandidate = (event) => {
|
||||
socket.current.emit(ACTIONS.RELAY_ICE, {
|
||||
peerId,
|
||||
icecandidate: event.candidate,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle on track event on this connection
|
||||
connections.current[peerId].ontrack = ({ streams: [remoteStream] }) => {
|
||||
addNewClient(
|
||||
{
|
||||
...remoteUser,
|
||||
muted: true,
|
||||
admin: false,
|
||||
control: false,
|
||||
},
|
||||
() => {
|
||||
// get current users mute info
|
||||
const currentUser = clientsRef.current.find(
|
||||
(client) => client.id === user.id
|
||||
);
|
||||
if (currentUser) {
|
||||
socket.current.emit(ACTIONS.MUTE_INFO, {
|
||||
userId: user.id,
|
||||
roomId,
|
||||
isMute: currentUser.muted,
|
||||
isAdmin: currentUser.admin,
|
||||
isControl: currentUser.control,
|
||||
});
|
||||
}
|
||||
if (audioElements.current[remoteUser.id]) {
|
||||
audioElements.current[remoteUser.id].srcObject = remoteStream;
|
||||
} else {
|
||||
let settled = false;
|
||||
const interval = setInterval(() => {
|
||||
if (audioElements.current[remoteUser.id]) {
|
||||
audioElements.current[remoteUser.id].srcObject =
|
||||
remoteStream;
|
||||
settled = true;
|
||||
}
|
||||
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Add connection to peer connections track
|
||||
localMediaStream.current.getTracks().forEach((track) => {
|
||||
connections.current[peerId].addTrack(track, localMediaStream.current);
|
||||
});
|
||||
|
||||
// Create an offer if required
|
||||
if (createOffer) {
|
||||
const offer = await connections.current[peerId].createOffer();
|
||||
|
||||
// Set as local description
|
||||
await connections.current[peerId].setLocalDescription(offer);
|
||||
|
||||
// send offer to the server
|
||||
socket.current.emit(ACTIONS.RELAY_SDP, {
|
||||
peerId,
|
||||
sessionDescription: offer,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function handleRemovePeer({ peerId, userId }) {
|
||||
console.log(userId);
|
||||
// Correction: peerID to peerId
|
||||
if (connections.current[peerId]) {
|
||||
connections.current[peerId].close();
|
||||
}
|
||||
|
||||
delete connections.current[peerId];
|
||||
delete audioElements.current[peerId];
|
||||
setClients((list) => handleLogout(list, userId));
|
||||
}
|
||||
async function handleIceCandidate({ peerId, icecandidate }) {
|
||||
if (icecandidate) {
|
||||
connections.current[peerId].addIceCandidate(icecandidate);
|
||||
}
|
||||
}
|
||||
async function setRemoteMedia({
|
||||
peerId,
|
||||
sessionDescription: remoteSessionDescription,
|
||||
}) {
|
||||
connections.current[peerId].setRemoteDescription(
|
||||
new RTCSessionDescription(remoteSessionDescription)
|
||||
);
|
||||
|
||||
// If session descrition is offer then create an answer
|
||||
if (remoteSessionDescription.type === "offer") {
|
||||
const connection = connections.current[peerId];
|
||||
|
||||
const answer = await connection.createAnswer();
|
||||
connection.setLocalDescription(answer);
|
||||
|
||||
socket.current.emit(ACTIONS.RELAY_SDP, {
|
||||
peerId,
|
||||
sessionDescription: answer,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function handleSetMute(mute, userId) {
|
||||
console.log(clientsRef.current);
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].muted = mute;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetControl(userId) {
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
for (let i = 0; i < allConnectedClients.length; i++) {
|
||||
allConnectedClients[i].control = false;
|
||||
}
|
||||
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].control = true;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetAdmin(admin, userId) {
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].admin = admin;
|
||||
allConnectedClients[clientIdx].control = true;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initChat();
|
||||
return () => {
|
||||
if (localMediaStream.current) {
|
||||
localMediaStream.current.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
socket.current.emit(ACTIONS.LEAVE, { roomId });
|
||||
for (let peerId in connections.current) {
|
||||
connections.current[peerId].close();
|
||||
delete connections.current[peerId];
|
||||
delete audioElements.current[peerId];
|
||||
}
|
||||
socket.current.off(ACTIONS.ADD_PEER);
|
||||
socket.current.off(ACTIONS.REMOVE_PEER);
|
||||
socket.current.off(ACTIONS.ICE_CANDIDATE);
|
||||
socket.current.off(ACTIONS.SESSION_DESCRIPTION);
|
||||
socket.current.off(ACTIONS.MUTE);
|
||||
socket.current.off(ACTIONS.UNMUTE);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const provideRef = (instance, userId) => {
|
||||
audioElements.current[userId] = instance;
|
||||
};
|
||||
|
||||
const handleAdmin = () => {
|
||||
let settled = false;
|
||||
let interval = setInterval(() => {
|
||||
socket.current.emit("ADMIN-SET", {
|
||||
roomId,
|
||||
});
|
||||
|
||||
settled = true;
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleMute = (isMute, userId) => {
|
||||
let settled = false;
|
||||
|
||||
if (userId === user.id) {
|
||||
let interval = setInterval(() => {
|
||||
if (localMediaStream.current) {
|
||||
localMediaStream.current.getTracks()[0].enabled = !isMute;
|
||||
if (isMute) {
|
||||
socket.current.emit(ACTIONS.MUTE, {
|
||||
roomId,
|
||||
userId: user.id,
|
||||
});
|
||||
} else {
|
||||
socket.current.emit(ACTIONS.UNMUTE, {
|
||||
roomId,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
settled = true;
|
||||
}
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleControl = (userId) => {
|
||||
let settled = false;
|
||||
let interval = setInterval(() => {
|
||||
socket.current.emit("CONTROL-SET", {
|
||||
roomId,
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
settled = true;
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleLogout = (list, userId) => {
|
||||
const isAdmin = list.some((i) => i.admin === true && i.id === userId);
|
||||
const isControl = list.some((i) => i.control === true && i.id === userId);
|
||||
console.log(isControl);
|
||||
if (isAdmin) {
|
||||
socket.current.emit("ADMIN-LEFT", {
|
||||
isLeft: true,
|
||||
});
|
||||
} else if (isControl) {
|
||||
handleChangeControl(list);
|
||||
}
|
||||
|
||||
return list.filter((c) => c.id !== userId);
|
||||
};
|
||||
|
||||
const handleChangeControl = (list) => {
|
||||
const admin = list.find((i) => i.admin === true);
|
||||
if (admin) {
|
||||
handleControl(admin.id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleReturnControl = () => {
|
||||
const admin = clients.find((i) => i.admin === true);
|
||||
if (admin) {
|
||||
handleControl(admin.id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const sendRequset = (user) => {
|
||||
socket.current.emit("REQUEST-CONTROL", {
|
||||
userID: user.id,
|
||||
roomId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
clients,
|
||||
provideRef,
|
||||
warning,
|
||||
handleMute,
|
||||
handleControl,
|
||||
sendRequset,
|
||||
message,
|
||||
control,
|
||||
handleAdmin,
|
||||
handleReturnControl,
|
||||
};
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from 'axios';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://a1.test.coord.graff.tech',
|
||||
baseURL: 'https://a1.coord.graff.tech',
|
||||
});
|
||||
|
||||
instance.defaults.headers.post['Content-Type'] = 'application/json';
|
||||
|
||||
+138
-135
@@ -998,10 +998,10 @@ function showConnectOverlay() {
|
||||
startText.id = 'container'
|
||||
let title = document.createElement('h2')
|
||||
title.id = "title"
|
||||
title.innerHTML = 'Демонстрация начата'
|
||||
title.innerHTML = store.getState().languageReducer.currentLang === 'ru' ? 'Демонстрация начата' : 'Demonstration is started'
|
||||
let caption = document.createElement('p')
|
||||
caption.id = 'caption'
|
||||
caption.innerHTML = 'Нажмите, чтобы продолжить'
|
||||
caption.innerHTML = store.getState().languageReducer.currentLang === 'ru' ? 'Нажмите, чтобы продолжить' : "Click to continue"
|
||||
let headerContainer = document.createElement('div')
|
||||
headerContainer.appendChild(title)
|
||||
headerContainer.appendChild(caption)
|
||||
@@ -1012,7 +1012,7 @@ function showConnectOverlay() {
|
||||
startText.appendChild(playButton)
|
||||
let buttonBack = document.createElement('a')
|
||||
let captionBack = document.createElement('div')
|
||||
captionBack.innerHTML = 'Выбор жилого комплекса'
|
||||
captionBack.innerHTML = store.getState().languageReducer.currentLang === 'ru' ? 'Выбор жилого комплекса' : 'Back to selection'
|
||||
buttonBack.appendChild(captionBack)
|
||||
buttonBack.id = 'link'
|
||||
buttonBack.href = 'https://stream.graff.tech/'
|
||||
@@ -1373,6 +1373,7 @@ function setupWebRtcPlayer(htmlElement, config) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
webRtcPlayerObj.onNewVideoTrack = function (streams) {
|
||||
if (webRtcPlayerObj.video && webRtcPlayerObj.video.srcObject && webRtcPlayerObj.onVideoInitialised) {
|
||||
webRtcPlayerObj.onVideoInitialised();
|
||||
@@ -1405,148 +1406,150 @@ function setupWebRtcPlayer(htmlElement, config) {
|
||||
}
|
||||
|
||||
function setupStats() {
|
||||
webRtcPlayerObj.aggregateStats(1 * 1000 /*Check every 1 second*/);
|
||||
if (webRtcPlayerObj) {
|
||||
webRtcPlayerObj.aggregateStats(1 * 1000 /*Check every 1 second*/);
|
||||
|
||||
let printInterval = 5 * 60 * 1000; /*Print every 5 minutes*/
|
||||
let nextPrintDuration = printInterval;
|
||||
let printInterval = 5 * 60 * 1000; /*Print every 5 minutes*/
|
||||
let nextPrintDuration = printInterval;
|
||||
|
||||
webRtcPlayerObj.onAggregatedStats = (aggregatedStats) => {
|
||||
let numberFormat = new Intl.NumberFormat(window.navigator.language, {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
let timeFormat = new Intl.NumberFormat(window.navigator.language, {
|
||||
maximumFractionDigits: 0,
|
||||
minimumIntegerDigits: 2
|
||||
});
|
||||
webRtcPlayerObj.onAggregatedStats = (aggregatedStats) => {
|
||||
let numberFormat = new Intl.NumberFormat(window.navigator.language, {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
let timeFormat = new Intl.NumberFormat(window.navigator.language, {
|
||||
maximumFractionDigits: 0,
|
||||
minimumIntegerDigits: 2
|
||||
});
|
||||
|
||||
// Calculate duration of run
|
||||
let runTime = (aggregatedStats.timestamp - aggregatedStats.timestampStart) / 1000;
|
||||
let timeValues = [];
|
||||
let timeDurations = [60, 60];
|
||||
for (let timeIndex = 0; timeIndex < timeDurations.length; timeIndex++) {
|
||||
timeValues.push(runTime % timeDurations[timeIndex]);
|
||||
runTime = runTime / timeDurations[timeIndex];
|
||||
}
|
||||
timeValues.push(runTime);
|
||||
// Calculate duration of run
|
||||
let runTime = (aggregatedStats.timestamp - aggregatedStats.timestampStart) / 1000;
|
||||
let timeValues = [];
|
||||
let timeDurations = [60, 60];
|
||||
for (let timeIndex = 0; timeIndex < timeDurations.length; timeIndex++) {
|
||||
timeValues.push(runTime % timeDurations[timeIndex]);
|
||||
runTime = runTime / timeDurations[timeIndex];
|
||||
}
|
||||
timeValues.push(runTime);
|
||||
|
||||
let runTimeSeconds = timeValues[0];
|
||||
let runTimeMinutes = Math.floor(timeValues[1]);
|
||||
let runTimeHours = Math.floor([timeValues[2]]);
|
||||
let runTimeSeconds = timeValues[0];
|
||||
let runTimeMinutes = Math.floor(timeValues[1]);
|
||||
let runTimeHours = Math.floor([timeValues[2]]);
|
||||
|
||||
let receivedBytesMeasurement = 'B';
|
||||
let receivedBytes = aggregatedStats.hasOwnProperty('bytesReceived') ? aggregatedStats.bytesReceived : 0;
|
||||
let dataMeasurements = ['kB', 'MB', 'GB'];
|
||||
for (let index = 0; index < dataMeasurements.length; index++) {
|
||||
if (receivedBytes < 100 * 1000)
|
||||
break;
|
||||
receivedBytes = receivedBytes / 1000;
|
||||
receivedBytesMeasurement = dataMeasurements[index];
|
||||
}
|
||||
let receivedBytesMeasurement = 'B';
|
||||
let receivedBytes = aggregatedStats.hasOwnProperty('bytesReceived') ? aggregatedStats.bytesReceived : 0;
|
||||
let dataMeasurements = ['kB', 'MB', 'GB'];
|
||||
for (let index = 0; index < dataMeasurements.length; index++) {
|
||||
if (receivedBytes < 100 * 1000)
|
||||
break;
|
||||
receivedBytes = receivedBytes / 1000;
|
||||
receivedBytesMeasurement = dataMeasurements[index];
|
||||
}
|
||||
|
||||
let qualityStatus = document.getElementById("connectionStrength");
|
||||
// "blinks" quality status element for 1 sec by making it transparent, speed = number of blinks
|
||||
let blinkQualityStatus = function (speed) {
|
||||
let iter = speed;
|
||||
let opacity = 1; // [0..1]
|
||||
let tickId = setInterval(
|
||||
function () {
|
||||
opacity -= 0.1;
|
||||
// map `opacity` to [-0.5..0.5] range, decrement by 0.2 per step and take `abs` to make it blink: 1 -> 0 -> 1
|
||||
qualityStatus.style.opacity = `${Math.abs((opacity - 0.5) * 2)}`;
|
||||
if (opacity <= 0.1) {
|
||||
if (--iter == 0) {
|
||||
clearInterval(tickId);
|
||||
} else { // next blink
|
||||
opacity = 1;
|
||||
let qualityStatus = document.getElementById("connectionStrength");
|
||||
// "blinks" quality status element for 1 sec by making it transparent, speed = number of blinks
|
||||
let blinkQualityStatus = function (speed) {
|
||||
let iter = speed;
|
||||
let opacity = 1; // [0..1]
|
||||
let tickId = setInterval(
|
||||
function () {
|
||||
opacity -= 0.1;
|
||||
// map `opacity` to [-0.5..0.5] range, decrement by 0.2 per step and take `abs` to make it blink: 1 -> 0 -> 1
|
||||
qualityStatus.style.opacity = `${Math.abs((opacity - 0.5) * 2)}`;
|
||||
if (opacity <= 0.1) {
|
||||
if (--iter == 0) {
|
||||
clearInterval(tickId);
|
||||
} else { // next blink
|
||||
opacity = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
100 / speed // msecs
|
||||
);
|
||||
};
|
||||
|
||||
const orangeQP = 26;
|
||||
const redQP = 35;
|
||||
|
||||
let statsText = '';
|
||||
let color;
|
||||
|
||||
// Wifi strength elements
|
||||
|
||||
if (VideoEncoderQP > redQP) {
|
||||
|
||||
|
||||
} else if (VideoEncoderQP > orangeQP) {
|
||||
|
||||
} else {
|
||||
}
|
||||
statsText += `<div>Duration: ${timeFormat.format(runTimeHours)}:${timeFormat.format(runTimeMinutes)}:${timeFormat.format(runTimeSeconds)}</div>`;
|
||||
statsText += `<div>Controls stream input: ${inputController === null ? "Not sent yet" : (inputController ? "true" : "false")}</div>`;
|
||||
statsText += `<div>Audio codec: ${aggregatedStats.hasOwnProperty('audioCodec') ? aggregatedStats.audioCodec : "Not set"}</div>`;
|
||||
statsText += `<div>Video codec: ${aggregatedStats.hasOwnProperty('videoCodec') ? aggregatedStats.videoCodec : "Not set"}</div>`;
|
||||
statsText += `<div>Video Resolution: ${aggregatedStats.hasOwnProperty('frameWidth') && aggregatedStats.frameWidth && aggregatedStats.hasOwnProperty('frameHeight') && aggregatedStats.frameHeight ?
|
||||
aggregatedStats.frameWidth + 'x' + aggregatedStats.frameHeight : 'Chrome only'
|
||||
}</div>`;
|
||||
statsText += `<div>Received (${receivedBytesMeasurement}): ${numberFormat.format(receivedBytes)}</div>`;
|
||||
statsText += `<div>Frames Decoded: ${aggregatedStats.hasOwnProperty('framesDecoded') ? numberFormat.format(aggregatedStats.framesDecoded) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Packets Lost: ${aggregatedStats.hasOwnProperty('packetsLost') ? numberFormat.format(aggregatedStats.packetsLost) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Framerate: ${aggregatedStats.hasOwnProperty('framerate') ? numberFormat.format(aggregatedStats.framerate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Frames dropped: ${aggregatedStats.hasOwnProperty('framesDropped') ? numberFormat.format(aggregatedStats.framesDropped) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Net RTT (ms): ${aggregatedStats.hasOwnProperty('currentRoundTripTime') ? numberFormat.format(aggregatedStats.currentRoundTripTime * 1000) : 'Can\'t calculate'}</div>`;
|
||||
statsText += `<div>Browser receive to composite (ms): ${aggregatedStats.hasOwnProperty('receiveToCompositeMs') ? numberFormat.format(aggregatedStats.receiveToCompositeMs) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Audio Bitrate (kbps): ${aggregatedStats.hasOwnProperty('audioBitrate') ? numberFormat.format(aggregatedStats.audioBitrate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Video Bitrate (kbps): ${aggregatedStats.hasOwnProperty('bitrate') ? numberFormat.format(aggregatedStats.bitrate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Video Quantization Parameter: ${VideoEncoderQP}</div>`;
|
||||
|
||||
let statsDiv = document.getElementById("stats");
|
||||
statsDiv.innerHTML = statsText;
|
||||
|
||||
if (print_stats) {
|
||||
if (aggregatedStats.timestampStart) {
|
||||
if ((aggregatedStats.timestamp - aggregatedStats.timestampStart) > nextPrintDuration) {
|
||||
if (ws && ws.readyState === WS_OPEN_STATE) {
|
||||
console.log(`-> SS: stats\n${JSON.stringify(aggregatedStats)}`);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'stats',
|
||||
data: aggregatedStats
|
||||
}));
|
||||
}
|
||||
nextPrintDuration += printInterval;
|
||||
}
|
||||
},
|
||||
100 / speed // msecs
|
||||
);
|
||||
};
|
||||
|
||||
const orangeQP = 26;
|
||||
const redQP = 35;
|
||||
|
||||
let statsText = '';
|
||||
let color;
|
||||
|
||||
// Wifi strength elements
|
||||
|
||||
if (VideoEncoderQP > redQP) {
|
||||
|
||||
|
||||
} else if (VideoEncoderQP > orangeQP) {
|
||||
|
||||
} else {
|
||||
}
|
||||
statsText += `<div>Duration: ${timeFormat.format(runTimeHours)}:${timeFormat.format(runTimeMinutes)}:${timeFormat.format(runTimeSeconds)}</div>`;
|
||||
statsText += `<div>Controls stream input: ${inputController === null ? "Not sent yet" : (inputController ? "true" : "false")}</div>`;
|
||||
statsText += `<div>Audio codec: ${aggregatedStats.hasOwnProperty('audioCodec') ? aggregatedStats.audioCodec : "Not set"}</div>`;
|
||||
statsText += `<div>Video codec: ${aggregatedStats.hasOwnProperty('videoCodec') ? aggregatedStats.videoCodec : "Not set"}</div>`;
|
||||
statsText += `<div>Video Resolution: ${aggregatedStats.hasOwnProperty('frameWidth') && aggregatedStats.frameWidth && aggregatedStats.hasOwnProperty('frameHeight') && aggregatedStats.frameHeight ?
|
||||
aggregatedStats.frameWidth + 'x' + aggregatedStats.frameHeight : 'Chrome only'
|
||||
}</div>`;
|
||||
statsText += `<div>Received (${receivedBytesMeasurement}): ${numberFormat.format(receivedBytes)}</div>`;
|
||||
statsText += `<div>Frames Decoded: ${aggregatedStats.hasOwnProperty('framesDecoded') ? numberFormat.format(aggregatedStats.framesDecoded) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Packets Lost: ${aggregatedStats.hasOwnProperty('packetsLost') ? numberFormat.format(aggregatedStats.packetsLost) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Framerate: ${aggregatedStats.hasOwnProperty('framerate') ? numberFormat.format(aggregatedStats.framerate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Frames dropped: ${aggregatedStats.hasOwnProperty('framesDropped') ? numberFormat.format(aggregatedStats.framesDropped) : 'Chrome only'}</div>`;
|
||||
statsText += `<div>Net RTT (ms): ${aggregatedStats.hasOwnProperty('currentRoundTripTime') ? numberFormat.format(aggregatedStats.currentRoundTripTime * 1000) : 'Can\'t calculate'}</div>`;
|
||||
statsText += `<div>Browser receive to composite (ms): ${aggregatedStats.hasOwnProperty('receiveToCompositeMs') ? numberFormat.format(aggregatedStats.receiveToCompositeMs) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Audio Bitrate (kbps): ${aggregatedStats.hasOwnProperty('audioBitrate') ? numberFormat.format(aggregatedStats.audioBitrate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Video Bitrate (kbps): ${aggregatedStats.hasOwnProperty('bitrate') ? numberFormat.format(aggregatedStats.bitrate) : 'Chrome only'}</div>`;
|
||||
statsText += `<div style="color: ${color}">Video Quantization Parameter: ${VideoEncoderQP}</div>`;
|
||||
|
||||
let statsDiv = document.getElementById("stats");
|
||||
statsDiv.innerHTML = statsText;
|
||||
|
||||
if (print_stats) {
|
||||
if (aggregatedStats.timestampStart) {
|
||||
if ((aggregatedStats.timestamp - aggregatedStats.timestampStart) > nextPrintDuration) {
|
||||
if (ws && ws.readyState === WS_OPEN_STATE) {
|
||||
console.log(`-> SS: stats\n${JSON.stringify(aggregatedStats)}`);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'stats',
|
||||
data: aggregatedStats
|
||||
}));
|
||||
}
|
||||
nextPrintDuration += printInterval;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
webRtcPlayerObj.latencyTestTimings.OnAllLatencyTimingsReady = function (timings) {
|
||||
|
||||
if (!timings.BrowserReceiptTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let latencyExcludingDecode = timings.BrowserReceiptTimeMs - timings.TestStartTimeMs;
|
||||
let encodeLatency = timings.UEEncodeMs;
|
||||
let uePixelStreamLatency = timings.UECaptureToSendMs;
|
||||
let ueTestDuration = timings.UETransmissionTimeMs - timings.UEReceiptTimeMs;
|
||||
let networkLatency = latencyExcludingDecode - ueTestDuration;
|
||||
|
||||
//these ones depend on FrameDisplayDeltaTimeMs
|
||||
let endToEndLatency = null;
|
||||
let browserSideLatency = null;
|
||||
|
||||
if (timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs) {
|
||||
endToEndLatency = timings.FrameDisplayDeltaTimeMs + networkLatency + (typeof uePixelStreamLatency === "string" ? 0 : uePixelStreamLatency);
|
||||
browserSideLatency = timings.FrameDisplayDeltaTimeMs + (latencyExcludingDecode - networkLatency - ueTestDuration);
|
||||
}
|
||||
|
||||
let latencyStatsInnerHTML = '';
|
||||
latencyStatsInnerHTML += `<div>Net latency RTT (ms): ${networkLatency.toFixed(2)}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE Encode (ms): ${(typeof encodeLatency === "string" ? encodeLatency : encodeLatency.toFixed(2))}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE Send to capture (ms): ${(typeof uePixelStreamLatency === "string" ? uePixelStreamLatency : uePixelStreamLatency.toFixed(2))}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE probe duration (ms): ${ueTestDuration.toFixed(2)}</div>`;
|
||||
latencyStatsInnerHTML += timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs ? `<div>Browser composite latency (ms): ${timings.FrameDisplayDeltaTimeMs.toFixed(2)}</div>` : "";
|
||||
latencyStatsInnerHTML += browserSideLatency ? `<div>Total browser latency (ms): ${browserSideLatency.toFixed(2)}</div>` : "";
|
||||
latencyStatsInnerHTML += endToEndLatency ? `<div>Total latency (ms): ${endToEndLatency.toFixed(2)}</div>` : "";
|
||||
document.getElementById("LatencyStats").innerHTML = latencyStatsInnerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
webRtcPlayerObj.latencyTestTimings.OnAllLatencyTimingsReady = function (timings) {
|
||||
|
||||
if (!timings.BrowserReceiptTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let latencyExcludingDecode = timings.BrowserReceiptTimeMs - timings.TestStartTimeMs;
|
||||
let encodeLatency = timings.UEEncodeMs;
|
||||
let uePixelStreamLatency = timings.UECaptureToSendMs;
|
||||
let ueTestDuration = timings.UETransmissionTimeMs - timings.UEReceiptTimeMs;
|
||||
let networkLatency = latencyExcludingDecode - ueTestDuration;
|
||||
|
||||
//these ones depend on FrameDisplayDeltaTimeMs
|
||||
let endToEndLatency = null;
|
||||
let browserSideLatency = null;
|
||||
|
||||
if (timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs) {
|
||||
endToEndLatency = timings.FrameDisplayDeltaTimeMs + networkLatency + (typeof uePixelStreamLatency === "string" ? 0 : uePixelStreamLatency);
|
||||
browserSideLatency = timings.FrameDisplayDeltaTimeMs + (latencyExcludingDecode - networkLatency - ueTestDuration);
|
||||
}
|
||||
|
||||
let latencyStatsInnerHTML = '';
|
||||
latencyStatsInnerHTML += `<div>Net latency RTT (ms): ${networkLatency.toFixed(2)}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE Encode (ms): ${(typeof encodeLatency === "string" ? encodeLatency : encodeLatency.toFixed(2))}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE Send to capture (ms): ${(typeof uePixelStreamLatency === "string" ? uePixelStreamLatency : uePixelStreamLatency.toFixed(2))}</div>`;
|
||||
latencyStatsInnerHTML += `<div>UE probe duration (ms): ${ueTestDuration.toFixed(2)}</div>`;
|
||||
latencyStatsInnerHTML += timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs ? `<div>Browser composite latency (ms): ${timings.FrameDisplayDeltaTimeMs.toFixed(2)}</div>` : "";
|
||||
latencyStatsInnerHTML += browserSideLatency ? `<div>Total browser latency (ms): ${browserSideLatency.toFixed(2)}</div>` : "";
|
||||
latencyStatsInnerHTML += endToEndLatency ? `<div>Total latency (ms): ${endToEndLatency.toFixed(2)}</div>` : "";
|
||||
document.getElementById("LatencyStats").innerHTML = latencyStatsInnerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2700,7 +2703,7 @@ function connect() {
|
||||
}
|
||||
|
||||
// Make a new websocket connection
|
||||
let connectionUrl = 'wss://a2.sess.graff.tech/s2/14000/'
|
||||
let connectionUrl = store.getState().sessionReducer.url
|
||||
console.log(`Creating a websocket connection to: ${connectionUrl}`);
|
||||
ws = new WebSocket(connectionUrl);
|
||||
ws.attemptStreamReconnection = true;
|
||||
|
||||
Reference in New Issue
Block a user