-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathuseDailyEventLogger.js
More file actions
156 lines (137 loc) · 4.36 KB
/
Copy pathuseDailyEventLogger.js
File metadata and controls
156 lines (137 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { useDaily, useDailyEvent } from "@daily-co/daily-react";
import { useCallback, useEffect, useRef } from "react";
import {
usePlayer,
useStageTimer,
useStage,
} from "@empirica/core/player/classic/react";
/**
* Centralized Daily event logging.
*
* Why this hook exists:
* - Tray buttons (or other UI) should only change state; logging the effects
* belongs in one predictable place.
* - We log from Daily.js events so we capture *every* toggle, even ones
* triggered via keyboard shortcuts or external controls.
* - All entries are written to `player.stage` with an elapsed stage timestamp
* so downstream analysis can reconstruct the meeting timeline.
*/
export function useDailyEventLogger() {
const callObject = useDaily();
const player = usePlayer();
const stageTimer = useStageTimer();
const stage = useStage();
/**
* Write a structured event to the current Empirica stage.
* We prefer stage timer seconds over wall-clock time so that
* analytics line up with the stage duration even if clients have
* skewed system clocks.
*/
const logEvent = useCallback(
(event, data = {}) => {
if (!player?.stage) return;
let elapsedSeconds = null;
if (typeof stageTimer?.elapsed === "number") {
elapsedSeconds = stageTimer.elapsed / 1000;
} else {
const startedAt = player.get("localStageStartTime");
if (startedAt) {
elapsedSeconds = (Date.now() - startedAt) / 1000;
}
}
player.stage.append("speakerEvents", {
event,
timestamp: elapsedSeconds,
debug: data,
});
},
[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");
}
if (ev?.kind === "audio") {
logEvent("audio-unmuted");
}
});
useDailyEvent("local-track-stopped", (ev) => {
if (ev?.kind === "video") {
logEvent("video-muted");
}
if (ev?.kind === "audio") {
logEvent("audio-muted");
}
});
// Once per second Daily re-evaluates network health for the local participant.
// We only care when the rating actually changes (0–5).
useDailyEvent("network-quality-change", (ev) => {
if (ev?.quality == null) return;
logEvent("network-quality-change", {
quality: ev.quality,
reason: ev.reason,
});
});
const pollIntervalRef = useRef(null);
useEffect(() => {
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) {
if (!callObject.isDestroyed?.()) {
console.warn("Failed to fetch network stats", err);
}
}
};
// Log immediately so the first snapshot exists even on short calls.
poll();
pollIntervalRef.current = setInterval(poll, 30000);
return () => {
cancelled = true;
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
};
}, [callObject, logEvent]);
}