Compare commits

...

2 Commits

Author SHA1 Message Date
DmitriyB 65bc4a011a refactoring 2023-03-22 13:54:05 +05:00
DmitriyB 4d9bd5c98b refactoring 2023-03-22 13:53:43 +05:00
15 changed files with 229 additions and 613 deletions
+3 -1
View File
@@ -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"
}
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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,
});
-401
View File
@@ -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,
};
};
+1 -1
View File
@@ -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
View File
@@ -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;