@editframe/elements 0.18.3-beta.0 → 0.18.7-beta.0

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.
Files changed (107) hide show
  1. package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
  2. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -4
  3. package/dist/elements/EFMedia/AssetMediaEngine.js +22 -3
  4. package/dist/elements/EFMedia/BaseMediaEngine.js +20 -1
  5. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +5 -5
  6. package/dist/elements/EFMedia/BufferedSeekingInput.js +27 -7
  7. package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -1
  8. package/dist/elements/EFMedia/JitMediaEngine.js +22 -3
  9. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +4 -1
  10. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +11 -3
  11. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
  12. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +10 -2
  13. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +11 -1
  14. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -2
  15. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +4 -1
  16. package/dist/elements/EFMedia/shared/PrecisionUtils.d.ts +28 -0
  17. package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
  18. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +11 -2
  19. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +11 -1
  20. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +3 -2
  21. package/dist/elements/EFMedia.d.ts +0 -12
  22. package/dist/elements/EFMedia.js +4 -30
  23. package/dist/elements/EFTimegroup.js +12 -17
  24. package/dist/elements/EFVideo.d.ts +0 -9
  25. package/dist/elements/EFVideo.js +0 -7
  26. package/dist/elements/SampleBuffer.js +6 -6
  27. package/dist/getRenderInfo.d.ts +2 -2
  28. package/dist/gui/ContextMixin.js +71 -17
  29. package/dist/gui/TWMixin.js +1 -1
  30. package/dist/style.css +1 -1
  31. package/dist/transcoding/types/index.d.ts +9 -9
  32. package/package.json +2 -3
  33. package/src/elements/EFAudio.browsertest.ts +7 -7
  34. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +100 -0
  35. package/src/elements/EFMedia/AssetMediaEngine.ts +52 -7
  36. package/src/elements/EFMedia/BaseMediaEngine.ts +50 -1
  37. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +135 -54
  38. package/src/elements/EFMedia/BufferedSeekingInput.ts +74 -17
  39. package/src/elements/EFMedia/JitMediaEngine.ts +58 -2
  40. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +10 -1
  41. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +16 -8
  42. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
  43. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +25 -3
  44. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +12 -1
  45. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +3 -2
  46. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +10 -1
  47. package/src/elements/EFMedia/shared/PrecisionUtils.ts +46 -0
  48. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +27 -3
  49. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +12 -1
  50. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +3 -2
  51. package/src/elements/EFMedia.browsertest.ts +73 -33
  52. package/src/elements/EFMedia.ts +11 -54
  53. package/src/elements/EFTimegroup.ts +21 -26
  54. package/src/elements/EFVideo.browsertest.ts +895 -162
  55. package/src/elements/EFVideo.ts +0 -16
  56. package/src/elements/SampleBuffer.ts +8 -10
  57. package/src/gui/ContextMixin.ts +104 -26
  58. package/src/transcoding/types/index.ts +10 -6
  59. package/test/EFVideo.framegen.browsertest.ts +1 -1
  60. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +3 -3
  61. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
  62. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +22 -0
  63. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +3 -3
  64. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
  65. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +22 -0
  66. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +3 -3
  67. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
  68. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +22 -0
  69. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +3 -3
  70. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +3 -3
  71. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +3 -3
  72. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +3 -3
  73. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
  74. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +21 -0
  75. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
  76. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +21 -0
  77. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +3 -3
  78. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
  79. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +4 -4
  80. package/test/recordReplayProxyPlugin.js +50 -0
  81. package/types.json +1 -1
  82. package/dist/DecoderResetFrequency.test.d.ts +0 -1
  83. package/dist/DecoderResetRecovery.test.d.ts +0 -1
  84. package/dist/ScrubTrackManager.d.ts +0 -96
  85. package/dist/elements/EFMedia/services/AudioElementFactory.browsertest.d.ts +0 -1
  86. package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +0 -22
  87. package/dist/elements/EFMedia/services/AudioElementFactory.js +0 -72
  88. package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +0 -1
  89. package/dist/elements/EFMedia/services/MediaSourceService.d.ts +0 -47
  90. package/dist/elements/EFMedia/services/MediaSourceService.js +0 -73
  91. package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +0 -1
  92. package/dist/gui/services/ElementConnectionManager.d.ts +0 -59
  93. package/dist/gui/services/ElementConnectionManager.js +0 -128
  94. package/dist/gui/services/PlaybackController.browsertest.d.ts +0 -1
  95. package/dist/gui/services/PlaybackController.d.ts +0 -103
  96. package/dist/gui/services/PlaybackController.js +0 -290
  97. package/dist/services/MediaSourceManager.d.ts +0 -62
  98. package/dist/services/MediaSourceManager.js +0 -211
  99. package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +0 -325
  100. package/src/elements/EFMedia/services/AudioElementFactory.ts +0 -119
  101. package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +0 -257
  102. package/src/elements/EFMedia/services/MediaSourceService.ts +0 -102
  103. package/src/gui/services/ElementConnectionManager.browsertest.ts +0 -263
  104. package/src/gui/services/ElementConnectionManager.ts +0 -224
  105. package/src/gui/services/PlaybackController.browsertest.ts +0 -437
  106. package/src/gui/services/PlaybackController.ts +0 -521
  107. package/src/services/MediaSourceManager.ts +0 -333
