Skip to content

Commit 45898db

Browse files
Merge pull request #571 from EscolaLMS/feature/jitsy-updates
REK-170 Jitsy closing update, add webinar links on mobile
2 parents 88d8857 + e30aebe commit 45898db

6 files changed

Lines changed: 107 additions & 39 deletions

File tree

‎src/components/Consultations/ConsultationCard/JitsyMeeting/index.tsx‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import { API_URL } from "@/config/index";
1212
import useCamera, { cameraPermissions } from "@/hooks/meeting/useCamera";
1313
import { API } from "@escolalms/sdk/lib";
1414
import { IMeetRecording } from "@/components/Consultations/ConsultationCard/JitsyMeeting/types";
15-
import {
16-
JITSY_ANALYTICS_INTERVAL,
17-
JITSY_TUTOR_INTERVAL,
18-
} from "@/utils/constants";
15+
import { JITSY_ANALYTICS_INTERVAL } from "@/utils/constants";
1916
import { Text } from "@escolalms/components/lib/components/atoms/Typography/Text";
2017
import { Button } from "@escolalms/components/lib/components/atoms/Button/Button";
2118
import { useJitsyAnalyticsControl } from "@/hooks/meeting/useJitsyAnalyticsControl";
@@ -78,7 +75,7 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
7875
const { consultation, token } = useContext(EscolaLMSContext);
7976
const { isStudent } = useRoles();
8077
const { t } = useTranslation();
81-
const { camera, getDataUrl, cameraAccessStatus } = useCamera();
78+
const { camera, getDataUrl, cameraAccessStatus, stopCamera } = useCamera();
8279
const userConsentedRef = useRef(false);
8380
const isCameraMutedRef = useRef(false);
8481

