@opentui/core 0.4.2 → 0.4.4

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/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  mergeKeyAliases,
44
44
  mergeKeyBindings,
45
45
  wrapWithDelegates
46
- } from "./index-6xr3rbbe.js";
46
+ } from "./index-7z5n7k9m.js";
47
47
  import {
48
48
  ASCIIFontSelectionHelper,
49
49
  ATTRIBUTE_BASE_BITS,
@@ -64,6 +64,14 @@ import {
64
64
  LogLevel,
65
65
  MacOSScrollAccel,
66
66
  MouseParser,
67
+ NativeAudioStreamCloseReason,
68
+ NativeAudioStreamCloseReason1 as NativeAudioStreamCloseReason2,
69
+ NativeAudioStreamFormat,
70
+ NativeAudioStreamFormat1 as NativeAudioStreamFormat2,
71
+ NativeAudioStreamState,
72
+ NativeAudioStreamState1 as NativeAudioStreamState2,
73
+ NativeAudioStreamStateNames,
74
+ NativeMeasureTargetKind,
67
75
  OptimizedBuffer,
68
76
  PasteEvent,
69
77
  RGBA,
@@ -184,7 +192,7 @@ import {
184
192
  visualizeRenderableTree,
185
193
  white,
186
194
  yellow
187
- } from "./index-x7n320jr.js";
195
+ } from "./index-za1krqsf.js";
188
196
  // src/post/effects.ts
189
197
  function toU8(value) {
190
198
  return Math.round(Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0)) * 255);
@@ -2431,7 +2439,7 @@ class SlotRenderable extends Renderable {
2431
2439
  }
2432
2440
  _detachNodeFromMount(node) {
2433
2441
  if (node.parent === this) {
2434
- this.remove(node.id);
2442
+ this.remove(node);
2435
2443
  }
2436
2444
  }
