@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,290 +0,0 @@
1
- /**
2
- * Manages playback timing, AudioContext lifecycle, and timeline synchronization
3
- * Extracted from ContextMixin to improve separation of concerns and testability
4
- */
5
- var PlaybackController = class {
6
- constructor(options = {}) {
7
- this.playbackAudioContext = null;
8
- this.animationFrameRequest = null;
9
- this.playing = false;
10
- this.currentTimeMs = 0;
11
- this.audioStartTime = 0;
12
- this.playbackStartTimeMs = 0;
13
- this.activeChunks = /* @__PURE__ */ new Map();
14
- this.chunkDurationMs = 4e3;
15
- this.lookaheadChunks = 2;
16
- this.currentChunkIndex = 0;
17
- this.renderingChunks = /* @__PURE__ */ new Set();
18
- this.options = {
19
- fps: 30,
20
- onTimeUpdate: () => {},
21
- onPlayStateChange: () => {},
22
- onError: () => {},
23
- ...options
24
- };
25
- this.msPerFrame = 1e3 / this.options.fps;
26
- }
27
- /**
28
- * Start playback for the given timegroup
29
- */
30
- async startPlayback(timegroup, fromMs) {
31
- await this.stopPlayback();
32
- if (!timegroup) {
33
- this.setPlaying(false);
34
- this.options.onPlayStateChange(false);
35
- this.options.onError(/* @__PURE__ */ new Error("No timegroup provided"));
36
- return;
37
- }
38
- await timegroup.waitForMediaDurations?.();
39
- const currentMs = fromMs ?? timegroup.currentTimeMs ?? 0;
40
- const toMs = timegroup.endTimeMs;
41
- if (currentMs >= toMs) {
42
- this.setPlaying(false);
43
- this.options.onPlayStateChange(false);
44
- return;
45
- }
46
- try {
47
- this.playbackAudioContext = new AudioContext({ latencyHint: "playback" });
48
- if (this.playbackAudioContext.state === "suspended") {
49
- console.warn("AudioContext is suspended, attempting to resume...");
50
- try {
51
- await Promise.race([this.playbackAudioContext.resume(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("AudioContext resume timeout")), 2e3))]);
52
- } catch (error) {
53
- console.warn("AudioContext resume failed:", error);
54
- }
55
- } else await this.playbackAudioContext.resume();
56
- this.audioStartTime = this.playbackAudioContext.currentTime;
57
- this.playbackStartTimeMs = currentMs;
58
- this.currentChunkIndex = Math.floor(currentMs / this.chunkDurationMs);
59
- await this.startProgressivePlayback(timegroup, currentMs, toMs);
60
- if (this.isAudioContextReady()) {
61
- this.setPlaying(true);
62
- this.currentTimeMs = currentMs;
63
- this.options.onTimeUpdate(currentMs);
64
- this.syncPlayheadToAudioBuffer(timegroup, currentMs);
65
- } else {
66
- this.setPlaying(false);
67
- this.options.onPlayStateChange(false);
68
- console.warn("AudioContext not ready for playback, state:", this.playbackAudioContext?.state);
69
- }
70
- } catch (error) {
71
- console.error("🎵 [PLAYBACK_ERROR] Failed to setup progressive audio playback:", error);
72
- this.setPlaying(false);
73
- this.options.onPlayStateChange(false);
74
- this.options.onError(error);
75
- }
76
- }
77
- /**
78
- * Stop playback and clean up resources
79
- */
80
- async stopPlayback() {
81
- for (const [_chunkIndex, bufferSource] of this.activeChunks.entries()) try {
82
- bufferSource.stop();
83
- } catch (_error) {}
84
- this.activeChunks.clear();
85
- this.renderingChunks.clear();
86
- if (this.playbackAudioContext) {
87
- if (this.playbackAudioContext.state !== "closed") await this.playbackAudioContext.close();
88
- }
89
- if (this.animationFrameRequest) {
90
- cancelAnimationFrame(this.animationFrameRequest);
91
- this.animationFrameRequest = null;
92
- }
93
- this.playbackAudioContext = null;
94
- this.setPlaying(false);
95
- }
96
- /**
97
- * Pause playback (can be resumed)
98
- */
99
- async pausePlayback() {
100
- if (this.playbackAudioContext && this.playbackAudioContext.state === "running") try {
101
- await Promise.race([this.playbackAudioContext.suspend(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("AudioContext suspend timeout")), 2e3))]);
102
- } catch (error) {
103
- console.warn("AudioContext suspend failed:", error);
104
- }
105
- if (this.animationFrameRequest) {
106
- cancelAnimationFrame(this.animationFrameRequest);
107
- this.animationFrameRequest = null;
108
- }
109
- this.setPlaying(false);
110
- }
111
- /**
112
- * Resume paused playback
113
- */
114
- async resumePlayback() {
115
- if (this.playbackAudioContext && this.playbackAudioContext.state === "suspended") {
116
- try {
117
- await Promise.race([this.playbackAudioContext.resume(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("AudioContext resume timeout")), 2e3))]);
118
- } catch (error) {
119
- console.warn("AudioContext resume failed:", error);
120
- }
121
- this.setPlaying(true);
122
- }
123
- }
124
- /**
125
- * Seek to a specific time (restarts progressive playback from new position)
126
- */
127
- async seekTo(timeMs, timegroup) {
128
- this.currentTimeMs = timeMs;
129
- this.options.onTimeUpdate(timeMs);
130
- if (this.playing && timegroup) {
131
- for (const bufferSource of this.activeChunks.values()) try {
132
- bufferSource.stop();
133
- } catch (_error) {}
134
- this.activeChunks.clear();
135
- this.renderingChunks.clear();
136
- this.audioStartTime = this.playbackAudioContext?.currentTime ?? 0;
137
- this.playbackStartTimeMs = timeMs;
138
- this.currentChunkIndex = Math.floor(timeMs / this.chunkDurationMs);
139
- await this.startProgressivePlayback(timegroup, timeMs, timegroup.endTimeMs);
140
- }
141
- }
142
- /**
143
- * Internal method to sync playhead with unified audio buffer timing
144
- */
145
- syncPlayheadToAudioBuffer(timegroup, startMs) {
146
- if (!this.playbackAudioContext || !this.playing) return;
147
- const elapsedAudioTime = this.playbackAudioContext.currentTime - this.audioStartTime;
148
- const rawTimeMs = startMs + elapsedAudioTime * 1e3;
149
- const nextTimeMs = Math.round(rawTimeMs / this.msPerFrame) * this.msPerFrame;
150
- if (nextTimeMs !== this.currentTimeMs) {
151
- this.currentTimeMs = nextTimeMs;
152
- this.options.onTimeUpdate(nextTimeMs);
153
- if (timegroup && timegroup.currentTimeMs !== nextTimeMs) timegroup.currentTimeMs = nextTimeMs;
154
- this.updateProgressiveChunks(timegroup, nextTimeMs, timegroup.endTimeMs);
155
- }
156
- this.animationFrameRequest = requestAnimationFrame(() => {
157
- this.syncPlayheadToAudioBuffer(timegroup, startMs);
158
- });
159
- }
160
- /**
161
- * Update playing state and notify observers
162
- */
163
- setPlaying(playing) {
164
- if (this.playing !== playing) {
165
- this.playing = playing;
166
- this.options.onPlayStateChange(playing);
167
- }
168
- }
169
- /**
170
- * Get current playback state
171
- */
172
- isPlaying() {
173
- return this.playing;
174
- }
175
- /**
176
- * Get current time
177
- */
178
- getCurrentTime() {
179
- return this.currentTimeMs;
180
- }
181
- /**
182
- * Get current AudioContext
183
- */
184
- getAudioContext() {
185
- return this.playbackAudioContext;
186
- }
187
- /**
188
- * Check if AudioContext is ready
189
- */
190
- isAudioContextReady() {
191
- return this.playbackAudioContext != null && this.playbackAudioContext.state !== "closed";
192
- }
193
- /**
194
- * Get playback statistics for debugging
195
- */
196
- getPlaybackInfo() {
197
- return {
198
- playing: this.playing,
199
- currentTimeMs: this.currentTimeMs,
200
- audioContextState: this.playbackAudioContext?.state || null,
201
- hasElementManager: false
202
- };
203
- }
204
- /**
205
- * Update playback options
206
- */
207
- updateOptions(options) {
208
- Object.assign(this.options, options);
209
- if (options.fps) this.msPerFrame = 1e3 / options.fps;
210
- }
211
- /**
212
- * Start progressive chunk rendering and playback
213
- */
214
- async startProgressivePlayback(timegroup, fromMs, _toMs) {
215
- const firstChunkIndex = Math.floor(fromMs / this.chunkDurationMs);
216
- const firstChunkStart = firstChunkIndex * this.chunkDurationMs;
217
- const offsetInChunk = fromMs - firstChunkStart;
218
- await this.renderAndScheduleChunk(timegroup, firstChunkStart, firstChunkIndex, offsetInChunk);
219
- }
220
- /**
221
- * Render and schedule a single audio chunk
222
- */
223
- async renderAndScheduleChunk(timegroup, chunkStartMs, chunkIndex, offsetInChunk = 0) {
224
- if (this.renderingChunks.has(chunkIndex) || this.activeChunks.has(chunkIndex)) return;
225
- this.renderingChunks.add(chunkIndex);
226
- try {
227
- const chunkEndMs = chunkStartMs + this.chunkDurationMs;
228
- const chunkBuffer = await timegroup.renderAudio(chunkStartMs, chunkEndMs);
229
- const bufferSource = this.playbackAudioContext?.createBufferSource();
230
- if (!bufferSource || !this.playbackAudioContext?.destination) throw new Error("Audio context or buffer source not available");
231
- bufferSource.buffer = chunkBuffer;
232
- bufferSource.connect(this.playbackAudioContext.destination);
233
- const chunkTimelineStartMs = chunkIndex * this.chunkDurationMs;
234
- const relativeDelayMs = Math.max(0, chunkTimelineStartMs - this.playbackStartTimeMs);
235
- const chunkStartTime = this.audioStartTime + relativeDelayMs / 1e3;
236
- const startOffset = offsetInChunk / 1e3;
237
- const now = this.playbackAudioContext?.currentTime ?? 0;
238
- if (chunkStartTime <= now) console.warn(`🎵 [CHUNK_TIMING_WARNING] Chunk ${chunkIndex} scheduled in the past! startTime=${chunkStartTime.toFixed(3)}s, currentTime=${now.toFixed(3)}s`);
239
- bufferSource.start(chunkStartTime, startOffset);
240
- this.activeChunks.set(chunkIndex, bufferSource);
241
- bufferSource.onended = () => {
242
- this.activeChunks.delete(chunkIndex);
243
- };
244
- } catch (error) {
245
- console.error(`🎵 [CHUNK_ERROR] Failed to render chunk ${chunkIndex}:`, error);
246
- } finally {
247
- this.renderingChunks.delete(chunkIndex);
248
- }
249
- }
250
- /**
251
- * Update chunk rendering as playhead advances - now handles all chunk management
252
- */
253
- updateProgressiveChunks(timegroup, currentTimeMs, maxTimeMs) {
254
- const newChunkIndex = Math.floor(currentTimeMs / this.chunkDurationMs);
255
- if (newChunkIndex !== this.currentChunkIndex) {
256
- this.currentChunkIndex = newChunkIndex;
257
- this.cleanupOldChunks();
258
- }
259
- this.ensureChunksAhead(timegroup, maxTimeMs);
260
- }
261
- /**
262
- * Systematically ensure chunks are ready ahead of current playback (synchronous)
263
- */
264
- ensureChunksAhead(timegroup, maxTimeMs) {
265
- for (let i = 1; i <= this.lookaheadChunks; i++) {
266
- const targetChunkIndex = this.currentChunkIndex + i;
267
- const targetChunkStartMs = targetChunkIndex * this.chunkDurationMs;
268
- if (targetChunkStartMs >= maxTimeMs) break;
269
- if (!this.renderingChunks.has(targetChunkIndex) && !this.activeChunks.has(targetChunkIndex)) {
270
- const offsetInChunk = 0;
271
- this.renderAndScheduleChunk(timegroup, targetChunkStartMs, targetChunkIndex, offsetInChunk).catch((error) => {
272
- console.error(`🎵 [ENSURE_CHUNKS_ERROR] Failed to render chunk ${targetChunkIndex}:`, error);
273
- });
274
- }
275
- }
276
- }
277
- /**
278
- * Clean up chunks that are behind the current playhead
279
- */
280
- cleanupOldChunks() {
281
- const cutoffChunkIndex = this.currentChunkIndex - 1;
282
- for (const [chunkIndex, bufferSource] of this.activeChunks.entries()) if (chunkIndex < cutoffChunkIndex) {
283
- try {
284
- bufferSource.stop();
285
- } catch (_error) {}
286
- this.activeChunks.delete(chunkIndex);
287
- }
288
- }
289
- };
290
- export { PlaybackController };
@@ -1,62 +0,0 @@
1
- export interface MediaSourceManagerOptions {
2
- onError?: (error: Error) => void;
3
- onReady?: () => void;
4
- onUpdateEnd?: () => void;
5
- timeout?: number;
6
- }
7
- /**
8
- * Manages MediaSource for audio streaming
9
- */
10
- export declare class MediaSourceManager {
11
- private mediaSource;
12
- private audioElement;
13
- private sourceBuffer;
14
- private mediaSourceReady;
15
- private pendingSegments;
16
- private options;
17
- constructor(options?: MediaSourceManagerOptions);
18
- /**
19
- * Initialize MediaSource for audio streaming
20
- */
21
- initialize(): Promise<void>;
22
- /**
23
- * Create SourceBuffer with codec fallback
24
- */
25
- private createSourceBuffer;
26
- /**
27
- * Setup SourceBuffer event listeners
28
- */
29
- private setupSourceBufferListeners;
30
- /**
31
- * Feed audio segments directly to MediaSource SourceBuffer
32
- */
33
- feedSegment(segmentBuffer: ArrayBuffer): Promise<void>;
34
- /**
35
- * Process any queued segments when SourceBuffer becomes available
36
- */
37
- private processPendingSegments;
38
- /**
39
- * Log debug information for troubleshooting
40
- */
41
- private logDebugInfo;
42
- /**
43
- * Set audio element current time
44
- */
45
- setCurrentTime(timeMs: number): void;
46
- /**
47
- * Get the audio element for MediaElementSource
48
- */
49
- getAudioElement(): HTMLAudioElement | null;
50
- /**
51
- * Check if MediaSource is ready
52
- */
53
- isReady(): boolean;
54
- /**
55
- * Get buffered time ranges
56
- */
57
- getBuffered(): TimeRanges | null;
58
- /**
59
- * Clean up MediaSource resources
60
- */
61
- cleanup(_preserveCache?: boolean): void;
62
- }
@@ -1,211 +0,0 @@
1
- /**
2
- * Manages MediaSource for audio streaming
3
- */
4
- var MediaSourceManager = class {
5
- constructor(options = {}) {
6
- this.mediaSource = null;
7
- this.audioElement = null;
8
- this.sourceBuffer = null;
9
- this.mediaSourceReady = false;
10
- this.pendingSegments = [];
11
- this.options = {
12
- timeout: 1e4,
13
- ...options
14
- };
15
- }
16
- /**
17
- * Initialize MediaSource for audio streaming
18
- */
19
- async initialize() {
20
- this.cleanup(true);
21
- this.mediaSource = new MediaSource();
22
- this.audioElement = document.createElement("audio");
23
- this.audioElement.addEventListener("error", (event) => {
24
- const error = this.audioElement?.error;
25
- console.error("🎵 [AUDIO_ELEMENT_ERROR] Audio element error:", {
26
- code: error?.code,
27
- message: error?.message,
28
- event
29
- });
30
- if (this.options.onError) this.options.onError(/* @__PURE__ */ new Error(`Audio element error: ${error?.message}`));
31
- });
32
- this.audioElement.src = URL.createObjectURL(this.mediaSource);
33
- return new Promise((resolve, reject) => {
34
- this.mediaSource?.addEventListener("sourceopen", () => {
35
- try {
36
- const sourceBuffer = this.createSourceBuffer();
37
- if (!sourceBuffer) throw new Error("Failed to create SourceBuffer with any supported codec");
38
- this.sourceBuffer = sourceBuffer;
39
- this.setupSourceBufferListeners();
40
- this.mediaSourceReady = true;
41
- if (this.options.onReady) this.options.onReady();
42
- resolve();
43
- } catch (error) {
44
- console.error("🎵 [MEDIA_SOURCE_ERROR] Failed to create SourceBuffer:", error);
45
- reject(error);
46
- }
47
- });
48
- this.mediaSource?.addEventListener("error", (error) => {
49
- console.error("🎵 [MEDIA_SOURCE_ERROR] MediaSource error:", error);
50
- reject(error);
51
- });
52
- setTimeout(() => {
53
- if (!this.mediaSourceReady) {
54
- const timeoutError = /* @__PURE__ */ new Error("MediaSource failed to open within timeout");
55
- console.error("🎵 [MEDIA_SOURCE_TIMEOUT] MediaSource initialization timeout");
56
- reject(timeoutError);
57
- }
58
- }, 4e3);
59
- });
60
- }
61
- /**
62
- * Create SourceBuffer with codec fallback
63
- */
64
- createSourceBuffer() {
65
- const codecOptions = [
66
- "audio/mp4; codecs=\"mp4a.40.2\"",
67
- "audio/mp4; codecs=\"mp4a.40.5\"",
68
- "audio/mp4"
69
- ];
70
- let sourceBuffer;
71
- let lastError;
72
- for (const codec of codecOptions) try {
73
- if (MediaSource.isTypeSupported(codec)) {
74
- sourceBuffer = this.mediaSource?.addSourceBuffer(codec);
75
- break;
76
- }
77
- } catch (error) {
78
- console.error(`🎵 [CODEC_ERROR] Failed to create SourceBuffer with ${codec}:`, error);
79
- lastError = error;
80
- }
81
- if (!sourceBuffer && lastError) throw new Error(`Failed to create SourceBuffer with any supported codec. Last error: ${lastError.message}`);
82
- return sourceBuffer;
83
- }
84
- /**
85
- * Setup SourceBuffer event listeners
86
- */
87
- setupSourceBufferListeners() {
88
- if (!this.sourceBuffer) return;
89
- this.sourceBuffer.addEventListener("updateend", () => {
90
- this.processPendingSegments();
91
- if (this.options.onUpdateEnd) this.options.onUpdateEnd();
92
- });
93
- this.sourceBuffer.addEventListener("error", (event) => {
94
- console.error("🎵 [SOURCE_BUFFER_EVENT_ERROR] SourceBuffer error event:", event);
95
- if (this.options.onError) this.options.onError(/* @__PURE__ */ new Error("SourceBuffer error"));
96
- });
97
- }
98
- /**
99
- * Feed audio segments directly to MediaSource SourceBuffer
100
- */
101
- async feedSegment(segmentBuffer) {
102
- if (!this.mediaSourceReady || !this.sourceBuffer) {
103
- this.pendingSegments.push(segmentBuffer);
104
- return;
105
- }
106
- if (this.sourceBuffer.updating) {
107
- this.pendingSegments.push(segmentBuffer);
108
- return;
109
- }
110
- if (this.audioElement?.error) {
111
- const error = this.audioElement.error;
112
- console.error("🎵 [MEDIA_ELEMENT_ERROR] HTMLMediaElement error detected:", {
113
- code: error.code,
114
- message: error.message,
115
- MEDIA_ERR_ABORTED: error.code === MediaError.MEDIA_ERR_ABORTED,
116
- MEDIA_ERR_NETWORK: error.code === MediaError.MEDIA_ERR_NETWORK,
117
- MEDIA_ERR_DECODE: error.code === MediaError.MEDIA_ERR_DECODE,
118
- MEDIA_ERR_SRC_NOT_SUPPORTED: error.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
119
- });
120
- this.audioElement.load();
121
- return;
122
- }
123
- try {
124
- this.sourceBuffer.appendBuffer(segmentBuffer);
125
- } catch (error) {
126
- console.error("🎵 [SOURCE_BUFFER_ERROR] Failed to append segment:", error);
127
- this.logDebugInfo();
128
- this.pendingSegments.push(segmentBuffer);
129
- }
130
- }
131
- /**
132
- * Process any queued segments when SourceBuffer becomes available
133
- */
134
- processPendingSegments() {
135
- if (!this.sourceBuffer || this.sourceBuffer.updating || this.pendingSegments.length === 0) return;
136
- const nextSegment = this.pendingSegments.shift();
137
- if (nextSegment) this.feedSegment(nextSegment);
138
- }
139
- /**
140
- * Log debug information for troubleshooting
141
- */
142
- logDebugInfo() {
143
- console.error("🎵 [SOURCE_BUFFER_DEBUG] SourceBuffer state:", {
144
- updating: this.sourceBuffer?.updating,
145
- buffered: this.sourceBuffer?.buffered ? Array.from({ length: this.sourceBuffer.buffered.length }, (_, i) => `${this.sourceBuffer?.buffered.start(i)}-${this.sourceBuffer?.buffered.end(i)}`) : [],
146
- mode: this.sourceBuffer?.mode,
147
- timestampOffset: this.sourceBuffer?.timestampOffset
148
- });
149
- console.error("🎵 [MEDIA_SOURCE_DEBUG] MediaSource state:", {
150
- readyState: this.mediaSource?.readyState,
151
- sourceBuffers: this.mediaSource?.sourceBuffers.length,
152
- duration: this.mediaSource?.duration
153
- });
154
- console.error("🎵 [AUDIO_ELEMENT_DEBUG] Audio element state:", {
155
- readyState: this.audioElement?.readyState,
156
- networkState: this.audioElement?.networkState,
157
- error: this.audioElement?.error?.code,
158
- src: `${this.audioElement?.src.substring(0, 50)}...`
159
- });
160
- }
161
- /**
162
- * Set audio element current time
163
- */
164
- setCurrentTime(timeMs) {
165
- if (this.audioElement) this.audioElement.currentTime = timeMs / 1e3;
166
- }
167
- /**
168
- * Get the audio element for MediaElementSource
169
- */
170
- getAudioElement() {
171
- return this.audioElement;
172
- }
173
- /**
174
- * Check if MediaSource is ready
175
- */
176
- isReady() {
177
- return this.mediaSourceReady;
178
- }
179
- /**
180
- * Get buffered time ranges
181
- */
182
- getBuffered() {
183
- return this.sourceBuffer?.buffered || null;
184
- }
185
- /**
186
- * Clean up MediaSource resources
187
- */
188
- cleanup(_preserveCache = false) {
189
- if (this.sourceBuffer && this.mediaSource && this.mediaSource.readyState === "open") try {
190
- this.mediaSource.removeSourceBuffer(this.sourceBuffer);
191
- } catch (error) {
192
- console.warn("🎵 [CLEANUP_ERROR] Error removing SourceBuffer:", error);
193
- }
194
- if (this.mediaSource) try {
195
- if (this.mediaSource.readyState === "open") this.mediaSource.endOfStream();
196
- } catch (error) {
197
- console.warn("🎵 [CLEANUP_ERROR] Error ending MediaSource:", error);
198
- }
199
- if (this.audioElement) try {
200
- URL.revokeObjectURL(this.audioElement.src);
201
- } catch (error) {
202
- console.warn("🎵 [CLEANUP_ERROR] Error revoking URL:", error);
203
- }
204
- this.mediaSource = null;
205
- this.audioElement = null;
206
- this.sourceBuffer = null;
207
- this.mediaSourceReady = false;
208
- this.pendingSegments = [];
209
- }
210
- };
211
- export { MediaSourceManager };