@@ -91,6 +88,8 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
9188
const workerRef = useRef<Worker | null>(null);
9289
const apiRef = useRef<IJitsiMeetExternalApi | null>(null);
9390
const recordingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
91+
const hasSentEndEventRef = useRef(false);
92+
9493
const { shouldRunAnalytics } = useJitsyAnalyticsControl({
9594
modelType,
9695
participantCount,
@@ -140,6 +139,13 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
140139
const sendRecordingEvent = useCallback(
141140
async (action: "start-recording" | "end-recording") => {
142141
if (isStudent) return;
142+
143+
if (
144+
action === "end-recording" &&
145+
(hasSentEndEventRef.current || !recordingIdRef.current)
146+
) {
147+
return;
148+
}
143149
try {
144150
const payload = preparePayload(action);
145151

@@ -156,6 +162,10 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
156162
}
157163
);
158164

165+
if (action === "end-recording") {
166+
recordingIdRef.current = null;
167+
}
168+
159169
const result = await response.json();
160170

161171
if (action === "start-recording" && result?.data?.id) {
@@ -364,13 +374,25 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
364374
useEffect(() => {
365375
const init = async () => {
366376
try {
367-
await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
377+
const stream = await navigator.mediaDevices.getUserMedia({
378+
video: true,
379+
audio: true,
380+
});
381+
382+
stream.getTracks().forEach((track) => {
383+
track.stop();
384+
console.log("Init test track stopped");
385+
});
386+
368387
isStudent ? setShowModal(true) : setShowMeeting(true);
369-
} catch {
388+
} catch (err) {
389+
console.warn("Camera access failed in init", err);
370390
setShowMeeting(true);
371391
}
372392
};
373393
init();
394+
395+
return () => {};
374396
}, [isStudent]);
375397

376398
const getProperRoomName = () => {
@@ -393,6 +415,7 @@ const JitsyMeeting: React.FC<JitsyMeetingProps> = ({
393415
}}
394416
onReadyToClose={async () => {
395417
stopAllIntervals();
418+
stopCamera();
396419
if (!isStudent && recordingIdRef.current) {
397420
await sendRecordingEvent("end-recording");
398421
}

‎src/components/MeetingAnalyticsOverlay/MeetingAnalyticsOverlay.tsx‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,16 @@ export default function MeetingAnalyticsOverlay({
213213
isArea: boolean
214214
) => {
215215
if (data.length === 0) return null;
216+
const isSinglePoint = data.length === 1;
217+
216218
return (
217219
<linearGradient id={id} x1="0" y1="0" x2="1" y2="0">
218220
{data.map((point, index) => (
219221
<stop
220222
key={`${id}-${index}`}
221-
offset={`${(index / (data.length - 1)) * 100}%`}
223+
offset={
224+
isSinglePoint ? "0%" : `${(index / (data.length - 1)) * 100}%`
225+
}
222226
stopColor={getColorByValue(point.value)}
223227
stopOpacity={isArea ? 0.4 : 1}
224228
/>
@@ -233,6 +237,14 @@ export default function MeetingAnalyticsOverlay({
233237
val: 0,
234238
};
235239

240+
useEffect(() => {
241+
if (!shouldRunAnalytics) {
242+
setAttentionData([]);
243+
setEmotionHistory([]);
244+
setShouldBreak(false);
245+
}
246+
}, [shouldRunAnalytics]);
247+
236248
return (
237249
<OverlayRoot>
238250
<Header>

‎src/components/Webinars/Webinar/WebinarMeetModal/index.tsx‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import MeetingAnalyticsOverlay from "@/components/MeetingAnalyticsOverlay/Meetin
1212
import { EndMeetingQuestionnairesModal } from "@/components/Consultations/ConsultationCard/EndMeetingQuestionnaires";
1313
import { QuestionnaireModelType } from "@/types/questionnaire";
1414
import { API } from "@escolalms/sdk/lib";
15-
import { useRoles } from "@/hooks/useRoles";
1615

1716
interface Props {
1817
onClose: () => void;
@@ -39,7 +38,6 @@ const WebinarMeetModal = ({ onClose, visible, webinarId, webinar }: Props) => {
3938
const onCloseRef = useRef(onClose);
4039
const { generateWebinarJitsy } = useContext(EscolaLMSContext);
4140
const { t } = useTranslation();
42-
const { isTutor } = useRoles();
4341

4442
useEffect(() => {
4543
onCloseRef.current = onClose;
@@ -86,8 +84,9 @@ const WebinarMeetModal = ({ onClose, visible, webinarId, webinar }: Props) => {
8684

8785
const handleOnClose = useCallback(() => {
8886
setIsEnded(true);
89-
isTutor && onClose();
90-
}, [isTutor, onClose]);
87+
setWebinarMeetData(null);
88+
onClose();
89+
}, [onClose]);
9190

9291
return (
9392
<>
@@ -114,7 +113,7 @@ const WebinarMeetModal = ({ onClose, visible, webinarId, webinar }: Props) => {
114113
webinar={webinar}
115114
participantCount={participantCount}
116115
/>
117-
{!loading && webinarMeetData && (
116+
{visible && !loading && webinarMeetData && (
118117
<JitsyMeeting
119118
key={webinarId}
120119
jitsyData={webinarMeetData}

‎src/components/_App/Navbar/index.tsx‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,16 @@ const Navbar = () => {
344344
),
345345
key: "menu-2",
346346
},
347+
{
348+
title: (
349+
<Link to={routeRoutes.webinars}>
350+
<Text noMargin bold>
351+
{t("Menu.Webinars")}
352+
</Text>
353+
</Link>
354+
),
355+
key: "menu-3",
356+
},
347357
{
348358
title: (
349359
<Link to={routeRoutes.courses}>
@@ -352,7 +362,7 @@ const Navbar = () => {
352362
</Text>
353363
</Link>
354364
),
355-
key: "menu-3",
365+
key: "menu-4",
356366
},
357367
{
358368
title: (
@@ -362,7 +372,7 @@ const Navbar = () => {
362372
</Text>
363373
</Link>
364374
),
365-
key: "menu-4",
375+
key: "menu-5",
366376
},
367377
{
368378
title: settings?.value?.config?.[metaDataKeys.termsPageMetaKey] && (
@@ -374,7 +384,7 @@ const Navbar = () => {
374384
</Text>
375385
</Link>
376386
),
377-
key: "menu-5",
387+
key: "menu-6",
378388
},
379389

380390
{
@@ -504,6 +514,11 @@ const Navbar = () => {
504514
{t("MyProfilePage.MyConsultations")}
505515
</NavLink>
506516
</li>
517+
<li>
518+
<NavLink to={routeRoutes.myWebinars}>
519+
{t("MyProfilePage.MyWebinars")}
520+
</NavLink>
521+
</li>
507522
<li>
508523
<NavLink to={routeRoutes.myCertificates}>
509524
{t("Navbar.MyCertificates")}

‎src/hooks/meeting/useCamera.ts‎

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -130,47 +130,63 @@ const useCamera = () => {
130130

131131
useEffect(() => {
132132
const handlePermissionChange = async () => {
133-
const permissionStatus = await navigator.permissions.query({
134-
name: "camera" as PermissionName,
135-
});
133+
try {
134+
const permissionStatus = await navigator.permissions.query({
135+
name: "camera" as PermissionName,
136+
});
136137

137-
// If permission is granted, restart the video track
138-
if (permissionStatus.state === cameraPermissions.GRANTED) {
139-
await restartVideoTrack();
140-
}
138+
const updateStatus = () => {
139+
if (permissionStatus.state === cameraPermissions.DENIED) {
140+
setHasCameraAccess(false);
141+
setCameraAccessStatus(cameraPermissions.DENIED);
142+
} else {
143+
setCameraAccessStatus(permissionStatus.state as cameraPermissions);
144+
}
145+
};
141146

142-
if (permissionStatus.state === cameraPermissions.DENIED) {
143-
setHasCameraAccess(false);
144-
setCameraAccessStatus(cameraPermissions.DENIED);
147+
permissionStatus.onchange = updateStatus;
148+
updateStatus();
149+
} catch (e) {
150+
console.error("Permissions API not supported", e);
145151
}
146-
147-
// Listen for permission changes
148-
permissionStatus.onchange = async () => {
149-
if (permissionStatus.state === cameraPermissions.GRANTED) {
150-
await restartVideoTrack();
151-
} else {
152-
setHasCameraAccess(false);
153-
setCameraAccessStatus(cameraPermissions.DENIED);
154-
}
155-
};
156152
};
157153

158154
handlePermissionChange();
159155

160156
return () => {
161-
// Clean up Workera
162157
if (workerRef.current) {
163158
workerRef.current.terminate();
164159
}
160+
161+
if (streamRef.current) {
162+
streamRef.current.getTracks().forEach((t) => t.stop());
163+
}
165164
};
166-
}, [restartVideoTrack]);
165+
}, []);
166+
167+
const stopCamera = useCallback(() => {
168+
if (streamRef.current) {
169+
streamRef.current.getTracks().forEach((track) => {
170+
track.stop();
171+
});
172+
streamRef.current = null;
173+
}
174+
175+
if (videoRef.current) {
176+
videoRef.current.srcObject = null;
177+
videoRef.current.pause();
178+
}
179+
180+
setHasCameraAccess(false);
181+
}, []);
167182

168183
return {
169184
camera,
170185
hasCameraAccess,
171186
restartVideoTrack,
172187
getDataUrl,
173188
cameraAccessStatus,
189+
stopCamera,
174190
};
175191
};
176192

‎src/hooks/useAnalyticsWebsockets.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export const useMeetingSockets = (
1111
const [socketData, setSocketData] = useState<SocketDataState | null>(null);
1212

1313
useEffect(() => {
14-
if (!modelId || !termUnix || !token) return;
14+
if (!modelId || !termUnix || !token) {
15+
setSocketData(null);
16+
return;
17+
}
1518

1619
const echo = getEchoInstance(token);
1720
if (!echo) return;

0 commit comments

Comments
 (0)