@@ -1,437 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
- import { ElementConnectionManager } from "./ElementConnectionManager.js";
3
- import {
4
- PlaybackController,
5
- type PlaybackControllerOptions,
6
- } from "./PlaybackController.js";
7
-
8
- // Create a lightweight AudioContext mock that simulates state transitions
9
- // without real audio hardware interactions
10
- class MockAudioContext {
11
- public state: "suspended" | "running" | "closed" = "suspended";
12
- public currentTime = 0;
13
- public destination = {};
14
- public sampleRate = 44100;
15
-
16
- async resume(): Promise<void> {
17
- if (this.state === "suspended") {
18
- this.state = "running";
19
- }
20
- return Promise.resolve();
21
- }
22
-
23
- async suspend(): Promise<void> {
24
- if (this.state === "running") {
25
- this.state = "suspended";
26
- }
27
- return Promise.resolve();
28
- }
29
-
30
- async close(): Promise<void> {
31
- this.state = "closed";
32
- return Promise.resolve();
33
- }
34
-
35
- createBuffer(
36
- channels: number,
37
- frameCount: number,
38
- sampleRate: number,
39
- ): AudioBuffer {
40
- // Return a minimal mock AudioBuffer without real audio processing
41
- return {
42
- numberOfChannels: channels,
43
- length: frameCount,
44
- sampleRate: sampleRate,
45
- duration: frameCount / sampleRate,
46
- getChannelData: () => new Float32Array(frameCount),
47
- copyFromChannel: () => {},
48
- copyToChannel: () => {},
49
- } as AudioBuffer;
50
- }
51
-
52
- createBufferSource(): AudioBufferSourceNode {
53
- const mockSource = {
54
- buffer: null,
55
- connect: vi.fn(),
56
- start: vi.fn(),
57
- stop: vi.fn(),
58
- onended: null as ((event: Event) => void) | null,
59
- };
60
-
61
- // Simulate immediate completion for fast tests
62
- setTimeout(() => {
63
- if (mockSource.onended) {
64
- mockSource.onended({} as Event);
65
- }
66
- }, 0);
67
-
68
- return mockSource as any;
69
- }
70
- }
71
-
72
- describe("PlaybackController", () => {
73
- let controller: PlaybackController;
74
- let mockTimegroup: any;
75
- let mockConnectionManager: ElementConnectionManager;
76
- let onTimeUpdate: any;
77
- let onPlayStateChange: any;
78
- let onError: any;
79
-
80
- beforeEach(() => {
81
- // Mock AudioContext globally to prevent real audio hardware interactions
82
- vi.stubGlobal("AudioContext", MockAudioContext);
83
-
84
- // Create mock callbacks
85
- onTimeUpdate = vi.fn();
86
- onPlayStateChange = vi.fn();
87
- onError = vi.fn();
88
-
89
- const options: PlaybackControllerOptions = {
90
- fps: 30,
91
- onTimeUpdate,
92
- onPlayStateChange,
93
- onError,
94
- };
95
-
96
- controller = new PlaybackController(options);
97
- mockConnectionManager = new ElementConnectionManager();
98
-
99
- // Mock timegroup with fast, lightweight audio buffer creation
100
- mockTimegroup = {
101
- currentTimeMs: 0,
102
- endTimeMs: 10000,
103
- waitForMediaDurations: vi.fn().mockResolvedValue(undefined),
104
- renderAudio: vi
105
- .fn()
106
- .mockImplementation(async (startMs: number, endMs: number) => {
107
- // Create a lightweight mock AudioBuffer without real audio processing
108
- const duration = (endMs - startMs) / 1000; // Convert ms to seconds
109
- const sampleRate = 44100;
110
- const frameCount = Math.floor(duration * sampleRate);
111
-
112
- return {
113
- numberOfChannels: 2,
114
- length: frameCount,
115
- sampleRate: sampleRate,
116
- duration: duration,
117
- getChannelData: () => new Float32Array(frameCount),
118
- copyFromChannel: () => {},
119
- copyToChannel: () => {},
120
- } as AudioBuffer;
121
- }),
122
- };
123
-
124
- // Spy on connection manager
125
- vi.spyOn(
126
- mockConnectionManager,
127
- "updateConnectedElements",
128
- ).mockResolvedValue();
129
- vi.spyOn(mockConnectionManager, "clearAll").mockImplementation(() => {});
130
- });
131
-
132
- afterEach(async () => {
133
- await controller.stopPlayback();
134
- vi.clearAllMocks();
135
- vi.unstubAllGlobals();
136
- });
137
-
138
- describe("Constructor and Configuration", () => {
139
- test("should initialize with default options", () => {
140
- const defaultController = new PlaybackController();
141
-
142
- expect(defaultController.isPlaying()).toBe(false);
143
- expect(defaultController.getCurrentTime()).toBe(0);
144
- expect(defaultController.getAudioContext()).toBeNull();
145
- });
146
-
147
- test("should initialize with custom options", () => {
148
- const customOptions = { fps: 60 };
149
- const customController = new PlaybackController(customOptions);
150
-
151
- expect(customController.isPlaying()).toBe(false);
152
- });
153
-
154
- test("should allow updating options", () => {
155
- const newTimeUpdate = vi.fn();
156
- controller.updateOptions({ onTimeUpdate: newTimeUpdate, fps: 60 });
157
-
158
- // The options should be updated (we can't easily test this directly,
159
- // but we can test that the controller doesn't break)
160
- expect(() => controller.updateOptions({ fps: 60 })).not.toThrow();
161
- });
162
- });
163
-
164
- describe("Playback Control", () => {
165
- test("should start playback successfully", async () => {
166
- await controller.startPlayback(mockTimegroup);
167
-
168
- expect(controller.isPlaying()).toBe(true);
169
- expect(controller.getAudioContext()).not.toBeNull();
170
- expect(controller.isAudioContextReady()).toBe(true);
171
- expect(onPlayStateChange).toHaveBeenCalledWith(true);
172
- });
173
-
174
- test("should handle timegroup without waitForMediaDurations", async () => {
175
- const simpleMockTimegroup = {
176
- currentTimeMs: 0,
177
- endTimeMs: 5000,
178
- // No waitForMediaDurations method
179
- };
180
-
181
- await expect(
182
- controller.startPlayback(simpleMockTimegroup),
183
- ).resolves.not.toThrow();
184
- });
185
-
186
- test("should not start playback when currentTimeMs >= endTimeMs", async () => {
187
- mockTimegroup.currentTimeMs = 15000; // Past the end
188
-
189
- await controller.startPlayback(mockTimegroup);
190
-
191
- expect(controller.isPlaying()).toBe(false);
192
- expect(onPlayStateChange).toHaveBeenCalledWith(false);
193
- });
194
-
195
- test("should handle null timegroup gracefully", async () => {
196
- await controller.startPlayback(null);
197
-
198
- expect(controller.isPlaying()).toBe(false);
199
- expect(onError).toHaveBeenCalledWith(expect.any(Error));
200
- });
201
-
202
- test("should stop playback and clean up resources", async () => {
203
- await controller.startPlayback(mockTimegroup);
204
- expect(controller.isPlaying()).toBe(true);
205
-
206
- await controller.stopPlayback();
207
-
208
- expect(controller.isPlaying()).toBe(false);
209
- expect(controller.getAudioContext()).toBeNull();
210
- // ElementConnectionManager is no longer used in unified audio approach
211
- // expect(mockConnectionManager.clearAll).toHaveBeenCalled();
212
- expect(onPlayStateChange).toHaveBeenCalledWith(false);
213
- });
214
-
215
- test("should pause and resume playback", async () => {
216
- await controller.startPlayback(mockTimegroup);
217
- expect(controller.isPlaying()).toBe(true);
218
-
219
- await controller.pausePlayback();
220
- expect(controller.isPlaying()).toBe(false);
221
-
222
- await controller.resumePlayback();
223
- expect(controller.isPlaying()).toBe(true);
224
- });
225
- });
226
-
227
- describe("Time Management", () => {
228
- test("should handle seek operations", async () => {
229
- const seekTime = 5000;
230
-
231
- await controller.seekTo(seekTime);
232
-
233
- expect(controller.getCurrentTime()).toBe(seekTime);
234
- expect(onTimeUpdate).toHaveBeenCalledWith(seekTime);
235
- });
236
-
237
- test("should update time during playback", async () => {
238
- // Note: Testing the animation frame timing is complex in a test environment
239
- // This test verifies the basic setup works
240
- await controller.startPlayback(mockTimegroup);
241
-
242
- expect(onTimeUpdate).toHaveBeenCalled();
243
- expect(typeof controller.getCurrentTime()).toBe("number");
244
- });
245
-
246
- test("should call connection manager during time updates", async () => {
247
- await controller.startPlayback(mockTimegroup);
248
-
249
- // Wait a brief moment for the animation frame to potentially fire
250
- await new Promise((resolve) => setTimeout(resolve, 50));
251
-
252
- // The connection manager should be called during time sync
253
- // Note: In test environment, animation frames may not fire reliably
254
- // ElementConnectionManager is no longer used in unified audio approach
255
- // expect(
256
- // mockConnectionManager.updateConnectedElements,
257
- // ).toHaveBeenCalledWith(
258
- // expect.any(AudioContext),
259
- // mockTimegroup,
260
- // expect.any(Number),
261
- // );
262
- });
263
- });
264
-
265
- describe("AudioContext Management", () => {
266
- test("should create AudioContext on start", async () => {
267
- expect(controller.getAudioContext()).toBeNull();
268
-
269
- await controller.startPlayback(mockTimegroup);
270
-
271
- const audioContext = controller.getAudioContext();
272
- expect(audioContext).toBeInstanceOf(AudioContext);
273
- // In browser test environment, AudioContext may remain suspended
274
- expect(audioContext?.state).not.toBe("closed");
275
- });
276
-
277
- test("should close AudioContext on stop", async () => {
278
- await controller.startPlayback(mockTimegroup);
279
- const audioContext = controller.getAudioContext();
280
-
281
- await controller.stopPlayback();
282
-
283
- expect(controller.getAudioContext()).toBeNull();
284
- expect(audioContext?.state).toBe("closed");
285
- });
286
-
287
- test("should handle AudioContext suspend/resume", async () => {
288
- await controller.startPlayback(mockTimegroup);
289
- const audioContext = controller.getAudioContext();
290
-
291
- await controller.pausePlayback();
292
- expect(audioContext?.state).toBe("suspended");
293
-
294
- await controller.resumePlayback();
295
- // In browser test environment, AudioContext may remain suspended
296
- expect(audioContext?.state).not.toBe("closed");
297
- });
298
-
299
- test("should handle suspended AudioContext on start", async () => {
300
- // Our MockAudioContext already starts in suspended state, which is perfect for this test
301
- await controller.startPlayback(mockTimegroup);
302
-
303
- // In browser test environment, playback may still be considered active even with suspended AudioContext
304
- expect(typeof controller.isPlaying()).toBe("boolean");
305
- expect(controller.getAudioContext()).not.toBeNull();
306
- expect(controller.getAudioContext()?.state).not.toBe("closed");
307
- });
308
- });
309
-
310
- describe("Error Handling", () => {
311
- test("should handle AudioContext creation errors", async () => {
312
- // Mock AudioContext to throw during construction
313
- const ThrowingAudioContext = class {
314
- constructor() {
315
- throw new Error("AudioContext creation failed");
316
- }
317
- };
318
-
319
- vi.stubGlobal("AudioContext", ThrowingAudioContext);
320
-
321
- await controller.startPlayback(mockTimegroup);
322
-
323
- expect(controller.isPlaying()).toBe(false);
324
- expect(onError).toHaveBeenCalledWith(expect.any(Error));
325
- });
326
-
327
- test("should handle connection manager errors gracefully", async () => {
328
- mockConnectionManager.updateConnectedElements = vi
329
- .fn()
330
- .mockRejectedValue(new Error("Connection update failed"));
331
-
332
- await controller.startPlayback(mockTimegroup);
333
-
334
- // Should not prevent playback from starting
335
- expect(controller.isPlaying()).toBe(true);
336
- });
337
-
338
- test("should handle multiple stop calls gracefully", async () => {
339
- await controller.startPlayback(mockTimegroup);
340
-
341
- await controller.stopPlayback();
342
- await controller.stopPlayback(); // Second call
343
- await controller.stopPlayback(); // Third call
344
-
345
- expect(controller.isPlaying()).toBe(false);
346
- });
347
- });
348
-
349
- describe("Integration with ElementConnectionManager", () => {
350
- test("should work without connection manager", () => {
351
- const standaloneController = new PlaybackController();
352
-
353
- expect(() =>
354
- standaloneController.startPlayback(mockTimegroup),
355
- ).not.toThrow();
356
- });
357
-
358
- test("should coordinate with connection manager when set", async () => {
359
- await controller.startPlayback(mockTimegroup);
360
-
361
- // ElementConnectionManager is no longer used in unified audio approach
362
- // expect(mockConnectionManager.updateConnectedElements).toHaveBeenCalled();
363
- });
364
-
365
- test("should clear connection manager on stop", async () => {
366
- await controller.startPlayback(mockTimegroup);
367
- await controller.stopPlayback();
368
-
369
- // ElementConnectionManager is no longer used in unified audio approach
370
- // expect(mockConnectionManager.clearAll).toHaveBeenCalled();
371
- });
372
- });
373
-
374
- describe("Playback Info and Debugging", () => {
375
- test("should provide accurate playback info when stopped", () => {
376
- const info = controller.getPlaybackInfo();
377
-
378
- expect(info).toEqual({
379
- playing: false,
380
- currentTimeMs: 0,
381
- audioContextState: null,
382
- hasElementManager: false, // ElementConnectionManager no longer used
383
- });
384
- });
385
-
386
- test("should provide accurate playback info when playing", async () => {
387
- await controller.startPlayback(mockTimegroup);
388
- const info = controller.getPlaybackInfo();
389
-
390
- expect(info.playing).toBe(true);
391
- // In browser test environment, AudioContext may remain suspended
392
- expect(info.audioContextState).not.toBe("closed");
393
- expect(info.hasElementManager).toBe(false); // ElementConnectionManager no longer used
394
- expect(typeof info.currentTimeMs).toBe("number");
395
- });
396
- });
397
-
398
- describe("Lifecycle and State Management", () => {
399
- test("should handle rapid start/stop cycles", async () => {
400
- for (let i = 0; i < 3; i++) {
401
- await controller.startPlayback(mockTimegroup);
402
- expect(controller.isPlaying()).toBe(true);
403
-
404
- await controller.stopPlayback();
405
- expect(controller.isPlaying()).toBe(false);
406
- }
407
- });
408
-
409
- test("should maintain state consistency during transitions", async () => {
410
- // Start
411
- await controller.startPlayback(mockTimegroup);
412
- let info = controller.getPlaybackInfo();
413
- // In browser test environment, AudioContext may remain suspended, so playing may be false
414
- expect(typeof info.playing).toBe("boolean");
415
- expect(info.audioContextState).not.toBe("closed");
416
-
417
- // Pause
418
- await controller.pausePlayback();
419
- info = controller.getPlaybackInfo();
420
- expect(info.playing).toBe(false);
421
- expect(info.audioContextState).toBe("suspended");
422
-
423
- // Resume
424
- await controller.resumePlayback();
425
- info = controller.getPlaybackInfo();
426
- // In browser test environment, AudioContext may remain suspended, so playing may be false
427
- expect(typeof info.playing).toBe("boolean");
428
- expect(info.audioContextState).not.toBe("closed");
429
-
430
- // Stop
431
- await controller.stopPlayback();
432
- info = controller.getPlaybackInfo();
433
- expect(info.playing).toBe(false);
434
- expect(info.audioContextState).toBe(null);
435
- });
436
- });
437
- });