@editframe/elements 0.18.3-beta.0 → 0.18.8-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.
- package/dist/elements/EFAudio.d.ts +1 -2
- package/dist/elements/EFAudio.js +6 -9
- package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +34 -5
- package/dist/elements/EFMedia/BaseMediaEngine.js +20 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +5 -5
- package/dist/elements/EFMedia/BufferedSeekingInput.js +27 -7
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +22 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +4 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +11 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +17 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +11 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +4 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.d.ts +28 -0
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +11 -2
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +11 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +3 -2
- package/dist/elements/EFMedia.d.ts +0 -12
- package/dist/elements/EFMedia.js +4 -30
- package/dist/elements/EFTimegroup.js +12 -17
- package/dist/elements/EFVideo.d.ts +0 -9
- package/dist/elements/EFVideo.js +0 -7
- package/dist/elements/SampleBuffer.js +6 -6
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +71 -17
- package/dist/gui/TWMixin.js +1 -1
- package/dist/style.css +1 -1
- package/dist/transcoding/types/index.d.ts +9 -9
- package/package.json +2 -3
- package/src/elements/EFAudio.browsertest.ts +7 -7
- package/src/elements/EFAudio.ts +7 -20
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +100 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +72 -7
- package/src/elements/EFMedia/BaseMediaEngine.ts +50 -1
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +135 -54
- package/src/elements/EFMedia/BufferedSeekingInput.ts +74 -17
- package/src/elements/EFMedia/JitMediaEngine.ts +58 -2
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +10 -1
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +16 -8
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +35 -4
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +12 -1
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +3 -2
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +10 -1
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +46 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +27 -3
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +12 -1
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +3 -2
- package/src/elements/EFMedia.browsertest.ts +73 -33
- package/src/elements/EFMedia.ts +11 -54
- package/src/elements/EFTimegroup.ts +21 -26
- package/src/elements/EFVideo.browsertest.ts +895 -162
- package/src/elements/EFVideo.ts +0 -16
- package/src/elements/SampleBuffer.ts +8 -10
- package/src/gui/ContextMixin.ts +104 -26
- package/src/transcoding/types/index.ts +10 -6
- package/test/EFVideo.framegen.browsertest.ts +1 -1
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +4 -4
- package/test/recordReplayProxyPlugin.js +50 -0
- package/types.json +1 -1
- package/dist/DecoderResetFrequency.test.d.ts +0 -1
- package/dist/DecoderResetRecovery.test.d.ts +0 -1
- package/dist/ScrubTrackManager.d.ts +0 -96
- package/dist/elements/EFMedia/services/AudioElementFactory.browsertest.d.ts +0 -1
- package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +0 -22
- package/dist/elements/EFMedia/services/AudioElementFactory.js +0 -72
- package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +0 -1
- package/dist/elements/EFMedia/services/MediaSourceService.d.ts +0 -47
- package/dist/elements/EFMedia/services/MediaSourceService.js +0 -73
- package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +0 -1
- package/dist/gui/services/ElementConnectionManager.d.ts +0 -59
- package/dist/gui/services/ElementConnectionManager.js +0 -128
- package/dist/gui/services/PlaybackController.browsertest.d.ts +0 -1
- package/dist/gui/services/PlaybackController.d.ts +0 -103
- package/dist/gui/services/PlaybackController.js +0 -290
- package/dist/services/MediaSourceManager.d.ts +0 -62
- package/dist/services/MediaSourceManager.js +0 -211
- package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +0 -325
- package/src/elements/EFMedia/services/AudioElementFactory.ts +0 -119
- package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +0 -257
- package/src/elements/EFMedia/services/MediaSourceService.ts +0 -102
- package/src/gui/services/ElementConnectionManager.browsertest.ts +0 -263
- package/src/gui/services/ElementConnectionManager.ts +0 -224
- package/src/gui/services/PlaybackController.browsertest.ts +0 -437
- package/src/gui/services/PlaybackController.ts +0 -521
- 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
|
-
});
|