@ai-matrx/media 0.6.67 → 0.7.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.
@@ -0,0 +1,674 @@
1
+ // src/live-audio/pcm-player.ts
2
+ function base64ToInt16(b64) {
3
+ const binary = atob(b64);
4
+ const len = binary.length;
5
+ const bytes = new Uint8Array(len);
6
+ for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
7
+ return new Int16Array(bytes.buffer, 0, len >> 1);
8
+ }
9
+ function createStreamingPcmPlayer(format) {
10
+ const sampleRate = format.sampleRate > 0 ? format.sampleRate : 24e3;
11
+ const channels = format.channels > 0 ? format.channels : 1;
12
+ const chunks = [];
13
+ const chunkEndFrames = [];
14
+ let totalFrames = 0;
15
+ let ctx = null;
16
+ const scheduled = [];
17
+ let nextChunkIndex = 0;
18
+ let nextPlayTime = 0;
19
+ let playing = false;
20
+ let ended = false;
21
+ let destroyed = false;
22
+ let pausedAtFrame = 0;
23
+ let anchorCtxTime = 0;
24
+ let anchorFrame = 0;
25
+ let updateTimer = null;
26
+ const updateCallbacks = /* @__PURE__ */ new Set();
27
+ function notify() {
28
+ for (const cb of updateCallbacks) {
29
+ try {
30
+ cb();
31
+ } catch {
32
+ }
33
+ }
34
+ }
35
+ function startUpdateLoop() {
36
+ if (updateTimer !== null) return;
37
+ updateTimer = setInterval(() => {
38
+ notify();
39
+ if (!playing && updateTimer !== null) {
40
+ clearInterval(updateTimer);
41
+ updateTimer = null;
42
+ }
43
+ }, 250);
44
+ }
45
+ function framesToMs(frames) {
46
+ return frames / sampleRate * 1e3;
47
+ }
48
+ function getPositionFrames() {
49
+ if (!playing || !ctx) return pausedAtFrame;
50
+ const elapsed = Math.max(0, ctx.currentTime - anchorCtxTime);
51
+ return Math.min(
52
+ anchorFrame + Math.round(elapsed * sampleRate),
53
+ totalFrames
54
+ );
55
+ }
56
+ function ensureContext() {
57
+ if (typeof window === "undefined") return null;
58
+ if (!ctx) {
59
+ const Ctor = window.AudioContext || window.webkitAudioContext;
60
+ if (!Ctor) return null;
61
+ ctx = new Ctor({ sampleRate });
62
+ }
63
+ if (ctx.state === "suspended") void ctx.resume();
64
+ return ctx;
65
+ }
66
+ function int16ToBuffer(c, pcm, fromFrame) {
67
+ const frames = pcm.length / channels - fromFrame;
68
+ if (frames <= 0) return null;
69
+ const buf = c.createBuffer(channels, frames, sampleRate);
70
+ for (let ch = 0; ch < channels; ch++) {
71
+ const data = buf.getChannelData(ch);
72
+ for (let i = 0; i < frames; i++) {
73
+ data[i] = (pcm[(fromFrame + i) * channels + ch] ?? 0) / 32768;
74
+ }
75
+ }
76
+ return buf;
77
+ }
78
+ function scheduleChunk(index, fromFrame) {
79
+ const pcm = chunks[index];
80
+ if (!ctx || !pcm) return;
81
+ const buf = int16ToBuffer(ctx, pcm, fromFrame);
82
+ if (!buf) return;
83
+ const src = ctx.createBufferSource();
84
+ src.buffer = buf;
85
+ src.connect(ctx.destination);
86
+ const now = ctx.currentTime;
87
+ const startAt = Math.max(now, nextPlayTime);
88
+ if (startAt > nextPlayTime && nextPlayTime > 0) {
89
+ anchorCtxTime += startAt - nextPlayTime;
90
+ }
91
+ src.start(startAt);
92
+ nextPlayTime = startAt + buf.duration;
93
+ scheduled.push(src);
94
+ src.onended = () => {
95
+ const i = scheduled.indexOf(src);
96
+ if (i !== -1) scheduled.splice(i, 1);
97
+ try {
98
+ src.disconnect();
99
+ } catch {
100
+ }
101
+ if (playing && scheduled.length === 0 && ended && nextChunkIndex >= chunks.length) {
102
+ pausedAtFrame = totalFrames;
103
+ playing = false;
104
+ notify();
105
+ }
106
+ };
107
+ }
108
+ function scheduleFrom(frame) {
109
+ if (!ctx) return;
110
+ let index = chunkEndFrames.findIndex((end2) => frame < end2);
111
+ if (index === -1) index = chunks.length;
112
+ const chunkStart = index === 0 ? 0 : chunkEndFrames[index - 1] ?? 0;
113
+ nextPlayTime = ctx.currentTime;
114
+ anchorCtxTime = ctx.currentTime;
115
+ anchorFrame = frame;
116
+ if (index < chunks.length) {
117
+ scheduleChunk(index, frame - chunkStart);
118
+ for (let i = index + 1; i < chunks.length; i++) scheduleChunk(i, 0);
119
+ }
120
+ nextChunkIndex = chunks.length;
121
+ }
122
+ function stopScheduled() {
123
+ for (const src of scheduled) {
124
+ try {
125
+ src.onended = null;
126
+ src.stop(0);
127
+ src.disconnect();
128
+ } catch {
129
+ }
130
+ }
131
+ scheduled.length = 0;
132
+ }
133
+ function enqueueBase64(b64) {
134
+ if (destroyed || ended) return;
135
+ let pcm;
136
+ try {
137
+ pcm = base64ToInt16(b64);
138
+ } catch {
139
+ return;
140
+ }
141
+ if (pcm.length === 0) return;
142
+ chunks.push(pcm);
143
+ totalFrames += pcm.length / channels;
144
+ chunkEndFrames.push(totalFrames);
145
+ if (playing && ctx) {
146
+ scheduleChunk(chunks.length - 1, 0);
147
+ nextChunkIndex = chunks.length;
148
+ }
149
+ notify();
150
+ }
151
+ function play() {
152
+ if (destroyed || playing) return;
153
+ const c = ensureContext();
154
+ if (!c) return;
155
+ if (ended && pausedAtFrame >= totalFrames) pausedAtFrame = 0;
156
+ playing = true;
157
+ scheduleFrom(pausedAtFrame);
158
+ startUpdateLoop();
159
+ notify();
160
+ }
161
+ function pause() {
162
+ if (!playing) return;
163
+ pausedAtFrame = getPositionFrames();
164
+ playing = false;
165
+ stopScheduled();
166
+ notify();
167
+ }
168
+ function seekMs(ms) {
169
+ const frame = Math.max(
170
+ 0,
171
+ Math.min(Math.round(ms / 1e3 * sampleRate), totalFrames)
172
+ );
173
+ if (playing) {
174
+ stopScheduled();
175
+ scheduleFrom(frame);
176
+ } else {
177
+ pausedAtFrame = frame;
178
+ }
179
+ notify();
180
+ }
181
+ function end() {
182
+ ended = true;
183
+ notify();
184
+ }
185
+ function destroy() {
186
+ if (destroyed) return;
187
+ destroyed = true;
188
+ playing = false;
189
+ stopScheduled();
190
+ if (updateTimer !== null) {
191
+ clearInterval(updateTimer);
192
+ updateTimer = null;
193
+ }
194
+ updateCallbacks.clear();
195
+ chunks.length = 0;
196
+ chunkEndFrames.length = 0;
197
+ if (ctx) {
198
+ void ctx.close().catch(() => void 0);
199
+ ctx = null;
200
+ }
201
+ }
202
+ return {
203
+ enqueueBase64,
204
+ end,
205
+ play,
206
+ pause,
207
+ seekMs,
208
+ getPositionMs: () => framesToMs(getPositionFrames()),
209
+ getBufferedMs: () => framesToMs(totalFrames),
210
+ isPlaying: () => playing,
211
+ hasEnded: () => ended,
212
+ onUpdate: (cb) => {
213
+ updateCallbacks.add(cb);
214
+ return () => updateCallbacks.delete(cb);
215
+ },
216
+ destroy
217
+ };
218
+ }
219
+
220
+ // src/live-audio/mp3-player.ts
221
+ function base64ToArrayBuffer(b64) {
222
+ const binary = atob(b64);
223
+ const bytes = new Uint8Array(binary.length);
224
+ for (let i = 0; i < binary.length; i++) {
225
+ bytes[i] = binary.charCodeAt(i);
226
+ }
227
+ return bytes.buffer;
228
+ }
229
+ function createStreamingMp3Player(options = {}) {
230
+ if (typeof window === "undefined" || typeof MediaSource === "undefined") {
231
+ console.info(
232
+ "[streaming-mp3] MediaSource is unavailable \u2014 live MP3 playback disabled; waiting for the canonical audio file"
233
+ );
234
+ return null;
235
+ }
236
+ const mimeType = options.mimeType || "audio/mpeg";
237
+ if (!MediaSource.isTypeSupported(mimeType)) {
238
+ console.info(
239
+ `[streaming-mp3] browser does not support MediaSource ${mimeType} \u2014 live playback disabled; waiting for the canonical audio file`
240
+ );
241
+ return null;
242
+ }
243
+ const mediaSource = new MediaSource();
244
+ const objectUrl = URL.createObjectURL(mediaSource);
245
+ const audio = document.createElement("audio");
246
+ audio.preload = "auto";
247
+ audio.hidden = true;
248
+ audio.setAttribute("aria-hidden", "true");
249
+ audio.src = objectUrl;
250
+ document.body.appendChild(audio);
251
+ const pending = [];
252
+ const callbacks = /* @__PURE__ */ new Set();
253
+ let sourceBuffer = null;
254
+ let streamEnded = false;
255
+ let destroyed = false;
256
+ let failed = false;
257
+ function notify() {
258
+ for (const callback of callbacks) callback();
259
+ }
260
+ function fail(message, cause) {
261
+ if (failed || destroyed) return;
262
+ failed = true;
263
+ const error = cause instanceof Error ? new Error(message, { cause }) : new Error(message);
264
+ console.error(`[streaming-mp3] ${message}`, cause ?? "");
265
+ options.onError?.(error);
266
+ notify();
267
+ }
268
+ function finishIfReady() {
269
+ if (!destroyed && !failed && streamEnded && pending.length === 0 && sourceBuffer && !sourceBuffer.updating && mediaSource.readyState === "open") {
270
+ try {
271
+ mediaSource.endOfStream();
272
+ } catch (error) {
273
+ fail("could not finalize the MediaSource stream", error);
274
+ }
275
+ }
276
+ }
277
+ function appendNext() {
278
+ if (destroyed || failed || !sourceBuffer || sourceBuffer.updating || mediaSource.readyState !== "open") {
279
+ return;
280
+ }
281
+ const next = pending.shift();
282
+ if (!next) {
283
+ finishIfReady();
284
+ return;
285
+ }
286
+ try {
287
+ sourceBuffer.appendBuffer(next);
288
+ } catch (error) {
289
+ fail("could not append an MP3 stream chunk", error);
290
+ }
291
+ }
292
+ mediaSource.addEventListener(
293
+ "sourceopen",
294
+ () => {
295
+ if (destroyed || failed || sourceBuffer) return;
296
+ try {
297
+ sourceBuffer = mediaSource.addSourceBuffer(mimeType);
298
+ sourceBuffer.mode = "sequence";
299
+ sourceBuffer.addEventListener("updateend", () => {
300
+ notify();
301
+ appendNext();
302
+ });
303
+ sourceBuffer.addEventListener("error", () => {
304
+ fail("MediaSource rejected the MP3 stream");
305
+ });
306
+ appendNext();
307
+ } catch (error) {
308
+ fail("could not initialize an MP3 SourceBuffer", error);
309
+ }
310
+ },
311
+ { once: true }
312
+ );
313
+ for (const event of ["timeupdate", "progress", "play", "pause", "ended"]) {
314
+ audio.addEventListener(event, notify);
315
+ }
316
+ audio.addEventListener("error", () => {
317
+ fail(
318
+ `browser could not decode the live MP3 stream${audio.error?.message ? `: ${audio.error.message}` : ""}`
319
+ );
320
+ });
321
+ function getBufferedMs() {
322
+ try {
323
+ const ranges = audio.buffered;
324
+ return ranges.length > 0 ? ranges.end(ranges.length - 1) * 1e3 : 0;
325
+ } catch {
326
+ return 0;
327
+ }
328
+ }
329
+ return {
330
+ enqueueBase64: (b64) => {
331
+ if (destroyed || failed || streamEnded) return;
332
+ try {
333
+ const bytes = base64ToArrayBuffer(b64);
334
+ if (bytes.byteLength === 0) return;
335
+ pending.push(bytes);
336
+ appendNext();
337
+ notify();
338
+ } catch (error) {
339
+ fail("received a malformed base64 MP3 chunk", error);
340
+ }
341
+ },
342
+ end: () => {
343
+ if (destroyed || failed) return;
344
+ streamEnded = true;
345
+ finishIfReady();
346
+ notify();
347
+ },
348
+ play: () => {
349
+ if (destroyed || failed) return;
350
+ void audio.play().catch((error) => {
351
+ fail("browser refused live MP3 playback", error);
352
+ });
353
+ },
354
+ pause: () => audio.pause(),
355
+ seekMs: (ms) => {
356
+ if (destroyed || failed) return;
357
+ const clampedMs = Math.max(0, Math.min(ms, getBufferedMs()));
358
+ audio.currentTime = clampedMs / 1e3;
359
+ notify();
360
+ },
361
+ getPositionMs: () => Number.isFinite(audio.currentTime) ? audio.currentTime * 1e3 : 0,
362
+ getBufferedMs,
363
+ isPlaying: () => !audio.paused && !audio.ended,
364
+ hasEnded: () => streamEnded,
365
+ onUpdate: (callback) => {
366
+ callbacks.add(callback);
367
+ return () => callbacks.delete(callback);
368
+ },
369
+ destroy: () => {
370
+ if (destroyed) return;
371
+ destroyed = true;
372
+ audio.pause();
373
+ pending.length = 0;
374
+ callbacks.clear();
375
+ if (sourceBuffer?.updating) {
376
+ try {
377
+ sourceBuffer.abort();
378
+ } catch {
379
+ }
380
+ }
381
+ audio.removeAttribute("src");
382
+ audio.load();
383
+ audio.remove();
384
+ URL.revokeObjectURL(objectUrl);
385
+ sourceBuffer = null;
386
+ }
387
+ };
388
+ }
389
+
390
+ // src/live-audio/controller.ts
391
+ var LIVE_AUDIO_CHUNK = "audio_stream_chunk";
392
+ var LIVE_AUDIO_END = "audio_stream_end";
393
+ function isRecord(value) {
394
+ return typeof value === "object" && value !== null && !Array.isArray(value);
395
+ }
396
+ function isLiveAudioEvent(data) {
397
+ return isRecord(data) && (data.type === LIVE_AUDIO_CHUNK || data.type === LIVE_AUDIO_END);
398
+ }
399
+ function liveAudioEncodingOf(chunk) {
400
+ const mime = chunk.mime_type ?? "";
401
+ if (chunk.encoding === "mp3" || /(?:mpeg|mp3)/i.test(mime)) return "mp3";
402
+ if (chunk.encoding === "pcm_s16le" || /(?:l16|pcm)/i.test(mime)) return "pcm_s16le";
403
+ return null;
404
+ }
405
+ function createLiveAudioController(options) {
406
+ const makePcm = options.createPcmPlayer ?? createStreamingPcmPlayer;
407
+ const makeMp3 = options.createMp3Player ?? createStreamingMp3Player;
408
+ let player = null;
409
+ let expectedSeq = 0;
410
+ let streamId = null;
411
+ let encoding = null;
412
+ let broken = false;
413
+ function clearPlayer() {
414
+ player?.destroy();
415
+ player = null;
416
+ options.onPlayer(null);
417
+ }
418
+ function drop(reason) {
419
+ const message = `${reason} \u2014 dropping live playback; the saved file takes over when rendering finishes`;
420
+ if (options.onDrop) options.onDrop(message);
421
+ else console.warn(`[live-audio] ${message}`);
422
+ broken = true;
423
+ clearPlayer();
424
+ }
425
+ function onChunk(e) {
426
+ if (broken) return;
427
+ const codec = liveAudioEncodingOf(e);
428
+ if (!codec) {
429
+ drop(`unsupported audio stream format ${e.encoding || e.mime_type}`);
430
+ return;
431
+ }
432
+ if (streamId !== null && (streamId !== e.stream_id || encoding !== codec)) {
433
+ drop(
434
+ `audio stream identity/format changed mid-render (${streamId}/${encoding} \u2192 ${e.stream_id}/${codec})`
435
+ );
436
+ return;
437
+ }
438
+ if (e.seq !== expectedSeq) {
439
+ drop(`audio stream gap (expected seq ${expectedSeq}, got ${e.seq})`);
440
+ return;
441
+ }
442
+ streamId = e.stream_id;
443
+ encoding = codec;
444
+ expectedSeq = e.seq + 1;
445
+ if (!player) {
446
+ const created = codec === "mp3" ? makeMp3({
447
+ mimeType: e.mime_type || "audio/mpeg",
448
+ onError: (error) => drop(error.message)
449
+ }) : makePcm({
450
+ sampleRate: e.sample_rate || 24e3,
451
+ channels: e.channels || 1
452
+ });
453
+ if (!created) {
454
+ drop(`no browser player is available for ${codec}`);
455
+ return;
456
+ }
457
+ player = created;
458
+ options.onPlayer(created);
459
+ }
460
+ player.enqueueBase64(e.audio_base64);
461
+ }
462
+ return {
463
+ handle(data) {
464
+ if (!isLiveAudioEvent(data)) return false;
465
+ if (data.type === LIVE_AUDIO_CHUNK) {
466
+ onChunk(data);
467
+ return true;
468
+ }
469
+ player?.end();
470
+ options.onEnd?.(data);
471
+ return true;
472
+ },
473
+ reset() {
474
+ expectedSeq = 0;
475
+ streamId = null;
476
+ encoding = null;
477
+ broken = false;
478
+ if (player) clearPlayer();
479
+ },
480
+ destroy() {
481
+ player?.destroy();
482
+ player = null;
483
+ },
484
+ get player() {
485
+ return player;
486
+ }
487
+ };
488
+ }
489
+ function takeLiveAudioHandoff(player) {
490
+ const resume = player.isPlaying();
491
+ const timeSec = player.getPositionMs() / 1e3;
492
+ player.pause();
493
+ return timeSec > 0 ? { timeSec, resume } : null;
494
+ }
495
+
496
+ // src/live-audio/registry.ts
497
+ function createLiveAudioRegistry(options = {}) {
498
+ const controllers = /* @__PURE__ */ new Map();
499
+ const players = /* @__PURE__ */ new Map();
500
+ const listeners = /* @__PURE__ */ new Set();
501
+ const notify = () => {
502
+ for (const listener of listeners) {
503
+ try {
504
+ listener();
505
+ } catch {
506
+ }
507
+ }
508
+ };
509
+ const controllerFor = (key) => {
510
+ let controller = controllers.get(key);
511
+ if (!controller) {
512
+ controller = createLiveAudioController({
513
+ onPlayer: (player) => {
514
+ if (player) players.set(key, player);
515
+ else players.delete(key);
516
+ notify();
517
+ },
518
+ onEnd: (event) => options.onEnd?.(key, event),
519
+ onDrop: (reason) => options.onDrop ? options.onDrop(key, reason) : console.warn(`[live-audio] ${key}: ${reason}`),
520
+ ...options.createPcmPlayer ? { createPcmPlayer: options.createPcmPlayer } : {},
521
+ ...options.createMp3Player ? { createMp3Player: options.createMp3Player } : {}
522
+ });
523
+ controllers.set(key, controller);
524
+ }
525
+ return controller;
526
+ };
527
+ return {
528
+ handle(key, payload) {
529
+ if (!isLiveAudioEvent(payload)) return false;
530
+ return controllerFor(key).handle(payload);
531
+ },
532
+ player: (key) => players.get(key) ?? null,
533
+ subscribe(listener) {
534
+ listeners.add(listener);
535
+ return () => listeners.delete(listener);
536
+ },
537
+ reset(key) {
538
+ controllers.get(key)?.reset();
539
+ },
540
+ destroy() {
541
+ for (const controller of controllers.values()) controller.destroy();
542
+ controllers.clear();
543
+ const had = players.size > 0;
544
+ players.clear();
545
+ if (had) notify();
546
+ }
547
+ };
548
+ }
549
+
550
+ // src/live-audio/media-frames.ts
551
+ function mediaFrameSourceKey(source) {
552
+ return `${source.nodeId ?? ""}|${source.dispatchId ?? ""}|${source.itemIndex ?? ""}`;
553
+ }
554
+ var MAX_OPEN_MEDIA_FRAME_SETS = 32;
555
+ function isRecord2(value) {
556
+ return typeof value === "object" && value !== null && !Array.isArray(value);
557
+ }
558
+ function createMediaFrameAssembler(options) {
559
+ const open = /* @__PURE__ */ new Map();
560
+ const ready = [];
561
+ let arrival = 0;
562
+ const drop = (frameId, reason) => {
563
+ open.delete(frameId);
564
+ if (options.onDrop) options.onDrop(frameId, reason);
565
+ else console.warn(`[media-frames] dropped ${frameId}: ${reason}`);
566
+ };
567
+ const release = () => {
568
+ if (ready.length === 0) return;
569
+ ready.sort((a, b) => a.streamSeq - b.streamSeq);
570
+ const blocking = /* @__PURE__ */ new Map();
571
+ for (const set of open.values()) {
572
+ blocking.set(set.key, Math.min(blocking.get(set.key) ?? Infinity, set.firstSeq));
573
+ }
574
+ for (let i = 0; i < ready.length; ) {
575
+ const item = ready[i];
576
+ const key = mediaFrameSourceKey(item.source);
577
+ if (item.streamSeq < (blocking.get(key) ?? Infinity)) {
578
+ ready.splice(i, 1);
579
+ options.onPayload(item);
580
+ } else {
581
+ i += 1;
582
+ }
583
+ }
584
+ };
585
+ return {
586
+ push(frame) {
587
+ if (frame.kind !== "media") return false;
588
+ const frameId = frame.frame_id;
589
+ const count = frame.frame_count ?? 1;
590
+ const index = frame.frame_index ?? 0;
591
+ if (!frameId || !Number.isInteger(count) || count < 1 || !Number.isInteger(index) || index < 0 || index >= count) {
592
+ if (options.onDrop) options.onDrop(frameId ?? "?", "malformed frame");
593
+ else console.warn("[media-frames] malformed media frame dropped");
594
+ return true;
595
+ }
596
+ arrival += 1;
597
+ const seq = typeof frame.stream_seq === "number" && Number.isFinite(frame.stream_seq) ? frame.stream_seq : arrival;
598
+ let set = open.get(frameId);
599
+ if (!set) {
600
+ if (open.size >= MAX_OPEN_MEDIA_FRAME_SETS) {
601
+ const oldest = open.keys().next();
602
+ if (!oldest.done) drop(oldest.value, "abandoned (too many open sets)");
603
+ }
604
+ const source = {
605
+ nodeId: frame.node_id ?? null,
606
+ dispatchId: frame.dispatch_id ?? null,
607
+ itemIndex: frame.item_index ?? null
608
+ };
609
+ set = {
610
+ key: mediaFrameSourceKey(source),
611
+ count,
612
+ slices: /* @__PURE__ */ new Map(),
613
+ firstSeq: seq,
614
+ source
615
+ };
616
+ open.set(frameId, set);
617
+ }
618
+ set.slices.set(index, frame.delta);
619
+ if (index === 0) set.firstSeq = Math.min(set.firstSeq, seq);
620
+ if (set.slices.size < set.count) {
621
+ release();
622
+ return true;
623
+ }
624
+ open.delete(frameId);
625
+ let body = "";
626
+ for (let i = 0; i < set.count; i++) body += set.slices.get(i) ?? "";
627
+ let parsed;
628
+ try {
629
+ parsed = JSON.parse(body);
630
+ } catch {
631
+ if (options.onDrop) options.onDrop(frameId, "unparseable payload");
632
+ else console.warn(`[media-frames] dropped ${frameId}: unparseable payload`);
633
+ release();
634
+ return true;
635
+ }
636
+ if (!isRecord2(parsed) || typeof parsed.type !== "string") {
637
+ if (options.onDrop) options.onDrop(frameId, "payload has no type");
638
+ else console.warn(`[media-frames] dropped ${frameId}: payload has no type`);
639
+ release();
640
+ return true;
641
+ }
642
+ for (const [id, other] of [...open.entries()]) {
643
+ if (other.key === set.key && other.firstSeq < set.firstSeq) {
644
+ drop(id, "incomplete set");
645
+ }
646
+ }
647
+ ready.push({ payload: parsed, source: set.source, streamSeq: set.firstSeq });
648
+ release();
649
+ return true;
650
+ },
651
+ flush() {
652
+ for (const id of [...open.keys()]) drop(id, "incomplete at stream end");
653
+ release();
654
+ },
655
+ get openCount() {
656
+ return open.size;
657
+ }
658
+ };
659
+ }
660
+ export {
661
+ LIVE_AUDIO_CHUNK,
662
+ LIVE_AUDIO_END,
663
+ MAX_OPEN_MEDIA_FRAME_SETS,
664
+ createLiveAudioController,
665
+ createLiveAudioRegistry,
666
+ createMediaFrameAssembler,
667
+ createStreamingMp3Player,
668
+ createStreamingPcmPlayer,
669
+ isLiveAudioEvent,
670
+ liveAudioEncodingOf,
671
+ mediaFrameSourceKey,
672
+ takeLiveAudioHandoff
673
+ };
674
+ //# sourceMappingURL=live-audio.js.map