@editframe/react 0.26.3-beta.0 → 0.30.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/Text.d.ts +9 -0
- package/dist/elements/Text.js +19 -0
- package/dist/elements/Text.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +3 -3
- package/tsdown.config.ts +1 -1
- package/types.json +1 -1
- package/src/components/TimeDisplay.tsx +0 -13
- package/src/elements/Audio.ts +0 -9
- package/src/elements/Captions.ts +0 -39
- package/src/elements/Image.ts +0 -9
- package/src/elements/Surface.ts +0 -11
- package/src/elements/ThumbnailStrip.ts +0 -11
- package/src/elements/Timegroup.ts +0 -9
- package/src/elements/Video.ts +0 -9
- package/src/elements/Waveform.ts +0 -9
- package/src/gui/Configuration.ts +0 -9
- package/src/gui/Controls.browsertest.tsx +0 -112
- package/src/gui/Controls.ts +0 -9
- package/src/gui/EFDial.ts +0 -12
- package/src/gui/EFResizableBox.ts +0 -12
- package/src/gui/Filmstrip.ts +0 -9
- package/src/gui/FitScale.ts +0 -9
- package/src/gui/FocusOverlay.ts +0 -9
- package/src/gui/Pause.ts +0 -9
- package/src/gui/Play.ts +0 -9
- package/src/gui/Preview.ts +0 -9
- package/src/gui/Scrubber.ts +0 -9
- package/src/gui/ToggleLoop.ts +0 -9
- package/src/gui/TogglePlay.ts +0 -9
- package/src/gui/Workbench.ts +0 -9
- package/src/hooks/create-element.ts +0 -167
- package/src/hooks/useTimingInfo.browsertest.tsx +0 -371
- package/src/hooks/useTimingInfo.ts +0 -107
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import type { EFTimegroup } from "@editframe/elements";
|
|
2
|
-
import { type FC, useEffect } from "react";
|
|
3
|
-
import { createRoot } from "react-dom/client";
|
|
4
|
-
import { assert, beforeEach, describe, test } from "vitest";
|
|
5
|
-
import { Timegroup } from "../elements/Timegroup.js";
|
|
6
|
-
import { Video } from "../elements/Video.js";
|
|
7
|
-
import { Configuration } from "../gui/Configuration.js";
|
|
8
|
-
import { Preview } from "../gui/Preview.js";
|
|
9
|
-
import { useTimingInfo } from "./useTimingInfo.js";
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
while (document.body.children.length) {
|
|
13
|
-
document.body.children[0]?.remove();
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
interface TimingDisplayProps {
|
|
18
|
-
onUpdate?: (info: {
|
|
19
|
-
ownCurrentTimeMs: number;
|
|
20
|
-
durationMs: number;
|
|
21
|
-
percentComplete: number;
|
|
22
|
-
}) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TimingDisplay: FC<TimingDisplayProps> = ({ onUpdate }) => {
|
|
26
|
-
const { ownCurrentTimeMs, durationMs, percentComplete, ref } =
|
|
27
|
-
useTimingInfo();
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (onUpdate) {
|
|
31
|
-
onUpdate({ ownCurrentTimeMs, durationMs, percentComplete });
|
|
32
|
-
}
|
|
33
|
-
}, [ownCurrentTimeMs, durationMs, percentComplete, onUpdate]);
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
// biome-ignore lint/correctness/useUniqueElementIds: OK for test fixture with single instance
|
|
37
|
-
<Preview id="test-preview">
|
|
38
|
-
<Timegroup mode="fixed" duration="3s" ref={ref}>
|
|
39
|
-
<Video
|
|
40
|
-
src="https://editframe-dev-assets.s3.us-east-1.amazonaws.com/test-assets/test_audio.mp4"
|
|
41
|
-
trim="0s-1s"
|
|
42
|
-
/>
|
|
43
|
-
</Timegroup>
|
|
44
|
-
</Preview>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
describe("useTimingInfo", () => {
|
|
49
|
-
test("provides initial timing information", async () => {
|
|
50
|
-
const container = document.createElement("div");
|
|
51
|
-
document.body.appendChild(container);
|
|
52
|
-
|
|
53
|
-
let receivedInfo: {
|
|
54
|
-
ownCurrentTimeMs: number;
|
|
55
|
-
durationMs: number;
|
|
56
|
-
percentComplete: number;
|
|
57
|
-
} | null = null;
|
|
58
|
-
|
|
59
|
-
const root = createRoot(container);
|
|
60
|
-
root.render(
|
|
61
|
-
<Configuration>
|
|
62
|
-
<TimingDisplay
|
|
63
|
-
onUpdate={(info) => {
|
|
64
|
-
receivedInfo = info;
|
|
65
|
-
}}
|
|
66
|
-
/>
|
|
67
|
-
</Configuration>,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
// Wait for the component to mount and update
|
|
71
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
72
|
-
|
|
73
|
-
const preview = container.querySelector("ef-preview");
|
|
74
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
75
|
-
|
|
76
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
77
|
-
await timegroup.updateComplete;
|
|
78
|
-
await timegroup.waitForMediaDurations();
|
|
79
|
-
|
|
80
|
-
// Wait for initial frame task
|
|
81
|
-
await timegroup.frameTask.run();
|
|
82
|
-
|
|
83
|
-
assert.ok(receivedInfo, "Should receive timing info");
|
|
84
|
-
assert.equal(receivedInfo?.ownCurrentTimeMs, 0);
|
|
85
|
-
assert.equal(receivedInfo?.durationMs, 3000);
|
|
86
|
-
assert.equal(receivedInfo?.percentComplete, 0);
|
|
87
|
-
|
|
88
|
-
root.unmount();
|
|
89
|
-
container.remove();
|
|
90
|
-
}, 5000);
|
|
91
|
-
|
|
92
|
-
test("updates only on frame task completion, not on every Lit update", async () => {
|
|
93
|
-
const container = document.createElement("div");
|
|
94
|
-
document.body.appendChild(container);
|
|
95
|
-
|
|
96
|
-
const updates: number[] = [];
|
|
97
|
-
|
|
98
|
-
const root = createRoot(container);
|
|
99
|
-
root.render(
|
|
100
|
-
<Configuration>
|
|
101
|
-
<TimingDisplay
|
|
102
|
-
onUpdate={(info) => {
|
|
103
|
-
updates.push(info.ownCurrentTimeMs);
|
|
104
|
-
}}
|
|
105
|
-
/>
|
|
106
|
-
</Configuration>,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Wait for initial mount
|
|
110
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
111
|
-
|
|
112
|
-
const preview = container.querySelector("ef-preview");
|
|
113
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
114
|
-
|
|
115
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
116
|
-
await timegroup.updateComplete;
|
|
117
|
-
await timegroup.waitForMediaDurations();
|
|
118
|
-
|
|
119
|
-
// Clear initial updates
|
|
120
|
-
updates.length = 0;
|
|
121
|
-
|
|
122
|
-
// Trigger multiple Lit updates without frame task
|
|
123
|
-
// These should NOT trigger React updates
|
|
124
|
-
timegroup.requestUpdate("mode");
|
|
125
|
-
await timegroup.updateComplete;
|
|
126
|
-
timegroup.requestUpdate("mode");
|
|
127
|
-
await timegroup.updateComplete;
|
|
128
|
-
timegroup.requestUpdate("mode");
|
|
129
|
-
await timegroup.updateComplete;
|
|
130
|
-
|
|
131
|
-
// Should have no updates since no frame tasks ran
|
|
132
|
-
assert.equal(
|
|
133
|
-
updates.length,
|
|
134
|
-
0,
|
|
135
|
-
"Should not update on Lit property changes",
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// Now trigger frame task via seek (proper API that triggers both task and update)
|
|
139
|
-
await timegroup.seek(1000);
|
|
140
|
-
// Give React a chance to process the state update
|
|
141
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
142
|
-
|
|
143
|
-
// Should have exactly one update from frame task
|
|
144
|
-
assert.ok(updates.length >= 1, "Should update once per frame task");
|
|
145
|
-
|
|
146
|
-
root.unmount();
|
|
147
|
-
container.remove();
|
|
148
|
-
}, 5000);
|
|
149
|
-
|
|
150
|
-
test("updates synchronously with frame tasks during seek", async () => {
|
|
151
|
-
const container = document.createElement("div");
|
|
152
|
-
document.body.appendChild(container);
|
|
153
|
-
|
|
154
|
-
const updates: number[] = [];
|
|
155
|
-
|
|
156
|
-
const root = createRoot(container);
|
|
157
|
-
root.render(
|
|
158
|
-
<Configuration>
|
|
159
|
-
<TimingDisplay
|
|
160
|
-
onUpdate={(info) => {
|
|
161
|
-
updates.push(info.ownCurrentTimeMs);
|
|
162
|
-
}}
|
|
163
|
-
/>
|
|
164
|
-
</Configuration>,
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
168
|
-
|
|
169
|
-
const preview = container.querySelector("ef-preview");
|
|
170
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
171
|
-
|
|
172
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
173
|
-
await timegroup.updateComplete;
|
|
174
|
-
await timegroup.waitForMediaDurations();
|
|
175
|
-
|
|
176
|
-
// Clear initial updates
|
|
177
|
-
updates.length = 0;
|
|
178
|
-
|
|
179
|
-
// Seek to different times
|
|
180
|
-
await timegroup.seek(1000);
|
|
181
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
182
|
-
const updatesAfterFirstSeek = updates.length;
|
|
183
|
-
assert.ok(updatesAfterFirstSeek > 0, "Should update after first seek");
|
|
184
|
-
|
|
185
|
-
await timegroup.seek(2000);
|
|
186
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
187
|
-
const updatesAfterSecondSeek = updates.length;
|
|
188
|
-
assert.ok(
|
|
189
|
-
updatesAfterSecondSeek > updatesAfterFirstSeek,
|
|
190
|
-
"Should update after second seek",
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Verify the last update has the correct time
|
|
194
|
-
const lastUpdate = updates[updates.length - 1];
|
|
195
|
-
assert.equal(lastUpdate, 2000, "Should reflect the seeked time");
|
|
196
|
-
|
|
197
|
-
root.unmount();
|
|
198
|
-
container.remove();
|
|
199
|
-
}, 5000);
|
|
200
|
-
|
|
201
|
-
test("updates at controlled rate with sequential frame updates", async () => {
|
|
202
|
-
const container = document.createElement("div");
|
|
203
|
-
document.body.appendChild(container);
|
|
204
|
-
|
|
205
|
-
const updateTimestamps: number[] = [];
|
|
206
|
-
const updateTimes: number[] = [];
|
|
207
|
-
|
|
208
|
-
const root = createRoot(container);
|
|
209
|
-
root.render(
|
|
210
|
-
<Configuration>
|
|
211
|
-
<TimingDisplay
|
|
212
|
-
onUpdate={(info) => {
|
|
213
|
-
updateTimestamps.push(performance.now());
|
|
214
|
-
updateTimes.push(info.ownCurrentTimeMs);
|
|
215
|
-
}}
|
|
216
|
-
/>
|
|
217
|
-
</Configuration>,
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
221
|
-
|
|
222
|
-
const preview = container.querySelector("ef-preview");
|
|
223
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
224
|
-
|
|
225
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
226
|
-
await timegroup.updateComplete;
|
|
227
|
-
await timegroup.waitForMediaDurations();
|
|
228
|
-
|
|
229
|
-
// Clear initial updates
|
|
230
|
-
updateTimestamps.length = 0;
|
|
231
|
-
updateTimes.length = 0;
|
|
232
|
-
|
|
233
|
-
// Simulate frame-by-frame updates at a controlled rate (30fps)
|
|
234
|
-
// Note: We use seek() rather than play() because AudioContext-based playback
|
|
235
|
-
// doesn't work in headless browsers due to autoplay policies that require user interaction
|
|
236
|
-
const FPS = 30;
|
|
237
|
-
const MS_PER_FRAME = 1000 / FPS;
|
|
238
|
-
const DURATION_MS = 500;
|
|
239
|
-
const numFrames = Math.floor(DURATION_MS / MS_PER_FRAME);
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i < numFrames; i++) {
|
|
242
|
-
const targetTime = i * MS_PER_FRAME;
|
|
243
|
-
await timegroup.seek(targetTime);
|
|
244
|
-
await new Promise((resolve) => setTimeout(resolve, MS_PER_FRAME));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Should have received multiple updates during simulated playback
|
|
248
|
-
assert.ok(
|
|
249
|
-
updateTimestamps.length >= 10,
|
|
250
|
-
`Should have at least 10 updates during simulated playback (got ${updateTimestamps.length})`,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
// Calculate update intervals
|
|
254
|
-
const intervals: number[] = [];
|
|
255
|
-
for (let i = 1; i < updateTimestamps.length; i++) {
|
|
256
|
-
intervals.push(updateTimestamps[i] - updateTimestamps[i - 1]);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Average interval should be around 33ms (30fps) matching our simulated rate
|
|
260
|
-
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
|
|
261
|
-
|
|
262
|
-
// Should be approximately 33ms per frame
|
|
263
|
-
// Allow generous variance for timing imprecision in CI
|
|
264
|
-
assert.ok(
|
|
265
|
-
avgInterval > 20 && avgInterval < 100,
|
|
266
|
-
`Update interval should be between 20-100ms (got ${avgInterval}ms), indicating controlled rate`,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
root.unmount();
|
|
270
|
-
container.remove();
|
|
271
|
-
}, 5000);
|
|
272
|
-
|
|
273
|
-
test("continues observing after errors in frame task", async () => {
|
|
274
|
-
const container = document.createElement("div");
|
|
275
|
-
document.body.appendChild(container);
|
|
276
|
-
|
|
277
|
-
const updates: number[] = [];
|
|
278
|
-
|
|
279
|
-
const root = createRoot(container);
|
|
280
|
-
root.render(
|
|
281
|
-
<Configuration>
|
|
282
|
-
<TimingDisplay
|
|
283
|
-
onUpdate={(info) => {
|
|
284
|
-
updates.push(info.ownCurrentTimeMs);
|
|
285
|
-
}}
|
|
286
|
-
/>
|
|
287
|
-
</Configuration>,
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
291
|
-
|
|
292
|
-
const preview = container.querySelector("ef-preview");
|
|
293
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
294
|
-
|
|
295
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
296
|
-
await timegroup.updateComplete;
|
|
297
|
-
await timegroup.waitForMediaDurations();
|
|
298
|
-
|
|
299
|
-
updates.length = 0;
|
|
300
|
-
|
|
301
|
-
// First seek triggers frame task
|
|
302
|
-
await timegroup.seek(500);
|
|
303
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
304
|
-
assert.ok(updates.length >= 1, "Should update after first seek");
|
|
305
|
-
|
|
306
|
-
// Even if there's an error or the task rejects, the observer should continue
|
|
307
|
-
// (The implementation catches errors and continues observing)
|
|
308
|
-
const updatesAfterFirst = updates.length;
|
|
309
|
-
|
|
310
|
-
// Second seek triggers another frame task
|
|
311
|
-
await timegroup.seek(1000);
|
|
312
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
313
|
-
assert.ok(updates.length > updatesAfterFirst, "Should continue updating");
|
|
314
|
-
|
|
315
|
-
root.unmount();
|
|
316
|
-
container.remove();
|
|
317
|
-
}, 5000);
|
|
318
|
-
|
|
319
|
-
test("stops observing when component unmounts", async () => {
|
|
320
|
-
const container = document.createElement("div");
|
|
321
|
-
document.body.appendChild(container);
|
|
322
|
-
|
|
323
|
-
const updates: number[] = [];
|
|
324
|
-
|
|
325
|
-
const root = createRoot(container);
|
|
326
|
-
root.render(
|
|
327
|
-
<Configuration>
|
|
328
|
-
<TimingDisplay
|
|
329
|
-
onUpdate={(info) => {
|
|
330
|
-
updates.push(info.ownCurrentTimeMs);
|
|
331
|
-
}}
|
|
332
|
-
/>
|
|
333
|
-
</Configuration>,
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
337
|
-
|
|
338
|
-
const preview = container.querySelector("ef-preview");
|
|
339
|
-
const timegroup = preview?.querySelector("ef-timegroup") as EFTimegroup;
|
|
340
|
-
|
|
341
|
-
assert.ok(timegroup, "Timegroup should be rendered");
|
|
342
|
-
await timegroup.updateComplete;
|
|
343
|
-
await timegroup.waitForMediaDurations();
|
|
344
|
-
|
|
345
|
-
updates.length = 0;
|
|
346
|
-
|
|
347
|
-
// Seek before unmount (triggers frame task)
|
|
348
|
-
await timegroup.seek(500);
|
|
349
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
350
|
-
assert.ok(updates.length >= 1, "Should update before unmount");
|
|
351
|
-
|
|
352
|
-
// Unmount the component
|
|
353
|
-
root.unmount();
|
|
354
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
355
|
-
|
|
356
|
-
// Seek after unmount should not cause updates
|
|
357
|
-
const updatesBeforePost = updates.length;
|
|
358
|
-
await timegroup.seek(1000);
|
|
359
|
-
|
|
360
|
-
// Give time for any potential updates
|
|
361
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
362
|
-
|
|
363
|
-
assert.equal(
|
|
364
|
-
updates.length,
|
|
365
|
-
updatesBeforePost,
|
|
366
|
-
"Should not update after unmount",
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
container.remove();
|
|
370
|
-
}, 5000);
|
|
371
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { EFTimegroup } from "@editframe/elements";
|
|
2
|
-
import type { Task } from "@lit/task";
|
|
3
|
-
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
4
|
-
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
|
|
6
|
-
interface TimeInfo {
|
|
7
|
-
ownCurrentTimeMs: number;
|
|
8
|
-
durationMs: number;
|
|
9
|
-
percentComplete: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
class CurrentTimeController implements ReactiveController {
|
|
13
|
-
#lastTaskPromise: Promise<unknown> | null = null;
|
|
14
|
-
#isConnected = false;
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
private host: {
|
|
18
|
-
ownCurrentTimeMs: number;
|
|
19
|
-
durationMs: number;
|
|
20
|
-
frameTask: Task<readonly unknown[], unknown>;
|
|
21
|
-
} & ReactiveControllerHost,
|
|
22
|
-
private setCurrentTime: React.Dispatch<React.SetStateAction<TimeInfo>>,
|
|
23
|
-
) {
|
|
24
|
-
this.host.addController(this);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
hostConnected(): void {
|
|
28
|
-
this.#isConnected = true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
hostDisconnected(): void {
|
|
32
|
-
this.#isConnected = false;
|
|
33
|
-
this.#lastTaskPromise = null;
|
|
34
|
-
this.host.removeController(this);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
hostUpdated(): void {
|
|
38
|
-
const currentTaskPromise = this.host.frameTask.taskComplete;
|
|
39
|
-
|
|
40
|
-
// Detect if a new frame task has started (promise reference changed)
|
|
41
|
-
if (currentTaskPromise !== this.#lastTaskPromise) {
|
|
42
|
-
this.#lastTaskPromise = currentTaskPromise;
|
|
43
|
-
|
|
44
|
-
// Wait for this specific task to complete, then update React
|
|
45
|
-
// This is async so it doesn't block the update cycle
|
|
46
|
-
currentTaskPromise
|
|
47
|
-
.then(() => {
|
|
48
|
-
// Only update if still connected
|
|
49
|
-
if (this.#isConnected) {
|
|
50
|
-
this.#updateReactState();
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
.catch(() => {
|
|
54
|
-
// Ignore task errors - we'll continue observing
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#updateReactState(): void {
|
|
60
|
-
// Always update to ensure React has the latest state
|
|
61
|
-
this.setCurrentTime({
|
|
62
|
-
ownCurrentTimeMs: this.host.ownCurrentTimeMs,
|
|
63
|
-
durationMs: this.host.durationMs,
|
|
64
|
-
percentComplete: this.host.ownCurrentTimeMs / this.host.durationMs,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Public method to manually trigger sync (for initialization)
|
|
69
|
-
syncNow(): void {
|
|
70
|
-
this.#updateReactState();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const useTimingInfo = (
|
|
75
|
-
timegroupRef: React.RefObject<EFTimegroup> = useRef<EFTimegroup>(null),
|
|
76
|
-
) => {
|
|
77
|
-
const [timeInfo, setTimeInfo] = useState<TimeInfo>({
|
|
78
|
-
ownCurrentTimeMs: 0,
|
|
79
|
-
durationMs: 0,
|
|
80
|
-
percentComplete: 0,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (!timegroupRef.current) {
|
|
85
|
-
throw new Error("Timegroup ref not set");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const controller = new CurrentTimeController(
|
|
89
|
-
timegroupRef.current,
|
|
90
|
-
setTimeInfo,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
// Trigger initial update if the timegroup is already connected
|
|
94
|
-
if (timegroupRef.current.isConnected) {
|
|
95
|
-
controller.hostConnected();
|
|
96
|
-
// Sync initial state immediately
|
|
97
|
-
controller.syncNow();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Cleanup function
|
|
101
|
-
return () => {
|
|
102
|
-
controller.hostDisconnected();
|
|
103
|
-
};
|
|
104
|
-
}, [timegroupRef.current]);
|
|
105
|
-
|
|
106
|
-
return { ...timeInfo, ref: timegroupRef };
|
|
107
|
-
};
|