2437
2445
  _cleanupInactivePluginNodes(nextActivePluginIds, registeredPluginIds) {
@@ -2504,7 +2512,7 @@ class SlotRenderable extends Renderable {
2504
2512
  for (const node of this._mountedNodes) {
2505
2513
  if (!desiredNodeSet.has(node)) {
2506
2514
  if (node.parent === this) {
2507
- this.remove(node.id);
2515
+ this.remove(node);
2508
2516
  }
2509
2517
  }
2510
2518
  }
@@ -2515,7 +2523,7 @@ class SlotRenderable extends Renderable {
2515
2523
  continue;
2516
2524
  }
2517
2525
  const childAtIndex = this.getChildren()[index];
2518
- if (childAtIndex?.id !== node.id) {
2526
+ if (childAtIndex !== node) {
2519
2527
  this.add(node, index);
2520
2528
  }
2521
2529
  }
@@ -2525,40 +2533,1295 @@ class SlotRenderable extends Renderable {
2525
2533
  // src/audio.ts
2526
2534
  import { EventEmitter } from "events";
2527
2535
  import { readFile } from "node:fs/promises";
2536
+
2537
+ // src/audio-stream/icy/metadata.ts
2538
+ function parseIcyMetadata(bytes, decoder) {
2539
+ const decoded = decoder.decode(bytes);
2540
+ const nul = decoded.indexOf("\x00");
2541
+ const text = nul === -1 ? decoded : decoded.slice(0, nul);
2542
+ if (text.length === 0)
2543
+ return null;
2544
+ const fields = Object.create(null);
2545
+ let found = false;
2546
+ let offset = 0;
2547
+ while (offset < text.length) {
2548
+ const separator = text.indexOf("='", offset);
2549
+ if (separator === -1)
2550
+ break;
2551
+ const key = text.slice(offset, separator);
2552
+ if (key.length === 0 || /[';=]/.test(key))
2553
+ break;
2554
+ const end = text.indexOf("';", separator + 2);
2555
+ if (end === -1)
2556
+ break;
2557
+ Object.defineProperty(fields, key, {
2558
+ configurable: true,
2559
+ enumerable: true,
2560
+ writable: true,
2561
+ value: text.slice(separator + 2, end)
2562
+ });
2563
+ found = true;
2564
+ offset = end + 2;
2565
+ }
2566
+ return found ? Object.freeze(fields) : null;
2567
+ }
2568
+
2569
+ // src/audio-stream/icy/demuxer.ts
2570
+ var EMPTY_FIELDS = Object.freeze(Object.create(null));
2571
+ function copyHeaders(headers) {
2572
+ const copy = Object.create(null);
2573
+ for (const [name, value] of Object.entries(headers ?? {}))
2574
+ copy[name.toLowerCase()] = value;
2575
+ return Object.freeze(copy);
2576
+ }
2577
+ function fieldsEqual(left, right) {
2578
+ const keys = Object.keys(left);
2579
+ return keys.length === Object.keys(right).length && keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && left[key] === right[key]);
2580
+ }
2581
+
2582
+ class IcyStreamDemuxer {
2583
+ initialMetadata;
2584
+ audioRemaining;
2585
+ metadata = null;
2586
+ metadataOffset = 0;
2587
+ fields = EMPTY_FIELDS;
2588
+ interval;
2589
+ decoder;
2590
+ headers;
2591
+ constructor(options) {
2592
+ if (!Number.isSafeInteger(options.metadataInterval) || options.metadataInterval < 0) {
2593
+ throw new TypeError("metadataInterval must be a non-negative safe integer");
2594
+ }
2595
+ this.interval = options.metadataInterval;
2596
+ try {
2597
+ this.decoder = new TextDecoder(options.metadataEncoding ?? "iso-8859-1");
2598
+ } catch {
2599
+ throw new TypeError(`Unsupported metadataEncoding: ${options.metadataEncoding}`);
2600
+ }
2601
+ this.headers = copyHeaders(options.headers);
2602
+ this.audioRemaining = this.interval;
2603
+ this.initialMetadata = Object.freeze({ format: "icy", headers: this.headers, fields: this.fields });
2604
+ }
2605
+ *push(chunk) {
2606
+ if (this.interval === 0) {
2607
+ if (chunk.byteLength > 0)
2608
+ yield { type: "audio", data: chunk };
2609
+ return;
2610
+ }
2611
+ let offset = 0;
2612
+ while (offset < chunk.byteLength) {
2613
+ if (this.audioRemaining > 0) {
2614
+ const length2 = Math.min(this.audioRemaining, chunk.byteLength - offset);
2615
+ yield { type: "audio", data: chunk.subarray(offset, offset + length2) };
2616
+ offset += length2;
2617
+ this.audioRemaining -= length2;
2618
+ continue;
2619
+ }
2620
+ if (this.metadata == null) {
2621
+ const metadataLength = (chunk[offset] ?? 0) * 16;
2622
+ offset += 1;
2623
+ if (metadataLength === 0) {
2624
+ this.audioRemaining = this.interval;
2625
+ continue;
2626
+ }
2627
+ this.metadata = new Uint8Array(metadataLength);
2628
+ this.metadataOffset = 0;
2629
+ }
2630
+ const metadata = this.metadata;
2631
+ const length = Math.min(metadata.byteLength - this.metadataOffset, chunk.byteLength - offset);
2632
+ metadata.set(chunk.subarray(offset, offset + length), this.metadataOffset);
2633
+ offset += length;
2634
+ this.metadataOffset += length;
2635
+ if (this.metadataOffset !== metadata.byteLength)
2636
+ continue;
2637
+ const fields = parseIcyMetadata(metadata, this.decoder);
2638
+ this.metadata = null;
2639
+ this.metadataOffset = 0;
2640
+ this.audioRemaining = this.interval;
2641
+ if (fields != null && !fieldsEqual(this.fields, fields)) {
2642
+ this.fields = fields;
2643
+ yield {
2644
+ type: "metadata",
2645
+ metadata: Object.freeze({ format: "icy", headers: this.headers, fields })
2646
+ };
2647
+ }
2648
+ }
2649
+ }
2650
+ *flush() {
2651
+ if (this.interval === 0)
2652
+ return;
2653
+ if (this.audioRemaining === 0 || this.metadata != null) {
2654
+ throw new Error("ICY stream ended inside a metadata block");
2655
+ }
2656
+ }
2657
+ }
2658
+ function createIcyStreamDemuxer(options) {
2659
+ return new IcyStreamDemuxer(options);
2660
+ }
2661
+
2662
+ // src/audio-stream/demuxer.ts
2663
+ function selectAudioStreamDemuxer(options) {
2664
+ const icyHeaders = Object.create(null);
2665
+ options.headers.forEach((value2, name) => {
2666
+ if (name.toLowerCase().startsWith("icy-"))
2667
+ icyHeaders[name.toLowerCase()] = value2;
2668
+ });
2669
+ const headers = Object.freeze(icyHeaders);
2670
+ const rawInterval = options.headers.get("icy-metaint");
2671
+ if (rawInterval == null) {
2672
+ return Object.keys(headers).length === 0 ? null : createIcyStreamDemuxer({ metadataInterval: 0, metadataEncoding: options.metadataEncoding, headers });
2673
+ }
2674
+ const value = rawInterval.trim();
2675
+ if (!/^\d+$/.test(value))
2676
+ throw new Error(`Invalid icy-metaint response header: ${rawInterval}`);
2677
+ const interval = Number(value);
2678
+ if (!Number.isSafeInteger(interval))
2679
+ throw new Error(`Invalid icy-metaint response header: ${rawInterval}`);
2680
+ return createIcyStreamDemuxer({ metadataInterval: interval, metadataEncoding: options.metadataEncoding, headers });
2681
+ }
2682
+
2683
+ // src/audio.ts
2684
+ class AudioInitializationError extends Error {
2685
+ action;
2686
+ status;
2687
+ constructor(action, message, status, cause) {
2688
+ super(message);
2689
+ this.name = "AudioInitializationError";
2690
+ this.action = action;
2691
+ this.status = status;
2692
+ if (cause !== undefined)
2693
+ this.cause = cause;
2694
+ }
2695
+ }
2528
2696
  function statusToError(action, status) {
2529
2697
  return new Error(`Audio ${action} failed: ${status}`);
2530
2698
  }
2531
2699
  function toBytes(data) {
2532
2700
  return data instanceof Uint8Array ? data : new Uint8Array(data);
2533
2701
  }
2702
+ var DEFAULT_AUDIO_SAMPLE_RATE = 48000;
2703
+ var DEFAULT_STREAM_PROBE_BYTES = 1024 * 1024;
2704
+ var STREAM_POLL_INTERVAL_MS = 5;
2705
+ var MAX_TIMER_DELAY_MS = 2147483647;
2706
+ var MAX_U32 = 4294967295;
2707
+ var INVALID_STREAM_CHUNK_MESSAGE = "Audio stream chunks must be Uint8Array instances";
2708
+
2709
+ class AudioStreamError extends Error {
2710
+ context;
2711
+ constructor(message, context, cause) {
2712
+ super(message);
2713
+ this.name = "AudioStreamError";
2714
+ this.context = context;
2715
+ if (cause !== undefined)
2716
+ this.cause = cause;
2717
+ }
2718
+ }
2719
+
2720
+ class ClassifiedAudioStreamError extends AudioStreamError {
2721
+ retryable;
2722
+ retryAfterMs;
2723
+ constructor(message, context, retryable, retryAfterMs, cause) {
2724
+ super(message, context, cause);
2725
+ this.retryable = retryable;
2726
+ this.retryAfterMs = retryAfterMs;
2727
+ }
2728
+ }
2729
+ function createAbortError() {
2730
+ return new DOMException("The operation was aborted", "AbortError");
2731
+ }
2732
+ var isU32 = (value) => Number.isInteger(value) && value >= 0 && value <= MAX_U32;
2733
+ function resolvePositiveU32(value, fallback, name) {
2734
+ const resolved = value ?? fallback;
2735
+ if (!Number.isFinite(resolved) || !Number.isInteger(resolved) || resolved <= 0) {
2736
+ throw new TypeError(`${name} must be a finite positive integer`);
2737
+ }
2738
+ if (resolved > MAX_U32)
2739
+ throw new RangeError(`${name} exceeds the supported limit`);
2740
+ return resolved;
2741
+ }
2742
+ function resolveReconnectOptions(options) {
2743
+ const maxRetries = options.maxRetries ?? Number.POSITIVE_INFINITY;
2744
+ const initialDelayMs = options.initialDelayMs ?? 1000;
2745
+ const maxDelayMs = options.maxDelayMs ?? 15000;
2746
+ const backoffFactor = options.backoffFactor ?? 2;
2747
+ const retryOnEnd = options.retryOnEnd ?? false;
2748
+ const retry = options.retry;
2749
+ if (maxRetries !== Number.POSITIVE_INFINITY && (!Number.isInteger(maxRetries) || maxRetries < 0)) {
2750
+ throw new TypeError("reconnect.maxRetries must be a non-negative integer or Infinity");
2751
+ }
2752
+ if (!Number.isFinite(initialDelayMs) || !Number.isInteger(initialDelayMs) || initialDelayMs < 0) {
2753
+ throw new TypeError("reconnect.initialDelayMs must be a finite non-negative integer");
2754
+ }
2755
+ if (!Number.isFinite(maxDelayMs) || !Number.isInteger(maxDelayMs) || maxDelayMs < 0) {
2756
+ throw new TypeError("reconnect.maxDelayMs must be a finite non-negative integer");
2757
+ }
2758
+ if (!Number.isFinite(backoffFactor) || backoffFactor < 1) {
2759
+ throw new TypeError("reconnect.backoffFactor must be a finite number greater than or equal to 1");
2760
+ }
2761
+ if (typeof retryOnEnd !== "boolean") {
2762
+ throw new TypeError("reconnect.retryOnEnd must be a boolean");
2763
+ }
2764
+ if (retry !== undefined && typeof retry !== "function") {
2765
+ throw new TypeError("reconnect.retry must be a function");
2766
+ }
2767
+ return {
2768
+ maxRetries,
2769
+ initialDelayMs,
2770
+ maxDelayMs,
2771
+ backoffFactor,
2772
+ retryOnEnd,
2773
+ retry: retry == null ? undefined : (error, context) => retry.call(options, error, context)
2774
+ };
2775
+ }
2776
+ function resolveAudioStreamOptions(options) {
2777
+ const format = resolveAudioStreamFormat(options.format);
2778
+ const capacityMs = resolvePositiveU32(options.buffer?.capacityMs, 2000, "buffer.capacityMs");
2779
+ const startupMs = resolvePositiveU32(options.buffer?.startupMs, 1000, "buffer.startupMs");
2780
+ const resumeMs = resolvePositiveU32(options.buffer?.resumeMs, 1000, "buffer.resumeMs");
2781
+ const maxProbeBytes = resolvePositiveU32(options.maxProbeBytes, DEFAULT_STREAM_PROBE_BYTES, "maxProbeBytes");
2782
+ if (startupMs > capacityMs)
2783
+ throw new RangeError("buffer.startupMs must not exceed buffer.capacityMs");
2784
+ if (resumeMs > capacityMs)
2785
+ throw new RangeError("buffer.resumeMs must not exceed buffer.capacityMs");
2786
+ return {
2787
+ format,
2788
+ capacityMs,
2789
+ startupMs,
2790
+ resumeMs,
2791
+ volume: options.volume ?? 1,
2792
+ pan: options.pan ?? 0,
2793
+ groupId: options.groupId ?? 0,
2794
+ maxProbeBytes,
2795
+ signal: options.signal,
2796
+ reconnect: options.reconnect === undefined ? undefined : resolveReconnectOptions(options.reconnect)
2797
+ };
2798
+ }
2799
+ function resolveAudioStreamConnector(connector) {
2800
+ const connect = connector?.connect;
2801
+ if (typeof connect !== "function")
2802
+ throw new TypeError("Audio stream connector must define connect()");
2803
+ return { connect: (context) => connect.call(connector, context) };
2804
+ }
2805
+ function runBoundedCleanup(cleanup, timeoutMs = 50) {
2806
+ let result;
2807
+ try {
2808
+ result = Promise.resolve(cleanup()).catch(() => {
2809
+ return;
2810
+ });
2811
+ } catch {
2812
+ return Promise.resolve();
2813
+ }
2814
+ return new Promise((resolve) => {
2815
+ const timer = setTimeout(resolve, timeoutMs);
2816
+ result.then(() => {
2817
+ clearTimeout(timer);
2818
+ resolve();
2819
+ });
2820
+ });
2821
+ }
2822
+ function waitForDelay(delayMs, signal) {
2823
+ if (signal.aborted)
2824
+ return Promise.reject(createAbortError());
2825
+ return new Promise((resolve, reject) => {
2826
+ let remainingMs = delayMs;
2827
+ let timer;
2828
+ const schedule = () => {
2829
+ const currentDelayMs = Math.min(remainingMs, MAX_TIMER_DELAY_MS);
2830
+ timer = setTimeout(() => {
2831
+ remainingMs -= currentDelayMs;
2832
+ if (remainingMs > 0)
2833
+ schedule();
2834
+ else {
2835
+ signal.removeEventListener("abort", onAbort);
2836
+ resolve();
2837
+ }
2838
+ }, currentDelayMs);
2839
+ };
2840
+ const onAbort = () => {
2841
+ clearTimeout(timer);
2842
+ reject(createAbortError());
2843
+ };
2844
+ signal.addEventListener("abort", onAbort, { once: true });
2845
+ schedule();
2846
+ });
2847
+ }
2848
+ function waitForPoll(signal) {
2849
+ return waitForDelay(STREAM_POLL_INTERVAL_MS, signal).then(() => true).catch(() => false);
2850
+ }
2851
+ function parseRetryAfter(value, maxDelayMs) {
2852
+ if (value == null)
2853
+ return;
2854
+ const seconds = Number(value.trim());
2855
+ if (Number.isFinite(seconds) && seconds >= 0)
2856
+ return Math.min(maxDelayMs, Math.ceil(seconds * 1000));
2857
+ const date = Date.parse(value);
2858
+ if (!Number.isFinite(date))
2859
+ return;
2860
+ return Math.min(maxDelayMs, Math.max(0, date - Date.now()));
2861
+ }
2862
+ function resolveAudioStreamFormat(value) {
2863
+ const format = value ?? "mp3";
2864
+ if (format !== "mp3" && format !== "flac")
2865
+ throw new TypeError(`Unsupported audio stream format: ${format}`);
2866
+ return format;
2867
+ }
2868
+ function toNativeAudioStreamFormat(format) {
2869
+ switch (format) {
2870
+ case "mp3":
2871
+ return NativeAudioStreamFormat.Mp3;
2872
+ case "flac":
2873
+ return NativeAudioStreamFormat.Flac;
2874
+ }
2875
+ }
2876
+ function resolveContentTypePolicy(value, receiver) {
2877
+ const policy = value ?? "validate";
2878
+ if (policy !== "validate" && policy !== "ignore" && typeof policy !== "function") {
2879
+ throw new TypeError("contentTypePolicy must be 'validate', 'ignore', or a function");
2880
+ }
2881
+ return typeof policy === "function" ? (context) => policy.call(receiver, context) : policy;
2882
+ }
2883
+ function isAllowedContentType(format, value) {
2884
+ const contentType = value.split(";", 1)[0]?.trim().toLowerCase();
2885
+ switch (format) {
2886
+ case "mp3":
2887
+ return ["audio/mpeg", "audio/mp3", "application/octet-stream", "application/mp3"].includes(contentType ?? "");
2888
+ case "flac":
2889
+ return ["audio/flac", "audio/x-flac", "application/octet-stream"].includes(contentType ?? "");
2890
+ }
2891
+ }
2892
+ function createAudioStreamUrlConnector(source, request, format, contentTypePolicy) {
2893
+ return {
2894
+ async connect({ signal, attempt }) {
2895
+ let response;
2896
+ try {
2897
+ const headers = new Headers(request?.headers);
2898
+ if (!headers.has("icy-metadata"))
2899
+ headers.set("Icy-MetaData", "1");
2900
+ response = await globalThis.fetch(source, { ...request, headers, signal });
2901
+ } catch (cause) {
2902
+ throw new ClassifiedAudioStreamError("Audio stream fetch failed", { action: "fetch", attempt }, true, undefined, cause);
2903
+ }
2904
+ if (!response.ok) {
2905
+ const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"), Number.POSITIVE_INFINITY);
2906
+ await runBoundedCleanup(() => response.body?.cancel());
2907
+ const retryable = [408, 425, 429].includes(response.status) || response.status >= 500 && response.status <= 599;
2908
+ throw new ClassifiedAudioStreamError(`Audio stream request failed with HTTP ${response.status}`, { action: "response", status: response.status, attempt }, retryable, retryAfterMs);
2909
+ }
2910
+ const contentType = response.headers.get("content-type");
2911
+ let contentTypeAccepted = contentType == null || contentTypePolicy === "ignore";
2912
+ if (!contentTypeAccepted && contentTypePolicy === "validate") {
2913
+ contentTypeAccepted = isAllowedContentType(format, contentType);
2914
+ } else if (typeof contentTypePolicy === "function") {
2915
+ try {
2916
+ const result = contentTypePolicy(Object.freeze({
2917
+ format,
2918
+ contentType,
2919
+ status: response.status,
2920
+ url: response.url || String(source)
2921
+ }));
2922
+ if (typeof result !== "boolean")
2923
+ throw new TypeError("contentTypePolicy must return a boolean");
2924
+ contentTypeAccepted = result;
2925
+ } catch (cause) {
2926
+ await runBoundedCleanup(() => response.body?.cancel());
2927
+ throw new ClassifiedAudioStreamError(cause instanceof Error ? cause.message : "Audio stream content type policy failed", { action: "response", status: response.status, attempt }, false, undefined, cause);
2928
+ }
2929
+ }
2930
+ if (!contentTypeAccepted) {
2931
+ await runBoundedCleanup(() => response.body?.cancel());
2932
+ throw new ClassifiedAudioStreamError(`Unsupported audio stream Content-Type: ${contentType}`, { action: "response", status: response.status, attempt }, false);
2933
+ }
2934
+ if (response.body == null) {
2935
+ throw new ClassifiedAudioStreamError("Audio stream response has no body", { action: "response", status: response.status, attempt }, true);
2936
+ }
2937
+ return {
2938
+ body: response.body,
2939
+ info: { headers: response.headers, status: response.status }
2940
+ };
2941
+ }
2942
+ };
2943
+ }
2944
+ function resolveMetadataEncoding(value) {
2945
+ try {
2946
+ return new TextDecoder(value ?? "iso-8859-1").encoding;
2947
+ } catch {
2948
+ throw new TypeError(`Unsupported metadataEncoding: ${value}`);
2949
+ }
2950
+ }
2951
+ function resolveAudioStreamRequest(request) {
2952
+ if (request === undefined)
2953
+ return;
2954
+ const { body: _body, signal: _signal, ...safeRequest } = request;
2955
+ return safeRequest;
2956
+ }
2957
+ function isReadableStreamSource(source) {
2958
+ try {
2959
+ return typeof source?.getReader === "function";
2960
+ } catch {
2961
+ return false;
2962
+ }
2963
+ }
2964
+ function isAsyncIterableSource(source) {
2965
+ try {
2966
+ return typeof source?.[Symbol.asyncIterator] === "function";
2967
+ } catch {
2968
+ return false;
2969
+ }
2970
+ }
2971
+ function isUint8Array(value) {
2972
+ return ArrayBuffer.isView(value) && Object.prototype.toString.call(value) === "[object Uint8Array]";
2973
+ }
2974
+ var createAudioStream;
2975
+ var openAudioStream;
2976
+
2977
+ class AudioStream extends EventEmitter {
2978
+ closed;
2979
+ format;
2980
+ lib;
2981
+ engine;
2982
+ connector;
2983
+ demuxerFactory;
2984
+ readAction;
2985
+ options;
2986
+ removeFromOwner;
2987
+ lifecycleController = new AbortController;
2988
+ nativeStreamId = null;
2989
+ nativeStats = null;
2990
+ activeAttempt = null;
2991
+ pendingCleanup = null;
2992
+ reconnectAttempts = 0;
2993
+ consecutiveReconnectAttempts = 0;
2994
+ disposed = false;
2995
+ exposed = false;
2996
+ terminalError = null;
2997
+ metadata = null;
2998
+ pendingMetadataEvent = false;
2999
+ metadataEventScheduled = false;
3000
+ terminalEventScheduled = false;
3001
+ setupResolve;
3002
+ setupReject;
3003
+ closedResolve;
3004
+ setupPromise;
3005
+ overallAbortListener = () => this.dispose();
3006
+ static {
3007
+ createAudioStream = (init) => new AudioStream(init);
3008
+ openAudioStream = (stream) => stream.open();
3009
+ }
3010
+ constructor(init) {
3011
+ super();
3012
+ this.lib = init.lib;
3013
+ this.engine = init.engine;
3014
+ this.connector = init.connector;
3015
+ this.demuxerFactory = init.demuxer;
3016
+ this.readAction = init.readAction;
3017
+ this.options = resolveAudioStreamOptions(init.options);
3018
+ this.format = this.options.format;
3019
+ this.removeFromOwner = init.removeFromOwner;
3020
+ this.setupPromise = new Promise((resolve, reject) => (this.setupResolve = resolve, this.setupReject = reject));
3021
+ this.closed = new Promise((resolve) => this.closedResolve = resolve);
3022
+ this.options.signal?.addEventListener("abort", this.overallAbortListener, { once: true });
3023
+ }
3024
+ get state() {
3025
+ if (this.disposed)
3026
+ return "disposed";
3027
+ if (this.terminalError != null)
3028
+ return "errored";
3029
+ return this.nativeStats == null ? "initializing" : NativeAudioStreamStateNames[this.nativeStats.state] ?? "errored";
3030
+ }
3031
+ async open() {
3032
+ if (this.options.signal?.aborted)
3033
+ this.dispose();
3034
+ else
3035
+ this.runLifecycle();
3036
+ await this.setupPromise;
3037
+ if (this.lifecycleController.signal.aborted && this.state !== "ended")
3038
+ throw this.terminalError ?? createAbortError();
3039
+ this.exposed = true;
3040
+ if (this.pendingMetadataEvent && this.metadata != null)
3041
+ this.emitMetadata();
3042
+ this.pendingMetadataEvent = false;
3043
+ if (this.state === "ended")
3044
+ this.emitTerminal("ended");
3045
+ }
3046
+ getStats() {
3047
+ const stats = this.readNativeStats();
3048
+ if (!this.lifecycleController.signal.aborted && this.nativeStreamId != null) {
3049
+ const error = this.snapshotError(stats);
3050
+ const ended = stats?.state === NativeAudioStreamState.Ended && !this.options.reconnect?.retryOnEnd;
3051
+ if (error != null || ended) {
3052
+ queueMicrotask(() => {
3053
+ if (this.lifecycleController.signal.aborted)
3054
+ return;
3055
+ const reason = error == null || error.context.action === "decoder" ? NativeAudioStreamCloseReason.PreserveNativeTerminal : NativeAudioStreamCloseReason.TransportError;
3056
+ this.finish(reason, error ?? undefined);
3057
+ });
3058
+ }
3059
+ }
3060
+ return this.toPublicStats();
3061
+ }
3062
+ getMetadata() {
3063
+ return this.metadata;
3064
+ }
3065
+ setVolume(volume) {
3066
+ return this.control("setVolume", (streamId) => this.lib.audioSetStreamVolume(this.engine, streamId, volume));
3067
+ }
3068
+ setPan(pan) {
3069
+ return this.control("setPan", (streamId) => this.lib.audioSetStreamPan(this.engine, streamId, pan));
3070
+ }
3071
+ setGroup(groupId) {
3072
+ if (!isU32(groupId)) {
3073
+ const context = { action: "setGroup" };
3074
+ if (this.exposed) {
3075
+ this.emitAsync("error", new AudioStreamError("Invalid audio stream group", context), context);
3076
+ }
3077
+ return false;
3078
+ }
3079
+ return this.control("setGroup", (streamId) => this.lib.audioSetStreamGroup(this.engine, streamId, groupId));
3080
+ }
3081
+ control(action, call) {
3082
+ const streamId = this.nativeStreamId;
3083
+ if (this.disposed || this.lifecycleController.signal.aborted || streamId == null)
3084
+ return false;
3085
+ const status = call(streamId);
3086
+ if (status !== 0) {
3087
+ const context = { action, status };
3088
+ if (this.exposed) {
3089
+ this.emitAsync("error", new AudioStreamError(`Audio stream ${action} failed: ${status}`, context), context);
3090
+ }
3091
+ return false;
3092
+ }
3093
+ return true;
3094
+ }
3095
+ dispose() {
3096
+ if (this.disposed) {
3097
+ if (this.nativeStreamId != null && this.closeNativeStream(NativeAudioStreamCloseReason.Disposed) === 0)
3098
+ this.removeOwner();
3099
+ return;
3100
+ }
3101
+ this.disposed = true;
3102
+ const wasExposed = this.exposed;
3103
+ this.lifecycleController.abort();
3104
+ const cleanup = this.stopSource();
3105
+ this.setupReject(createAbortError());
3106
+ if (this.closeNativeStream(NativeAudioStreamCloseReason.Disposed) === 0)
3107
+ this.removeOwner();
3108
+ cleanup.finally(() => {
3109
+ if (wasExposed && !this.terminalEventScheduled)
3110
+ this.emitTerminal("disposed");
3111
+ else if (!this.terminalEventScheduled)
3112
+ this.closedResolve();
3113
+ });
3114
+ }
3115
+ async runLifecycle() {
3116
+ while (!this.lifecycleController.signal.aborted) {
3117
+ const attempt = {
3118
+ controller: new AbortController,
3119
+ body: null,
3120
+ closeConnection: null,
3121
+ reader: null,
3122
+ iterator: null,
3123
+ demuxer: null,
3124
+ demuxerFinished: false,
3125
+ sourceReleased: false,
3126
+ sourceAcquisitionAttempted: false,
3127
+ connectionClosed: false,
3128
+ demuxerAborted: false,
3129
+ resourceAcquisition: null,
3130
+ cleanupPromise: null
3131
+ };
3132
+ this.activeAttempt = attempt;
3133
+ let connection;
3134
+ try {
3135
+ let returnedConnection;
3136
+ const finishConnectionAcquisition = this.beginResourceAcquisition(attempt);
3137
+ try {
3138
+ returnedConnection = await this.connector.connect({
3139
+ signal: attempt.controller.signal,
3140
+ attempt: this.consecutiveReconnectAttempts
3141
+ });
3142
+ } finally {
3143
+ finishConnectionAcquisition();
3144
+ }
3145
+ connection = this.resolveConnection(returnedConnection, attempt);
3146
+ } catch (failure) {
3147
+ const active = this.isAttemptActive(attempt);
3148
+ await this.stopSource(attempt);
3149
+ if (!active)
3150
+ return;
3151
+ const context = {
3152
+ action: this.readAction,
3153
+ attempt: this.consecutiveReconnectAttempts
3154
+ };
3155
+ const error2 = failure instanceof AudioStreamError ? failure : new AudioStreamError(failure instanceof Error ? failure.message : "Audio stream connection failed", context, failure);
3156
+ if (await this.retry(error2, attempt, "connect"))
3157
+ continue;
3158
+ return;
3159
+ }
3160
+ if (!this.isAttemptActive(attempt)) {
3161
+ await this.stopSource(attempt);
3162
+ return;
3163
+ }
3164
+ if (!isReadableStreamSource(connection.body) && !isAsyncIterableSource(connection.body)) {
3165
+ await this.stopSource(attempt);
3166
+ const context = { action: "source" };
3167
+ await this.finish(NativeAudioStreamCloseReason.TransportError, new AudioStreamError("Audio stream connection body must be a ReadableStream or AsyncIterable", context));
3168
+ return;
3169
+ }
3170
+ if (!this.isAttemptActive(attempt)) {
3171
+ await this.stopSource(attempt);
3172
+ return;
3173
+ }
3174
+ let initialMetadata = null;
3175
+ let demuxerFailed = false;
3176
+ let demuxerFailure;
3177
+ const finishDemuxerAcquisition = this.beginResourceAcquisition(attempt);
3178
+ try {
3179
+ attempt.demuxer = this.demuxerFactory?.(connection.info) ?? null;
3180
+ initialMetadata = attempt.demuxer?.initialMetadata ?? null;
3181
+ } catch (cause) {
3182
+ demuxerFailed = true;
3183
+ demuxerFailure = cause;
3184
+ } finally {
3185
+ finishDemuxerAcquisition();
3186
+ }
3187
+ if (demuxerFailed) {
3188
+ await this.stopSource(attempt);
3189
+ const error2 = demuxerFailure instanceof AudioStreamError ? demuxerFailure : new AudioStreamError(demuxerFailure instanceof Error ? demuxerFailure.message : "Audio stream demuxer creation failed", { action: "demuxer" }, demuxerFailure);
3190
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error2);
3191
+ return;
3192
+ }
3193
+ if (!this.isAttemptActive(attempt)) {
3194
+ await this.stopSource(attempt);
3195
+ return;
3196
+ }
3197
+ this.publishMetadata(initialMetadata, attempt);
3198
+ try {
3199
+ this.createNativeStream();
3200
+ } catch (cause) {
3201
+ await this.stopSource(attempt);
3202
+ const error2 = cause instanceof AudioStreamError ? cause : new AudioStreamError("Audio stream create failed", { action: "create" }, cause);
3203
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error2);
3204
+ return;
3205
+ }
3206
+ try {
3207
+ if (!await this.consumeSource(connection, attempt))
3208
+ return;
3209
+ } catch (cause) {
3210
+ if (this.lifecycleController.signal.aborted)
3211
+ return;
3212
+ const error2 = cause instanceof AudioStreamError ? cause : cause instanceof TypeError && cause.message === INVALID_STREAM_CHUNK_MESSAGE ? cause : new AudioStreamError("Audio stream source failed", { action: "source" }, cause);
3213
+ if (error2 instanceof ClassifiedAudioStreamError) {
3214
+ if (await this.retry(error2, attempt, "read"))
3215
+ continue;
3216
+ return;
3217
+ }
3218
+ const context = error2 instanceof AudioStreamError ? error2.context : { action: "source" };
3219
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error2, context);
3220
+ return;
3221
+ }
3222
+ if (!this.options.reconnect?.retryOnEnd) {
3223
+ await this.finish(NativeAudioStreamCloseReason.PreserveNativeTerminal);
3224
+ return;
3225
+ }
3226
+ const error = new AudioStreamError("Audio stream source ended", { action: "source" });
3227
+ if (await this.retry(error, attempt, undefined, true))
3228
+ continue;
3229
+ return;
3230
+ }
3231
+ }
3232
+ createNativeStream() {
3233
+ if (this.nativeStreamId != null)
3234
+ return;
3235
+ const created = this.lib.audioCreateStream(this.engine, {
3236
+ capacityMs: this.options.capacityMs,
3237
+ startupMs: this.options.startupMs,
3238
+ resumeMs: this.options.resumeMs,
3239
+ maxProbeBytes: this.options.maxProbeBytes,
3240
+ volume: this.options.volume,
3241
+ pan: this.options.pan,
3242
+ groupId: this.options.groupId,
3243
+ format: toNativeAudioStreamFormat(this.options.format)
3244
+ });
3245
+ if (created.status !== 0 || created.streamId == null) {
3246
+ const context = { action: "create", status: created.status };
3247
+ throw new AudioStreamError(`Audio stream create failed: ${created.status}`, context);
3248
+ }
3249
+ this.nativeStreamId = created.streamId;
3250
+ }
3251
+ async consumeSource(connection, attempt) {
3252
+ const initial = await this.pollNativeSnapshot(attempt);
3253
+ if (initial == null || !this.isAttemptActive(attempt))
3254
+ return false;
3255
+ const decoderReady = this.awaitReady(attempt, initial.readyGeneration);
3256
+ try {
3257
+ await this.pumpSource(connection, attempt);
3258
+ } catch (cause) {
3259
+ this.observeReady(this.readNativeStats(), initial.readyGeneration);
3260
+ await this.stopSource(attempt);
3261
+ await decoderReady;
3262
+ if (!this.lifecycleController.signal.aborted)
3263
+ throw cause;
3264
+ return false;
3265
+ }
3266
+ if (this.lifecycleController.signal.aborted)
3267
+ return false;
3268
+ const status = this.lib.audioEndStream(this.engine, this.nativeStreamId);
3269
+ if (status !== 0) {
3270
+ const nativeError = this.snapshotError(this.readNativeStats());
3271
+ if (nativeError?.context.action === "decoder")
3272
+ throw nativeError;
3273
+ const context = { action: "end", status };
3274
+ throw new AudioStreamError(`Audio stream end failed: ${status}`, context);
3275
+ }
3276
+ if (!await decoderReady || this.lifecycleController.signal.aborted)
3277
+ return false;
3278
+ if (!await this.awaitEnded(attempt))
3279
+ return false;
3280
+ await this.stopSource(attempt);
3281
+ return !this.lifecycleController.signal.aborted;
3282
+ }
3283
+ async pumpSource(connection, attempt) {
3284
+ const source = connection.body;
3285
+ if (!this.isAttemptActive(attempt)) {
3286
+ await this.runBoundedAttemptCleanup(attempt);
3287
+ return;
3288
+ }
3289
+ const release = () => {
3290
+ if (attempt.sourceReleased)
3291
+ return;
3292
+ if (attempt.reader == null) {
3293
+ attempt.sourceReleased = true;
3294
+ return;
3295
+ }
3296
+ try {
3297
+ attempt.reader.releaseLock();
3298
+ attempt.sourceReleased = true;
3299
+ } catch {}
3300
+ };
3301
+ const next = () => attempt.reader == null ? attempt.iterator.next() : attempt.reader.read();
3302
+ attempt.sourceAcquisitionAttempted = true;
3303
+ const finishSourceAcquisition = this.beginResourceAcquisition(attempt);
3304
+ try {
3305
+ attempt.reader = isReadableStreamSource(source) ? source.getReader() : null;
3306
+ attempt.iterator = attempt.reader == null ? source[Symbol.asyncIterator]() : null;
3307
+ } catch (cause) {
3308
+ const context = { action: this.readAction };
3309
+ throw new ClassifiedAudioStreamError("Audio stream source failed", context, true, undefined, cause);
3310
+ } finally {
3311
+ finishSourceAcquisition();
3312
+ }
3313
+ if (!this.isAttemptActive(attempt)) {
3314
+ await this.runBoundedAttemptCleanup(attempt);
3315
+ return;
3316
+ }
3317
+ while (this.isAttemptActive(attempt)) {
3318
+ let result;
3319
+ try {
3320
+ result = await next();
3321
+ } catch (cause) {
3322
+ const context = { action: this.readAction };
3323
+ throw new ClassifiedAudioStreamError("Audio stream source failed", context, true, undefined, cause);
3324
+ }
3325
+ if (!this.isAttemptActive(attempt))
3326
+ return;
3327
+ if (result.done) {
3328
+ release();
3329
+ if (attempt.demuxer != null) {
3330
+ try {
3331
+ await this.processDemuxOutput(attempt.demuxer.flush(), attempt);
3332
+ } catch (cause) {
3333
+ if (cause instanceof AudioStreamError)
3334
+ throw cause;
3335
+ const context = { action: this.readAction };
3336
+ throw new ClassifiedAudioStreamError(cause instanceof Error ? cause.message : "Audio stream demuxer flush failed", context, true, undefined, cause);
3337
+ }
3338
+ }
3339
+ attempt.demuxerFinished = true;
3340
+ await this.runBoundedAttemptCleanup(attempt);
3341
+ return;
3342
+ }
3343
+ const chunk = result.value;
3344
+ if (!isUint8Array(chunk))
3345
+ throw new TypeError(INVALID_STREAM_CHUNK_MESSAGE);
3346
+ if (chunk.byteLength === 0) {
3347
+ await waitForDelay(0, attempt.controller.signal);
3348
+ continue;
3349
+ }
3350
+ if (attempt.demuxer == null) {
3351
+ await this.writeStreamChunk(chunk, attempt);
3352
+ continue;
3353
+ }
3354
+ try {
3355
+ await this.processDemuxOutput(attempt.demuxer.push(chunk), attempt);
3356
+ } catch (cause) {
3357
+ if (cause instanceof AudioStreamError)
3358
+ throw cause;
3359
+ throw new AudioStreamError(cause instanceof Error ? cause.message : "Audio stream demuxer failed", { action: "demuxer" }, cause);
3360
+ }
3361
+ }
3362
+ }
3363
+ async processDemuxOutput(outputs, attempt) {
3364
+ for (const output of outputs) {
3365
+ if (!this.isAttemptActive(attempt))
3366
+ return;
3367
+ if (output.type === "audio") {
3368
+ if (!isUint8Array(output.data)) {
3369
+ throw new AudioStreamError("Audio stream demuxer audio output must be a Uint8Array", {
3370
+ action: "demuxer"
3371
+ });
3372
+ }
3373
+ await this.writeStreamChunk(output.data, attempt);
3374
+ } else if (output.type === "metadata") {
3375
+ this.publishMetadata(output.metadata, attempt);
3376
+ } else {
3377
+ throw new AudioStreamError("Audio stream demuxer returned an invalid output", { action: "demuxer" });
3378
+ }
3379
+ }
3380
+ }
3381
+ async writeStreamChunk(chunk, attempt) {
3382
+ let offset = 0;
3383
+ while (offset < chunk.byteLength && this.isAttemptActive(attempt)) {
3384
+ const streamId = this.nativeStreamId;
3385
+ if (streamId == null)
3386
+ return;
3387
+ let accepted;
3388
+ try {
3389
+ accepted = this.lib.audioWriteStream(this.engine, streamId, chunk.subarray(offset));
3390
+ } catch (cause) {
3391
+ const context = { action: "write" };
3392
+ throw new AudioStreamError("Audio stream write failed", context, cause);
3393
+ }
3394
+ if (accepted < 0) {
3395
+ const context = { action: "write", status: accepted };
3396
+ throw new AudioStreamError(`Audio stream write failed: ${accepted}`, context);
3397
+ }
3398
+ if (accepted === 0) {
3399
+ if (await this.pollNativeSnapshot(attempt) == null)
3400
+ return;
3401
+ await waitForDelay(STREAM_POLL_INTERVAL_MS, attempt.controller.signal);
3402
+ continue;
3403
+ }
3404
+ offset += accepted;
3405
+ }
3406
+ }
3407
+ resolveConnection(connection, attempt) {
3408
+ const finishAcquisition = this.beginResourceAcquisition(attempt);
3409
+ try {
3410
+ const close = connection.close;
3411
+ if (close !== undefined && typeof close !== "function") {
3412
+ throw new TypeError("Audio stream connection close must be a function");
3413
+ }
3414
+ attempt.closeConnection = close == null ? null : () => close.call(connection);
3415
+ const info = connection.info;
3416
+ const body = connection.body;
3417
+ attempt.body = body;
3418
+ return { body, info };
3419
+ } finally {
3420
+ finishAcquisition();
3421
+ }
3422
+ }
3423
+ beginResourceAcquisition(attempt) {
3424
+ let resolve;
3425
+ const acquisition = new Promise((done) => {
3426
+ resolve = done;
3427
+ });
3428
+ attempt.resourceAcquisition = acquisition;
3429
+ return () => {
3430
+ if (attempt.resourceAcquisition === acquisition)
3431
+ attempt.resourceAcquisition = null;
3432
+ resolve();
3433
+ };
3434
+ }
3435
+ cleanupAttempt(attempt) {
3436
+ if (attempt.cleanupPromise != null)
3437
+ return attempt.cleanupPromise;
3438
+ let resolveCleanup;
3439
+ let rejectCleanup;
3440
+ const cleanup = new Promise((resolve, reject) => {
3441
+ resolveCleanup = resolve;
3442
+ rejectCleanup = reject;
3443
+ });
3444
+ attempt.cleanupPromise = cleanup;
3445
+ this.performAttemptCleanup(attempt).then(resolveCleanup, rejectCleanup);
3446
+ const clearCleanup = () => {
3447
+ if (attempt.cleanupPromise === cleanup)
3448
+ attempt.cleanupPromise = null;
3449
+ };
3450
+ cleanup.then(clearCleanup, clearCleanup);
3451
+ return cleanup;
3452
+ }
3453
+ async performAttemptCleanup(attempt) {
3454
+ if (attempt.resourceAcquisition != null)
3455
+ await attempt.resourceAcquisition;
3456
+ const pending = [];
3457
+ const reason = createAbortError();
3458
+ if (!attempt.demuxerFinished && !attempt.demuxerAborted && attempt.demuxer != null) {
3459
+ attempt.demuxerAborted = true;
3460
+ try {
3461
+ attempt.demuxer.abort?.(reason);
3462
+ } catch {}
3463
+ }
3464
+ if (!attempt.sourceReleased) {
3465
+ if (attempt.reader != null) {
3466
+ attempt.sourceReleased = true;
3467
+ try {
3468
+ const result = attempt.reader.cancel(reason);
3469
+ try {
3470
+ attempt.reader.releaseLock();
3471
+ } catch {}
3472
+ pending.push(result);
3473
+ } catch {}
3474
+ } else if (attempt.iterator != null) {
3475
+ attempt.sourceReleased = true;
3476
+ try {
3477
+ pending.push(Promise.resolve(attempt.iterator.return?.()));
3478
+ } catch {}
3479
+ } else if (attempt.body != null && !attempt.sourceAcquisitionAttempted) {
3480
+ attempt.sourceReleased = true;
3481
+ try {
3482
+ if (isReadableStreamSource(attempt.body)) {
3483
+ pending.push(attempt.body.cancel(reason));
3484
+ } else if (isAsyncIterableSource(attempt.body)) {
3485
+ attempt.iterator = attempt.body[Symbol.asyncIterator]();
3486
+ pending.push(Promise.resolve(attempt.iterator.return?.()));
3487
+ }
3488
+ } catch {}
3489
+ }
3490
+ }
3491
+ if (!attempt.connectionClosed && attempt.closeConnection != null) {
3492
+ attempt.connectionClosed = true;
3493
+ try {
3494
+ pending.push(Promise.resolve(attempt.closeConnection()));
3495
+ } catch {}
3496
+ }
3497
+ await Promise.allSettled(pending);
3498
+ }
3499
+ runBoundedAttemptCleanup(attempt) {
3500
+ if (this.pendingCleanup != null)
3501
+ return this.pendingCleanup;
3502
+ let resolveCleanup;
3503
+ let rejectCleanup;
3504
+ const pendingCleanup = new Promise((resolve, reject) => {
3505
+ resolveCleanup = resolve;
3506
+ rejectCleanup = reject;
3507
+ });
3508
+ this.pendingCleanup = pendingCleanup;
3509
+ const cleanup = runBoundedCleanup(() => this.cleanupAttempt(attempt));
3510
+ cleanup.then(resolveCleanup, rejectCleanup);
3511
+ const clearCleanup = () => {
3512
+ if (this.pendingCleanup === pendingCleanup)
3513
+ this.pendingCleanup = null;
3514
+ };
3515
+ pendingCleanup.then(clearCleanup, clearCleanup);
3516
+ return pendingCleanup;
3517
+ }
3518
+ async retry(error, attempt, phase, cleanEnd = false) {
3519
+ if (this.lifecycleController.signal.aborted)
3520
+ return false;
3521
+ const reconnect = this.options.reconnect;
3522
+ const retryable = phase == null || !(error instanceof ClassifiedAudioStreamError) || error.retryable;
3523
+ if (reconnect == null || this.consecutiveReconnectAttempts >= reconnect.maxRetries) {
3524
+ if (cleanEnd)
3525
+ await this.finish(NativeAudioStreamCloseReason.PreserveNativeTerminal);
3526
+ else
3527
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error);
3528
+ return false;
3529
+ }
3530
+ let retryDelayMs = error instanceof ClassifiedAudioStreamError ? error.retryAfterMs : undefined;
3531
+ if (phase != null && reconnect.retry != null) {
3532
+ let decision;
3533
+ try {
3534
+ decision = reconnect.retry(error, {
3535
+ attempt: this.consecutiveReconnectAttempts + 1,
3536
+ maxRetries: reconnect.maxRetries,
3537
+ phase
3538
+ });
3539
+ if (decision !== false && (decision == null || typeof decision !== "object")) {
3540
+ throw new TypeError("Audio stream retry policy must return false or a retry decision");
3541
+ }
3542
+ } catch (cause) {
3543
+ await this.finish(NativeAudioStreamCloseReason.TransportError, new AudioStreamError("Audio stream retry policy failed", { action: "source" }, cause));
3544
+ return false;
3545
+ }
3546
+ if (decision === false) {
3547
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error);
3548
+ return false;
3549
+ }
3550
+ if (this.lifecycleController.signal.aborted)
3551
+ return false;
3552
+ let policyDelayMs;
3553
+ try {
3554
+ policyDelayMs = decision.delayMs;
3555
+ } catch (cause) {
3556
+ await this.finish(NativeAudioStreamCloseReason.TransportError, new AudioStreamError("Audio stream retry policy failed", { action: "source" }, cause));
3557
+ return false;
3558
+ }
3559
+ if (policyDelayMs !== undefined && (!Number.isFinite(policyDelayMs) || !Number.isInteger(policyDelayMs) || policyDelayMs < 0)) {
3560
+ await this.finish(NativeAudioStreamCloseReason.TransportError, new AudioStreamError("Audio stream retry delay must be a finite non-negative integer", {
3561
+ action: "source"
3562
+ }));
3563
+ return false;
3564
+ }
3565
+ if (policyDelayMs !== undefined)
3566
+ retryDelayMs = Math.min(reconnect.maxDelayMs, policyDelayMs);
3567
+ } else if (!retryable) {
3568
+ await this.finish(NativeAudioStreamCloseReason.TransportError, error);
3569
+ return false;
3570
+ }
3571
+ if (retryDelayMs !== undefined)
3572
+ retryDelayMs = Math.min(reconnect.maxDelayMs, retryDelayMs);
3573
+ await this.cleanupAttempt(attempt);
3574
+ if (this.lifecycleController.signal.aborted)
3575
+ return false;
3576
+ if (this.nativeStreamId != null) {
3577
+ const nativeError = this.snapshotError(this.readNativeStats());
3578
+ if (nativeError != null) {
3579
+ const reason = nativeError.context.action === "decoder" ? NativeAudioStreamCloseReason.PreserveNativeTerminal : NativeAudioStreamCloseReason.TransportError;
3580
+ await this.finish(reason, nativeError);
3581
+ return false;
3582
+ }
3583
+ const restartStatus = this.lib.audioRestartStream(this.engine, this.nativeStreamId);
3584
+ if (restartStatus !== 0) {
3585
+ const restartContext = { action: "restart", status: restartStatus };
3586
+ await this.finish(NativeAudioStreamCloseReason.TransportError, new AudioStreamError("Audio stream restart failed during reconnect", restartContext));
3587
+ return false;
3588
+ }
3589
+ this.readNativeStats();
3590
+ }
3591
+ this.reconnectAttempts += 1;
3592
+ this.consecutiveReconnectAttempts += 1;
3593
+ const delayMs = retryDelayMs ?? Math.min(reconnect.maxDelayMs, reconnect.initialDelayMs * reconnect.backoffFactor ** (this.consecutiveReconnectAttempts - 1));
3594
+ if (this.exposed) {
3595
+ this.emitAsync("reconnecting", {
3596
+ attempt: this.consecutiveReconnectAttempts,
3597
+ delayMs,
3598
+ maxRetries: reconnect.maxRetries,
3599
+ error
3600
+ });
3601
+ }
3602
+ return waitForDelay(delayMs, this.lifecycleController.signal).then(() => true).catch(() => false);
3603
+ }
3604
+ async awaitReady(attempt, previousGeneration) {
3605
+ while (this.isAttemptActive(attempt)) {
3606
+ const stats = await this.pollNativeSnapshot(attempt);
3607
+ if (stats == null)
3608
+ return false;
3609
+ if (this.observeReady(stats, previousGeneration))
3610
+ return true;
3611
+ if (!await waitForPoll(attempt.controller.signal))
3612
+ return false;
3613
+ }
3614
+ return false;
3615
+ }
3616
+ observeReady(stats, previousGeneration) {
3617
+ if (stats == null || stats.readyGeneration === previousGeneration)
3618
+ return false;
3619
+ this.consecutiveReconnectAttempts = 0;
3620
+ this.setupResolve();
3621
+ return true;
3622
+ }
3623
+ async awaitEnded(attempt) {
3624
+ while (this.isAttemptActive(attempt)) {
3625
+ const stats = await this.pollNativeSnapshot(attempt);
3626
+ if (stats == null)
3627
+ return false;
3628
+ if (stats.state === NativeAudioStreamState.Ended)
3629
+ return true;
3630
+ if (!await waitForPoll(attempt.controller.signal))
3631
+ return false;
3632
+ }
3633
+ return false;
3634
+ }
3635
+ async pollNativeSnapshot(attempt) {
3636
+ if (!this.isAttemptActive(attempt))
3637
+ return null;
3638
+ const stats = this.readNativeStats();
3639
+ const error = this.snapshotError(stats);
3640
+ if (error != null) {
3641
+ const reason = error.context.action === "decoder" ? NativeAudioStreamCloseReason.PreserveNativeTerminal : NativeAudioStreamCloseReason.TransportError;
3642
+ await this.finish(reason, error);
3643
+ return null;
3644
+ }
3645
+ return stats;
3646
+ }
3647
+ snapshotError(stats) {
3648
+ if (stats == null)
3649
+ return new AudioStreamError("Audio stream stats failed", { action: "stats" });
3650
+ if (NativeAudioStreamStateNames[stats.state] == null) {
3651
+ return new AudioStreamError(`Unknown native audio stream state: ${stats.state}`, { action: "stats" });
3652
+ }
3653
+ if (stats.state !== NativeAudioStreamState.Failed && stats.state !== NativeAudioStreamState.Cancelled)
3654
+ return null;
3655
+ const context = { action: "decoder", errorCode: stats.errorCode };
3656
+ return new AudioStreamError(stats.state === NativeAudioStreamState.Failed ? `Audio stream decoder failed: ${stats.errorCode}` : "Audio stream was cancelled by the decoder", context);
3657
+ }
3658
+ async finish(reason, error, context) {
3659
+ if (this.lifecycleController.signal.aborted)
3660
+ return;
3661
+ if (error instanceof AudioStreamError)
3662
+ context = error.context;
3663
+ this.lifecycleController.abort();
3664
+ this.terminalError = error ?? null;
3665
+ const cleanup = this.stopSource();
3666
+ const closeStatus = this.closeNativeStream(reason);
3667
+ if (error == null && closeStatus !== 0) {
3668
+ context = { action: "destroy", status: closeStatus };
3669
+ error = new AudioStreamError("Audio stream destroy failed after end", context);
3670
+ this.terminalError = error;
3671
+ }
3672
+ await cleanup;
3673
+ if (error != null)
3674
+ this.setupReject(error);
3675
+ else
3676
+ this.setupResolve();
3677
+ if (closeStatus === 0)
3678
+ this.removeOwner();
3679
+ if (!this.disposed && this.exposed) {
3680
+ if (error != null)
3681
+ this.emitTerminal("error", error, context);
3682
+ else
3683
+ this.emitTerminal("ended");
3684
+ } else if (error != null && !this.disposed)
3685
+ this.closedResolve();
3686
+ }
3687
+ publishMetadata(metadata, attempt) {
3688
+ if (!this.isAttemptActive(attempt) || Object.is(this.metadata, metadata))
3689
+ return;
3690
+ this.metadata = metadata;
3691
+ if (!this.exposed) {
3692
+ this.pendingMetadataEvent = true;
3693
+ return;
3694
+ }
3695
+ this.emitMetadata();
3696
+ }
3697
+ emitMetadata() {
3698
+ if (this.metadataEventScheduled)
3699
+ return;
3700
+ this.metadataEventScheduled = true;
3701
+ setTimeout(() => {
3702
+ this.metadataEventScheduled = false;
3703
+ if (!this.disposed)
3704
+ EventEmitter.prototype.emit.call(this, "metadata", this.metadata);
3705
+ }, 0);
3706
+ }
3707
+ emitAsync(event, ...args) {
3708
+ setTimeout(() => EventEmitter.prototype.emit.call(this, event, ...args), 0);
3709
+ }
3710
+ emitTerminal(event, ...args) {
3711
+ if (this.terminalEventScheduled)
3712
+ return;
3713
+ this.terminalEventScheduled = true;
3714
+ setTimeout(() => {
3715
+ try {
3716
+ EventEmitter.prototype.emit.call(this, event, ...args);
3717
+ } finally {
3718
+ this.closedResolve();
3719
+ }
3720
+ }, 0);
3721
+ }
3722
+ isAttemptActive(attempt) {
3723
+ return !this.lifecycleController.signal.aborted && this.activeAttempt === attempt;
3724
+ }
3725
+ stopSource(attempt = this.activeAttempt) {
3726
+ if (attempt == null)
3727
+ return this.pendingCleanup ?? Promise.resolve();
3728
+ if (this.activeAttempt === attempt)
3729
+ this.activeAttempt = null;
3730
+ attempt.controller.abort();
3731
+ return this.runBoundedAttemptCleanup(attempt);
3732
+ }
3733
+ closeNativeStream(reason) {
3734
+ const streamId = this.nativeStreamId;
3735
+ if (streamId == null)
3736
+ return 0;
3737
+ const result = this.lib.audioCloseStream(this.engine, streamId, reason);
3738
+ if (result.status !== 0 || result.stats == null)
3739
+ return result.status === 0 ? -1 : result.status;
3740
+ this.nativeStats = result.stats;
3741
+ this.nativeStreamId = null;
3742
+ return 0;
3743
+ }
3744
+ readNativeStats() {
3745
+ if (this.nativeStreamId == null)
3746
+ return this.nativeStats;
3747
+ const stats = this.lib.audioGetStreamStats(this.engine, this.nativeStreamId);
3748
+ if (stats != null)
3749
+ this.nativeStats = stats;
3750
+ return stats;
3751
+ }
3752
+ toPublicStats() {
3753
+ const stats = this.nativeStats;
3754
+ const sampleRate = stats?.sampleRate ?? 0;
3755
+ const bufferedFrames = stats?.bufferedFrames ?? 0;
3756
+ return {
3757
+ state: this.state,
3758
+ sampleRate,
3759
+ channels: stats?.channels ?? 0,
3760
+ bufferedFrames,
3761
+ capacityFrames: stats?.capacityFrames ?? 0,
3762
+ bufferedDurationMs: sampleRate === 0 ? 0 : bufferedFrames * 1000 / sampleRate,
3763
+ bytesReceived: stats?.bytesReceived ?? 0n,
3764
+ framesDecoded: stats?.framesDecoded ?? 0n,
3765
+ framesPlayed: stats?.framesPlayed ?? 0n,
3766
+ underruns: stats?.underruns ?? 0,
3767
+ reconnectAttempts: this.reconnectAttempts
3768
+ };
3769
+ }
3770
+ removeOwner() {
3771
+ this.options.signal?.removeEventListener("abort", this.overallAbortListener);
3772
+ this.removeFromOwner();
3773
+ }
3774
+ }
2534
3775
 
