-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
320 lines (294 loc) · 10 KB
/
Copy pathscript.js
File metadata and controls
320 lines (294 loc) · 10 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
console.log("script.js loaded");
// Initialize variables
let timer;
let timeLeft;
let audio;
let isRunning = false;
let videoFilterInterval;
// Update status on page load
document.getElementById("status").textContent = "App loaded, waiting for Zoom SDK...";
// Function to check audio URL validity
async function checkAudioUrl(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('audio/')) {
throw new Error('Invalid audio content type');
}
return true;
} catch (err) {
console.error("Audio URL check failed:", err);
return false;
}
}
// Function to render timer on canvas
function renderTimerOnCanvas(timeLeft) {
const canvas = document.getElementById("timerCanvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
ctx.fillRect(0, 0, canvas.width, 50);
ctx.font = "30px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
ctx.fillText(
`${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`,
canvas.width / 2,
35
);
}
// Function to initialize Zoom SDK with retry
function initializeZoomSdk(attempts = 10, delay = 2000) {
console.log(`Attempting Zoom SDK initialization (${attempts} attempts left)`);
if (typeof zoomSdk === "undefined") {
if (attempts > 0) {
console.warn(`Zoom SDK not available, retrying in ${delay}ms...`);
setTimeout(() => initializeZoomSdk(attempts - 1, delay), delay);
} else {
console.error("Zoom SDK not available after retries");
document.getElementById("status").textContent = "Error: Zoom SDK not available. This app must be run within a Zoom meeting. Please ensure the app is installed and the meeting supports apps.";
}
return;
}
zoomSdk.config({
capabilities: ["notifyAppEvent", "postMessage", "onMessage", "setVideoFilter", "getUserContext"]
}).then(() => {
console.log("Zoom SDK Initialized Successfully");
document.getElementById("status").textContent = "Ready to start timer";
// Listen for timer data from other participants
zoomSdk.onMessage(data => {
console.log("Received message:", data);
if (data.type === "timer_start") {
const { startTime, duration, audioUrl, starterId } = data;
startSynchronizedTimer(startTime, duration, audioUrl, starterId);
}
});
}).catch(err => {
console.error("Zoom SDK Initialization Failed:", err);
document.getElementById("status").textContent = "Error initializing Zoom SDK: " + err.message;
});
}
// Update button state
function updateButtonState() {
const button = document.getElementById("timerButton");
if (isRunning || (audio && !audio.paused)) {
button.textContent = "Stop Timer";
button.className = "stop";
} else {
button.textContent = "Start Timer";
button.className = "start";
}
}
// Attach event listeners
document.getElementById("preset2Min").addEventListener("click", () => setPreset(120));
document.getElementById("preset1Min").addEventListener("click", () => setPreset(60));
document.getElementById("timerButton").addEventListener("click", toggleTimer);
// Initialize Zoom SDK after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
// Give the SDK a moment to load
setTimeout(() => {
initializeZoomSdk();
}, 100);
});
function setPreset(seconds) {
console.log("Setting preset to", seconds, "seconds");
document.getElementById("timeInput").value = seconds;
document.getElementById("status").textContent = `Preset set to ${seconds} seconds`;
}
async function toggleTimer() {
if (isRunning || (audio && !audio.paused)) {
stopTimer();
} else {
await startTimer();
}
updateButtonState();
}
async function startTimer() {
console.log("Start Timer clicked");
document.getElementById("status").textContent = "Validating inputs...";
const timeInput = document.getElementById("timeInput").value;
const audioUrl = document.getElementById("audioUrl").value;
if (!timeInput || timeInput <= 0) {
console.error("Invalid time input:", timeInput);
document.getElementById("status").textContent = "Please enter a valid time";
return;
}
if (!audioUrl) {
console.error("No audio URL provided");
document.getElementById("status").textContent = "Please select a valid audio URL";
return;
}
// Check audio URL validity
document.getElementById("status").textContent = "Checking audio URL...";
const isAudioValid = await checkAudioUrl(audioUrl);
if (!isAudioValid) {
console.error("Invalid audio URL:", audioUrl);
document.getElementById("status").textContent = "Error: Invalid or inaccessible audio URL";
return;
}
// Get user ID to identify the starter
let starterId = null;
if (typeof zoomSdk !== "undefined") {
try {
const userContext = await zoomSdk.callZoomApi("getUserContext");
starterId = userContext.userId;
} catch (err) {
console.error("Error getting user context:", err);
}
}
// Broadcast timer data to all participants
const startTime = Date.now();
const duration = parseInt(timeInput);
if (typeof zoomSdk !== "undefined") {
try {
await zoomSdk.callZoomApi("postMessage", {
payload: JSON.stringify({
type: "timer_start",
startTime,
duration,
audioUrl,
starterId
})
});
} catch (err) {
console.error("Error sending message:", err);
}
}
// Start the timer locally
startSynchronizedTimer(startTime, duration, audioUrl, starterId);
}
async function startSynchronizedTimer(startTime, duration, audioUrl, starterId) {
// Stop any existing timer and audio
stopTimer();
// Initialize audio
console.log("Initializing audio with URL:", audioUrl);
audio = new Audio(audioUrl);
audio.onerror = () => {
console.error("Error loading audio URL:", audioUrl);
document.getElementById("status").textContent = "Error loading audio URL";
isRunning = false;
updateButtonState();
};
audio.onended = () => {
console.log("Audio playback ended");
isRunning = false;
updateButtonState();
if (videoFilterInterval) {
clearInterval(videoFilterInterval);
if (typeof zoomSdk !== "undefined") {
zoomSdk.callZoomApi("setVideoFilter", { filter: null }).catch(err => {
console.error("Error removing video filter:", err);
});
}
}
};
// Calculate initial time left based on start time
const elapsed = Math.floor((Date.now() - startTime) / 1000);
timeLeft = duration - elapsed;
if (timeLeft <= 0) {
document.getElementById("status").textContent = "Timer already finished";
return;
}
// Apply video filter if this is the starter
let isStarter = false;
if (typeof zoomSdk !== "undefined") {
try {
const userContext = await zoomSdk.callZoomApi("getUserContext");
isStarter = userContext.userId === starterId;
} catch (err) {
console.error("Error checking user context:", err);
}
if (isStarter) {
const canvas = document.getElementById("timerCanvas");
renderTimerOnCanvas(timeLeft);
try {
await zoomSdk.callZoomApi("setVideoFilter", {
filter: canvas.captureStream()
});
} catch (err) {
console.error("Error setting video filter:", err);
document.getElementById("status").textContent = "Error applying video filter";
}
}
}
// Set timer
console.log("Starting synchronized timer with", timeLeft, "seconds");
document.getElementById("status").textContent = "Timer started";
isRunning = true;
updateButtonState();
updateTimerDisplay();
timer = setInterval(() => {
timeLeft--;
updateTimerDisplay();
if (isStarter) {
renderTimerOnCanvas(timeLeft);
}
if (timeLeft <= 0) {
clearInterval(timer);
document.getElementById("status").textContent = "Timer finished! Playing song...";
console.log("Timer finished, playing audio");
audio.play().catch(err => {
console.error("Error playing audio:", err);
document.getElementById("status").textContent = "Error playing audio: " + err.message;
isRunning = false;
updateButtonState();
});
if (isStarter && videoFilterInterval) {
clearInterval(videoFilterInterval);
if (typeof zoomSdk !== "undefined") {
zoomSdk.callZoomApi("setVideoFilter", { filter: null }).catch(err => {
console.error("Error removing video filter:", err);
});
}
}
if (typeof zoomSdk !== "undefined") {
zoomSdk.callZoomApi("notifyAppEvent", {
event: "timer_finished",
data: { message: "Timer reached zero and audio played" }
}).catch(err => {
console.error("Error notifying Zoom event:", err);
});
}
}
}, 1000);
// Update video filter stream
if (isStarter) {
videoFilterInterval = setInterval(() => {
renderTimerOnCanvas(timeLeft);
}, 1000);
}
}
function stopTimer() {
if (timer) {
clearInterval(timer);
document.getElementById("status").textContent = "Timer stopped";
console.log("Timer stopped");
}
if (audio) {
audio.pause();
audio.currentTime = 0;
console.log("Audio paused and reset");
}
if (videoFilterInterval) {
clearInterval(videoFilterInterval);
if (typeof zoomSdk !== "undefined") {
zoomSdk.callZoomApi("setVideoFilter", { filter: null }).catch(err => {
console.error("Error removing video filter:", err);
});
}
}
isRunning = false;
updateButtonState();
updateTimerDisplay();
}
function updateTimerDisplay() {
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
document.getElementById("timerDisplay").textContent =
`${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}