Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 32 additions & 13 deletions client/src/call/VideoCall.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,23 @@ export function VideoCall({ showNickname, showTitle }) {
}

useEffect(() => {
if (callObject) {
if (!callObject || callObject.isDestroyed?.()) return;
try {
callObject.setUserName(displayName || "Guest");
} catch (err) {
console.warn("Failed to set Daily username", err);
}
}, [callObject, displayName]);

useEffect(() => {
if (!callObject || !roomUrl) return undefined;
if (!callObject || callObject.isDestroyed?.() || !roomUrl) return undefined;

const joinRoom = async () => {
try {
if (callObject.meetingState() !== "joined-meeting") {
if (
!callObject.isDestroyed?.() &&
callObject.meetingState() !== "joined-meeting"
) {
await callObject.join({ url: roomUrl });
}
} catch (err) {
Expand All @@ -54,7 +60,10 @@ export function VideoCall({ showNickname, showTitle }) {
joinRoom();

return () => {
if (callObject.meetingState() !== "left-meeting") {
if (
!callObject.isDestroyed?.() &&
callObject.meetingState() !== "left-meeting"
) {
callObject.leave();
}
};
Expand All @@ -68,7 +77,7 @@ export function VideoCall({ showNickname, showTitle }) {
// The logic below tries to handle these cases gracefully, but there
// may still be edge cases that are not covered.
useEffect(() => {
if (!callObject) return;
if (!callObject || callObject.isDestroyed?.()) return;

const alignCamera = async () => {
if (
Expand All @@ -80,10 +89,15 @@ export function VideoCall({ showNickname, showTitle }) {
cameraId: preferredCameraId,
});
updatingCameraRef.current = true;
await callObject.setInputDevicesAsync({
videoDeviceId: preferredCameraId,
});
updatingCameraRef.current = false;
try {
if (!callObject.isDestroyed?.()) {
await callObject.setInputDevicesAsync({
videoDeviceId: preferredCameraId,
});
}
} finally {
updatingCameraRef.current = false;
}
} else {
console.log("Preferred camera not available, keeping current camera", {
preferredCameraId,
Expand All @@ -102,10 +116,15 @@ export function VideoCall({ showNickname, showTitle }) {
micId: preferredMicId,
});
updatingMicRef.current = true;
await callObject.setInputDevicesAsync({
audioDeviceId: preferredMicId,
});
updatingMicRef.current = false;
try {
if (!callObject.isDestroyed?.()) {
await callObject.setInputDevicesAsync({
audioDeviceId: preferredMicId,
});
}
} finally {
updatingMicRef.current = false;
}
} else {
console.log(
"Preferred microphone not available, keeping current microphone",
Expand Down
57 changes: 54 additions & 3 deletions client/src/call/hooks/useDailyEventLogger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useDaily, useDailyEvent } from "@daily-co/daily-react";
import { useCallback, useEffect, useRef } from "react";
import { usePlayer, useStageTimer } from "@empirica/core/player/classic/react";
import {
usePlayer,
useStageTimer,
useStage,
} from "@empirica/core/player/classic/react";

/**
* Centralized Daily event logging.
Expand All @@ -17,6 +21,7 @@ export function useDailyEventLogger() {
const callObject = useDaily();
const player = usePlayer();
const stageTimer = useStageTimer();
const stage = useStage();

/**
* Write a structured event to the current Empirica stage.
Expand Down Expand Up @@ -47,6 +52,46 @@ export function useDailyEventLogger() {
[player, stageTimer]
);

useDailyEvent("joined-meeting", (ev) => {
const dailyId = ev?.participants?.local?.user_id;

if (player && dailyId) {
try {
player.append("dailyIds", dailyId);
} catch (err) {
console.error("Failed to append Daily ID", err);
}
try {
player.set("dailyId", dailyId);
} catch (err) {
console.error("Failed to set current Daily ID", err);
}
}

// if we are the first to join, trigger server-side action to start recording
if (stage && stage.get("callStarted") !== true) {
try {
stage.set("callStarted", true);
} catch (err) {
console.error("Failed to set callStarted flag", err);
}
}

logEvent("joined-meeting", { dailyId });
});

useDailyEvent("left-meeting", (ev) => {
if (player) {
try {
player.set("dailyId", null);
} catch (err) {
console.error("Failed to clear Daily ID", err);
}
}

logEvent("left-meeting", { reason: ev?.reason });
});

useDailyEvent("local-track-started", (ev) => {
if (ev?.kind === "video") {
logEvent("video-unmuted");
Expand Down Expand Up @@ -78,16 +123,21 @@ export function useDailyEventLogger() {
const pollIntervalRef = useRef(null);

useEffect(() => {
if (!callObject) return undefined;
if (!callObject || callObject.isDestroyed?.()) return undefined;

// Some metrics (bitrate, packet loss) are only available through
// `getNetworkStats()`. Poll every 30s to create a coarse timeline.
let cancelled = false;

const poll = async () => {
if (cancelled || !callObject || callObject.isDestroyed?.()) return;
try {
const networkStats = await callObject.getNetworkStats();
logEvent("network-stats", networkStats);
} catch (err) {
console.warn("Failed to fetch network stats", err);
if (!callObject.isDestroyed?.()) {
console.warn("Failed to fetch network stats", err);
}
}
};

Expand All @@ -96,6 +146,7 @@ export function useDailyEventLogger() {
pollIntervalRef.current = setInterval(poll, 30000);

return () => {
cancelled = true;
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
Expand Down
3 changes: 3 additions & 0 deletions dlconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"experimentRoot": "cypress/fixtures/mockCDN"
}
Loading