2535
3776
  class Audio extends EventEmitter {
2536
3777
  static create(options = {}) {
2537
- return new Audio(resolveRenderLib(), options);
3778
+ let lib;
3779
+ try {
3780
+ lib = resolveRenderLib();
3781
+ } catch (cause) {
3782
+ throw new AudioInitializationError("resolveRenderLib", "Failed to resolve the native audio library", undefined, cause);
3783
+ }
3784
+ return new Audio(lib, options);
2538
3785
  }
3786
+ sampleRate;
2539
3787
  lib;
2540
3788
  defaultStartOptions;
2541
3789
  engine = null;
2542
3790
  groups = new Map;
3791
+ streams = new Set;
2543
3792
  playbackStarted = false;
2544
3793
  mixerStarted = false;
3794
+ disposing = false;
2545
3795
  constructor(lib, options) {
2546
3796
  super();
2547
3797
  this.lib = lib;
2548
3798
  this.defaultStartOptions = options.startOptions;
3799
+ const normalizedSampleRate = options.sampleRate == null || !Number.isFinite(options.sampleRate) ? 0 : Math.min(MAX_U32, Math.max(0, Math.trunc(options.sampleRate)));
3800
+ this.sampleRate = normalizedSampleRate || DEFAULT_AUDIO_SAMPLE_RATE;
2549
3801
  const createOptions = options.sampleRate == null && options.playbackChannels == null ? undefined : {
2550
- sampleRate: options.sampleRate == null ? undefined : Math.max(0, Math.trunc(options.sampleRate)),
3802
+ sampleRate: options.sampleRate == null ? undefined : normalizedSampleRate,
2551
3803
  playbackChannels: options.playbackChannels == null ? undefined : Math.max(0, Math.trunc(options.playbackChannels))
2552
3804
  };
2553
3805
  this.engine = this.lib.createAudioEngine(createOptions);
2554
3806
  if (!this.engine) {
2555
- this.emitError("createAudioEngine", undefined, "Audio createAudioEngine returned null");
2556
- return;
3807
+ throw new AudioInitializationError("createAudioEngine", "Audio createAudioEngine returned null");
2557
3808
  }
2558
3809
  if (options.autoStart ?? false) {
2559
- this.start(this.defaultStartOptions);
3810
+ const status = this.lib.audioStart(this.engine, this.defaultStartOptions);
3811
+ if (status !== 0) {
3812
+ this.throwAfterInitializationCleanup(new AudioInitializationError("start", `Audio auto-start failed: ${status}`, status));
3813
+ }
3814
+ this.playbackStarted = true;
3815
+ this.mixerStarted = true;
2560
3816
  }
2561
3817
  }
3818
+ throwAfterInitializationCleanup(error) {
3819
+ const engine2 = this.engine;
3820
+ this.engine = null;
3821
+ if (engine2)
3822
+ this.lib.destroyAudioEngine(engine2);
3823
+ throw error;
3824
+ }
2562
3825
  emitError(action, status, message, cause) {
2563
3826
  const error = message ? new Error(message) : statusToError(action, status ?? -1);
2564
3827
  if (cause)
@@ -2697,6 +3960,74 @@ class Audio extends EventEmitter {
2697
3960
  }
2698
3961
  return result.voiceId;
2699
3962
  }
3963
+ async playStream(source, options = {}) {
3964
+ const urlOptions = options;
3965
+ if (urlOptions.request !== undefined || urlOptions.reconnect !== undefined || urlOptions.metadataEncoding !== undefined || urlOptions.contentTypePolicy !== undefined) {
3966
+ return Promise.reject(new TypeError("request, reconnect, metadataEncoding, and contentTypePolicy options are only supported by playStreamUrl()"));
3967
+ }
3968
+ if (!isReadableStreamSource(source) && !isAsyncIterableSource(source)) {
3969
+ return Promise.reject(new TypeError("Audio stream source must be a ReadableStream or AsyncIterable"));
3970
+ }
3971
+ const { demuxer, ...streamOptions } = options;
3972
+ const connector = {
3973
+ async connect() {
3974
+ return { body: source, info: undefined };
3975
+ }
3976
+ };
3977
+ return this.openStream(connector, demuxer == null ? undefined : () => demuxer(), streamOptions, "source");
3978
+ }
3979
+ async playStreamUrl(source, options = {}) {
3980
+ if (options.demuxer !== undefined) {
3981
+ throw new TypeError("demuxer is only supported by playStream() and playStreamSource()");
3982
+ }
3983
+ if (typeof source !== "string" && Object.prototype.toString.call(source) !== "[object URL]") {
3984
+ return Promise.reject(new TypeError("Audio stream URL source must be a string or URL"));
3985
+ }
3986
+ const { request, metadataEncoding, reconnect, format, contentTypePolicy, ...streamOptions } = options;
3987
+ const resolvedFormat = resolveAudioStreamFormat(format);
3988
+ const resolvedContentTypePolicy = resolveContentTypePolicy(contentTypePolicy, options);
3989
+ const encoding = resolveMetadataEncoding(metadataEncoding);
3990
+ const connector = createAudioStreamUrlConnector(source, resolveAudioStreamRequest(request), resolvedFormat, resolvedContentTypePolicy);
3991
+ return this.openStream(connector, (info) => {
3992
+ try {
3993
+ return selectAudioStreamDemuxer({ headers: info.headers, metadataEncoding: encoding });
3994
+ } catch (cause) {
3995
+ throw new AudioStreamError(cause instanceof Error ? cause.message : "Invalid audio stream metadata response", { action: "response", status: info.status }, cause);
3996
+ }
3997
+ }, { ...streamOptions, reconnect, format: resolvedFormat }, "fetch");
3998
+ }
3999
+ async playStreamSource(connector, options = {}) {
4000
+ const urlOptions = options;
4001
+ if (urlOptions.request !== undefined || urlOptions.metadataEncoding !== undefined || urlOptions.contentTypePolicy !== undefined) {
4002
+ return Promise.reject(new TypeError("request, metadataEncoding, and contentTypePolicy are only supported by playStreamUrl()"));
4003
+ }
4004
+ const { demuxer, ...streamOptions } = options;
4005
+ return this.openStream(connector, demuxer, streamOptions, "source");
4006
+ }
4007
+ async openStream(connector, demuxer, options, readAction) {
4008
+ const engine2 = this.engine;
4009
+ if (!engine2)
4010
+ throw new Error("Audio engine unavailable during stream playback");
4011
+ const resolvedConnector = resolveAudioStreamConnector(connector);
4012
+ let stream;
4013
+ stream = createAudioStream({
4014
+ lib: this.lib,
4015
+ engine: engine2,
4016
+ connector: resolvedConnector,
4017
+ demuxer,
4018
+ options,
4019
+ readAction,
4020
+ removeFromOwner: () => this.streams.delete(stream)
4021
+ });
4022
+ this.streams.add(stream);
4023
+ try {
4024
+ await openAudioStream(stream);
4025
+ return stream;
4026
+ } catch (error) {
4027
+ stream.dispose();
4028
+ throw error;
4029
+ }
4030
+ }
2700
4031
  stopVoice(voice) {
2701
4032
  const engine2 = this.engine;
2702
4033
  if (!engine2) {
@@ -2864,15 +4195,22 @@ class Audio extends EventEmitter {
2864
4195
  return stats;
2865
4196
  }
2866
4197
  dispose() {
2867
- if (!this.engine)
4198
+ if (!this.engine || this.disposing)
2868
4199
  return;
2869
- if (this.mixerStarted) {
2870
- this.stop();
4200
+ this.disposing = true;
4201
+ try {
4202
+ for (const stream of [...this.streams])
4203
+ stream.dispose();
4204
+ if (this.mixerStarted) {
4205
+ this.stop();
4206
+ }
4207
+ this.groups.clear();
4208
+ this.lib.destroyAudioEngine(this.engine);
4209
+ this.engine = null;
4210
+ this.emit("disposed");
4211
+ } finally {
4212
+ this.disposing = false;
2871
4213
  }
2872
- this.groups.clear();
2873
- this.lib.destroyAudioEngine(this.engine);
2874
- this.engine = null;
2875
- this.emit("disposed");
2876
4214
  }
2877
4215
  }
2878
4216
  function setupAudio(options = {}) {
@@ -3435,10 +4773,10 @@ class LineNumberRenderable extends Renderable {
3435
4773
  return;
3436
4774
  if (this.target) {
3437
4775
  this.target.off("line-info-change", this.handleLineInfoChange);
3438
- super.remove(this.target.id);
4776
+ super.remove(this.target);
3439
4777
  }
3440
4778
  if (this.gutter) {
3441
- super.remove(this.gutter.id);
4779
+ super.remove(this.gutter);
3442
4780
  this.gutter = null;
3443
4781
  }
3444
4782
  this.target = target;
@@ -3467,18 +4805,18 @@ class LineNumberRenderable extends Renderable {
3467
4805
  }
3468
4806
  return -1;
3469
4807
  }
3470
- remove(id) {
4808
+ remove(child) {
3471
4809
  if (this._isDestroying) {
3472
- super.remove(id);
4810
+ super.remove(child);
3473
4811
  return;
3474
4812
  }
3475
- if (this.gutter && id === this.gutter.id) {
4813
+ if (this.gutter && child === this.gutter) {
3476
4814
  throw new Error("LineNumberRenderable: Cannot remove gutter directly.");
3477
4815
  }
3478
- if (this.target && id === this.target.id) {
4816
+ if (this.target && child === this.target) {
3479
4817
  throw new Error("LineNumberRenderable: Cannot remove target directly. Use clearTarget() instead.");
3480
4818
  }
3481
- super.remove(id);
4819
+ super.remove(child);
3482
4820
  }
3483
4821
  destroyRecursively() {
3484
4822
  this._isDestroying = true;
@@ -3492,11 +4830,11 @@ class LineNumberRenderable extends Renderable {
3492
4830
  clearTarget() {
3493
4831
  if (this.target) {
3494
4832
  this.target.off("line-info-change", this.handleLineInfoChange);
3495
- super.remove(this.target.id);
4833
+ super.remove(this.target);
3496
4834
  this.target = null;
3497
4835
  }
3498
4836
  if (this.gutter) {
3499
- super.remove(this.gutter.id);
4837
+ super.remove(this.gutter);
3500
4838
  this.gutter = null;
3501
4839
  }
3502
4840
  }
@@ -4209,11 +5547,11 @@ class DiffRenderable extends Renderable {
4209
5547
  buildErrorView() {
4210
5548
  this.flexDirection = "column";
4211
5549
  if (this.leftSide && this.leftSideAdded) {
4212
- super.remove(this.leftSide.id);
5550
+ super.remove(this.leftSide);
4213
5551
  this.leftSideAdded = false;
4214
5552
  }
4215
5553
  if (this.rightSide && this.rightSideAdded) {
4216
- super.remove(this.rightSide.id);
5554
+ super.remove(this.rightSide);
4217
5555
  this.rightSideAdded = false;
4218
5556
  }
4219
5557
  const errorMessage = `Error parsing diff: ${this._parseError?.message || "Unknown error"}
@@ -4361,13 +5699,13 @@ class DiffRenderable extends Renderable {
4361
5699
  if (this.errorTextRenderable) {
4362
5700
  const errorTextIndex = this.getChildren().indexOf(this.errorTextRenderable);
4363
5701
  if (errorTextIndex !== -1) {
4364
- super.remove(this.errorTextRenderable.id);
5702
+ super.remove(this.errorTextRenderable);
4365
5703
  }
4366
5704
  }
4367
5705
  if (this.errorCodeRenderable) {
4368
5706
  const errorCodeIndex = this.getChildren().indexOf(this.errorCodeRenderable);
4369
5707
  if (errorCodeIndex !== -1) {
4370
- super.remove(this.errorCodeRenderable.id);
5708
+ super.remove(this.errorCodeRenderable);
4371
5709
  }
4372
5710
  }
4373
5711
  const contentLines = [];
@@ -4442,7 +5780,7 @@ class DiffRenderable extends Renderable {
4442
5780
  this.attachLineInfoListeners();
4443
5781
  this.createOrUpdateSide("left", codeRenderable, lineColors, lineSigns, lineNumbers, new Set, "100%");
4444
5782
  if (this.rightSide && this.rightSideAdded) {
4445
- super.remove(this.rightSide.id);
5783
+ super.remove(this.rightSide);
4446
5784
  this.rightSideAdded = false;
4447
5785
  }
4448
5786
  }
@@ -4453,13 +5791,13 @@ class DiffRenderable extends Renderable {
4453
5791
  if (this.errorTextRenderable) {
4454
5792
  const errorTextIndex = this.getChildren().indexOf(this.errorTextRenderable);
4455
5793
  if (errorTextIndex !== -1) {
4456
- super.remove(this.errorTextRenderable.id);
5794
+ super.remove(this.errorTextRenderable);
4457
5795
  }
4458
5796
  }
4459
5797
  if (this.errorCodeRenderable) {
4460
5798
  const errorCodeIndex = this.getChildren().indexOf(this.errorCodeRenderable);
4461
5799
  if (errorCodeIndex !== -1) {
4462
- super.remove(this.errorCodeRenderable.id);
5800
+ super.remove(this.errorCodeRenderable);
4463
5801
  }
4464
5802
  }
4465
5803
  const leftLogicalLines = [];
@@ -6770,6 +8108,83 @@ var Nt = d.parseInline;
6770
8108
  var Ft = b.parse;
6771
8109
  var jt = x.lex;
6772
8110
 
8111
+ // src/renderables/text-table-width.ts
8112
+ function comparePriority(leftGrowth, leftCapacity, rightGrowth, rightCapacity) {
8113
+ const left = leftGrowth * leftGrowth * rightCapacity;
8114
+ const right = rightGrowth * rightGrowth * leftCapacity;
8115
+ if (Number.isSafeInteger(left) && Number.isSafeInteger(right)) {
8116
+ return left < right ? -1 : left > right ? 1 : 0;
8117
+ }
8118
+ const exactLeft = BigInt(leftGrowth) * BigInt(leftGrowth) * BigInt(rightCapacity);
8119
+ const exactRight = BigInt(rightGrowth) * BigInt(rightGrowth) * BigInt(leftCapacity);
8120
+ return exactLeft < exactRight ? -1 : exactLeft > exactRight ? 1 : 0;
8121
+ }
8122
+ function allocateProportionalColumnWidths(widths, targetWidth, minWidth) {
8123
+ const baseWidths = widths.map((width) => Math.max(minWidth, Math.floor(width)));
8124
+ const totalBaseWidth = baseWidths.reduce((sum, width) => sum + width, 0);
8125
+ const capacity = baseWidths.map((width) => width - minWidth);
8126
+ const growth = new Array(baseWidths.length).fill(0);
8127
+ const available = Math.min(Math.max(0, targetWidth - minWidth * baseWidths.length), totalBaseWidth - minWidth * baseWidths.length);
8128
+ if (available === 0)
8129
+ return growth.map(() => minWidth);
8130
+ if (available === capacity.reduce((sum, width) => sum + width, 0))
8131
+ return baseWidths;
8132
+ const weights = capacity.map(Math.sqrt);
8133
+ const active = capacity.map((width, idx) => ({ idx, width, weight: weights[idx] })).filter((column) => column.width > 0).sort((a, b2) => a.weight - b2.weight);
8134
+ if (active.length === capacity.length && capacity.every((width) => width === capacity[0])) {
8135
+ const sharedGrowth = Math.floor(available / capacity.length);
8136
+ const remainder = available % capacity.length;
8137
+ return growth.map((_2, idx) => minWidth + sharedGrowth + (idx < remainder ? 1 : 0));
8138
+ }
8139
+ let remaining = available;
8140
+ let totalWeight = active.reduce((sum, column) => sum + column.weight, 0);
8141
+ for (const column of active) {
8142
+ if (remaining / totalWeight <= column.weight)
8143
+ break;
8144
+ growth[column.idx] = column.width;
8145
+ remaining -= column.width;
8146
+ totalWeight -= column.weight;
8147
+ }
8148
+ const level = remaining / totalWeight;
8149
+ for (const column of active) {
8150
+ if (growth[column.idx] === column.width)
8151
+ continue;
8152
+ growth[column.idx] = Math.min(column.width, Math.floor(level * column.weight));
8153
+ }
8154
+ let allocatedGrowth = growth.reduce((sum, width) => sum + width, 0);
8155
+ while (allocatedGrowth > available) {
8156
+ let worstIdx = -1;
8157
+ for (let idx = 0;idx < baseWidths.length; idx++) {
8158
+ if (growth[idx] === 0)
8159
+ continue;
8160
+ const comparison = worstIdx === -1 ? 1 : comparePriority(growth[idx], capacity[idx], growth[worstIdx], capacity[worstIdx]);
8161
+ if (comparison > 0 || comparison === 0 && idx > worstIdx) {
8162
+ worstIdx = idx;
8163
+ }
8164
+ }
8165
+ if (worstIdx === -1)
8166
+ break;
8167
+ growth[worstIdx] -= 1;
8168
+ allocatedGrowth -= 1;
8169
+ }
8170
+ while (allocatedGrowth < available) {
8171
+ let bestIdx = -1;
8172
+ for (let idx = 0;idx < baseWidths.length; idx++) {
8173
+ if (growth[idx] >= capacity[idx])
8174
+ continue;
8175
+ const comparison = bestIdx === -1 ? -1 : comparePriority(growth[idx] + 1, capacity[idx], growth[bestIdx] + 1, capacity[bestIdx]);
8176
+ if (comparison < 0) {
8177
+ bestIdx = idx;
8178
+ }
8179
+ }
8180
+ if (bestIdx === -1)
8181
+ break;
8182
+ growth[bestIdx] += 1;
8183
+ allocatedGrowth += 1;
8184
+ }
8185
+ return growth.map((width) => width + minWidth);
8186
+ }
8187
+
6773
8188
  // src/renderables/TextTable.ts
6774
8189
  var MEASURE_HEIGHT = 1e4;
6775
8190
 
@@ -7323,54 +8738,7 @@ class TextTableRenderable extends Renderable {
7323
8738
  }
7324
8739
  fitColumnWidthsProportional(widths, targetContentWidth) {
7325
8740
  const minWidth = 1 + this.getHorizontalCellPadding();
7326
- const hardMinWidths = new Array(widths.length).fill(minWidth);
7327
- const baseWidths = widths.map((width) => Math.max(1, Math.floor(width)));
7328
- const preferredMinWidths = baseWidths.map((width) => Math.min(width, minWidth + 1));
7329
- const preferredMinTotal = preferredMinWidths.reduce((sum, width) => sum + width, 0);
7330
- const floorWidths = preferredMinTotal <= targetContentWidth ? preferredMinWidths : hardMinWidths;
7331
- const floorTotal = floorWidths.reduce((sum, width) => sum + width, 0);
7332
- const clampedTarget = Math.max(floorTotal, targetContentWidth);
7333
- const totalBaseWidth = baseWidths.reduce((sum, width) => sum + width, 0);
7334
- if (totalBaseWidth <= clampedTarget) {
7335
- return baseWidths;
7336
- }
7337
- const shrinkable = baseWidths.map((width, idx) => width - floorWidths[idx]);
7338
- const totalShrinkable = shrinkable.reduce((sum, value) => sum + value, 0);
7339
- if (totalShrinkable <= 0) {
7340
- return [...floorWidths];
7341
- }
7342
- const targetShrink = totalBaseWidth - clampedTarget;
7343
- const integerShrink = new Array(baseWidths.length).fill(0);
7344
- const fractions = new Array(baseWidths.length).fill(0);
7345
- let usedShrink = 0;
7346
- for (let idx = 0;idx < baseWidths.length; idx++) {
7347
- if (shrinkable[idx] <= 0)
7348
- continue;
7349
- const exact = shrinkable[idx] / totalShrinkable * targetShrink;
7350
- const whole = Math.min(shrinkable[idx], Math.floor(exact));
7351
- integerShrink[idx] = whole;
7352
- fractions[idx] = exact - whole;
7353
- usedShrink += whole;
7354
- }
7355
- let remainingShrink = targetShrink - usedShrink;
7356
- while (remainingShrink > 0) {
7357
- let bestIdx = -1;
7358
- let bestFraction = -1;
7359
- for (let idx = 0;idx < baseWidths.length; idx++) {
7360
- if (shrinkable[idx] - integerShrink[idx] <= 0)
7361
- continue;
7362
- if (fractions[idx] > bestFraction) {
7363
- bestFraction = fractions[idx];
7364
- bestIdx = idx;
7365
- }
7366
- }
7367
- if (bestIdx === -1)
7368
- break;
7369
- integerShrink[bestIdx] += 1;
7370
- fractions[bestIdx] = 0;
7371
- remainingShrink -= 1;
7372
- }
7373
- return baseWidths.map((width, idx) => Math.max(floorWidths[idx], width - integerShrink[idx]));
8741
+ return allocateProportionalColumnWidths(widths, targetContentWidth, minWidth);
7374
8742
  }
7375
8743
  fitColumnWidthsBalanced(widths, targetContentWidth) {
7376
8744
  const minWidth = 1 + this.getHorizontalCellPadding();
@@ -10375,12 +11743,19 @@ class ScrollBoxRenderable extends BoxRenderable {
10375
11743
  insertBefore(obj, anchor) {
10376
11744
  return this.content.insertBefore(obj, anchor);
10377
11745
  }
10378
- remove(id) {
10379
- this.content.remove(id);
11746
+ remove(child) {
11747
+ if (child.parent === this) {
11748
+ super.remove(child);
11749
+ return;
11750
+ }
11751
+ this.content.remove(child);
10380
11752
  }
10381
11753
  getChildren() {
10382
11754
  return this.content.getChildren();
10383
11755
  }
11756
+ getRenderable(id) {
11757
+ return this.content.getRenderable(id);
11758
+ }
10384
11759
  onMouseEvent(event) {
10385
11760
  if (event.type === "scroll") {
10386
11761
  let dir = event.scroll?.direction;
@@ -10701,6 +12076,7 @@ class SelectRenderable extends Renderable {
10701
12076
  _showScrollIndicator;
10702
12077
  _wrapSelection;
10703
12078
  _showDescription;
12079
+ _showSelectionIndicator;
10704
12080
  _font;
10705
12081
  _itemSpacing;
10706
12082
  linesPerItem;
@@ -10722,6 +12098,7 @@ class SelectRenderable extends Renderable {
10722
12098
  showScrollIndicator: false,
10723
12099
  wrapSelection: false,
10724
12100
  showDescription: true,
12101
+ showSelectionIndicator: true,
10725
12102
  itemSpacing: 0,
10726
12103
  fastScrollStep: 5
10727
12104
  };
@@ -10737,6 +12114,7 @@ class SelectRenderable extends Renderable {
10737
12114
  this._showScrollIndicator = options.showScrollIndicator ?? this._defaultOptions.showScrollIndicator;
10738
12115
  this._wrapSelection = options.wrapSelection ?? this._defaultOptions.wrapSelection;
10739
12116
  this._showDescription = options.showDescription ?? this._defaultOptions.showDescription;
12117
+ this._showSelectionIndicator = options.showSelectionIndicator ?? this._defaultOptions.showSelectionIndicator;
10740
12118
  this._font = options.font;
10741
12119
  this._itemSpacing = options.itemSpacing || this._defaultOptions.itemSpacing;
10742
12120
  this.fontHeight = this._font ? measureText({ text: "A", font: this._font }).height : 1;
@@ -10785,29 +12163,30 @@ class SelectRenderable extends Renderable {
10785
12163
  const contentHeight2 = this.linesPerItem - this._itemSpacing;
10786
12164
  this.frameBuffer.fillRect(contentX, itemY, contentWidth, contentHeight2, this._selectedBackgroundColor);
10787
12165
  }
10788
- const nameContent = `${isSelected ? "▶ " : " "}${option.name}`;
12166
+ const indicator = this._showSelectionIndicator ? isSelected ? "▶ " : " " : "";
12167
+ const indicatorWidth = this._showSelectionIndicator ? 2 : 0;
12168
+ const nameContent = `${indicator}${option.name}`;
10789
12169
  const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
10790
12170
  const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
10791
- let descX = contentX + 3;
12171
+ const textX = contentX + 1 + indicatorWidth;
10792
12172
  if (this._font) {
10793
- const indicator = isSelected ? "▶ " : " ";
10794
- this.frameBuffer.drawText(indicator, contentX + 1, itemY, nameColor);
10795
- const indicatorWidth = 2;
12173
+ if (indicator) {
12174
+ this.frameBuffer.drawText(indicator, contentX + 1, itemY, nameColor);
12175
+ }
10796
12176
  renderFontToFrameBuffer(this.frameBuffer, {
10797
12177
  text: option.name,
10798
- x: contentX + 1 + indicatorWidth,
12178
+ x: textX,
10799
12179
  y: itemY,
10800
12180
  color: nameColor,
10801
12181
  backgroundColor: isSelected ? this._selectedBackgroundColor : bgColor,
10802
12182
  font: this._font
10803
12183
  });
10804
- descX = contentX + 1 + indicatorWidth;
10805
12184
  } else {
10806
12185
  this.frameBuffer.drawText(nameContent, contentX + 1, itemY, nameColor);
10807
12186
  }
10808
12187
  if (this._showDescription && itemY + this.fontHeight < contentY + contentHeight) {
10809
12188
  const descColor = isSelected ? this._selectedDescriptionColor : this._descriptionColor;
10810
- this.frameBuffer.drawText(option.description, descX, itemY + this.fontHeight, descColor);
12189
+ this.frameBuffer.drawText(option.description, textX, itemY + this.fontHeight, descColor);
10811
12190
  }
10812
12191
  }
10813
12192
  if (this._showScrollIndicator && this._options.length > this.maxVisibleItems) {
@@ -10937,6 +12316,16 @@ class SelectRenderable extends Renderable {
10937
12316
  this.requestRender();
10938
12317
  }
10939
12318
  }
12319
+ get showSelectionIndicator() {
12320
+ return this._showSelectionIndicator;
12321
+ }
12322
+ set showSelectionIndicator(show) {
12323
+ const next = show ?? this._defaultOptions.showSelectionIndicator;
12324
+ if (this._showSelectionIndicator !== next) {
12325
+ this._showSelectionIndicator = next;
12326
+ this.requestRender();
12327
+ }
12328
+ }
10940
12329
  get wrapSelection() {
10941
12330
  return this._wrapSelection;
10942
12331
  }
@@ -11525,6 +12914,7 @@ export {
11525
12914
  createTerminalPalette,
11526
12915
  createSlotRegistry,
11527
12916
  createMarkdownCodeBlockRenderer,
12917
+ createIcyStreamDemuxer,
11528
12918
  createExtmarksController,
11529
12919
  createCoreSlotRegistry,
11530
12920
  createCliRenderer,
@@ -11622,6 +13012,10 @@ export {
11622
13012
  PROTANOPIA_COMP_MATRIX,
11623
13013
  OptimizedBuffer,
11624
13014
  NativeSpanFeed,
13015
+ NativeMeasureTargetKind,
13016
+ NativeAudioStreamState2 as NativeAudioStreamState,
13017
+ NativeAudioStreamFormat2 as NativeAudioStreamFormat,
13018
+ NativeAudioStreamCloseReason2 as NativeAudioStreamCloseReason,
11625
13019
  MouseParser,
11626
13020
  MouseEvent,
11627
13021
  MouseButton,
@@ -11672,6 +13066,9 @@ export {
11672
13066
  BorderCharArrays,
11673
13067
  BloomEffect,
11674
13068
  BaseRenderable,
13069
+ AudioStreamError,
13070
+ AudioStream,
13071
+ AudioInitializationError,
11675
13072
  Audio,
11676
13073
  ArrowRenderable,
11677
13074
  ATTRIBUTE_BASE_MASK,
@@ -11682,5 +13079,5 @@ export {
11682
13079
  ACHROMATOPSIA_MATRIX
11683
13080
  };
11684
13081
 
11685
- //# debugId=C226AD7FDBC68DA164756E2164756E21
13082
+ //# debugId=EF989EBD255C13EC64756E2164756E21
11686
13083
  //# sourceMappingURL=index.js.map