@absolutejs/voice 0.0.22-beta.630 → 0.0.22-beta.631
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/htmxBootstrap.js +64 -1
- package/dist/client/index.js +64 -1
- package/dist/core/types.d.ts +16 -0
- package/dist/testing/index.js +64 -1
- package/package.json +1 -1
|
@@ -1721,6 +1721,7 @@ var createTimeStretcher = () => {
|
|
|
1721
1721
|
};
|
|
1722
1722
|
|
|
1723
1723
|
// src/client/audioPlayer.ts
|
|
1724
|
+
var activeAudioPlayers = new Set;
|
|
1724
1725
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1725
1726
|
var DEFAULT_VOLUME = 1;
|
|
1726
1727
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
@@ -1729,6 +1730,7 @@ var MAX_PLAYBACK_RATE = 2;
|
|
|
1729
1730
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1730
1731
|
var ANALYSER_FFT_SIZE = 256;
|
|
1731
1732
|
var PCM_BYTE_MIDPOINT = 128;
|
|
1733
|
+
var GAP_EPSILON_S = 0.03;
|
|
1732
1734
|
var createInitialState3 = () => ({
|
|
1733
1735
|
activeSourceCount: 0,
|
|
1734
1736
|
error: null,
|
|
@@ -1800,6 +1802,36 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1800
1802
|
let interruptPromise = null;
|
|
1801
1803
|
let resolveInterruptPromise = null;
|
|
1802
1804
|
let interruptFallbackTimer = null;
|
|
1805
|
+
let concurrencyCounted = false;
|
|
1806
|
+
let observedPeakConcurrency = 0;
|
|
1807
|
+
let scheduledDurationMs = 0;
|
|
1808
|
+
let gapCount = 0;
|
|
1809
|
+
let totalGapMs = 0;
|
|
1810
|
+
let maxGapMs = 0;
|
|
1811
|
+
let errorCount = 0;
|
|
1812
|
+
let hasScheduledAnyChunk = false;
|
|
1813
|
+
const sampleConcurrency = () => {
|
|
1814
|
+
if (activeAudioPlayers.size > observedPeakConcurrency) {
|
|
1815
|
+
observedPeakConcurrency = activeAudioPlayers.size;
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
const markPlayerActive = () => {
|
|
1819
|
+
if (concurrencyCounted) {
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
concurrencyCounted = true;
|
|
1823
|
+
activeAudioPlayers.add(sampleConcurrency);
|
|
1824
|
+
for (const sample of activeAudioPlayers) {
|
|
1825
|
+
sample();
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
const markPlayerInactive = () => {
|
|
1829
|
+
if (!concurrencyCounted) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
concurrencyCounted = false;
|
|
1833
|
+
activeAudioPlayers.delete(sampleConcurrency);
|
|
1834
|
+
};
|
|
1803
1835
|
const notify = () => {
|
|
1804
1836
|
for (const subscriber of subscribers) {
|
|
1805
1837
|
subscriber();
|
|
@@ -1913,7 +1945,17 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1913
1945
|
});
|
|
1914
1946
|
maybeResolveInterrupt();
|
|
1915
1947
|
};
|
|
1916
|
-
const
|
|
1948
|
+
const earliestStart = context.currentTime + lookaheadSeconds;
|
|
1949
|
+
const startAt = Math.max(earliestStart, queueEndTime);
|
|
1950
|
+
if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
|
|
1951
|
+
const gapMs = (earliestStart - queueEndTime) * 1000;
|
|
1952
|
+
gapCount += 1;
|
|
1953
|
+
totalGapMs += gapMs;
|
|
1954
|
+
maxGapMs = Math.max(maxGapMs, gapMs);
|
|
1955
|
+
}
|
|
1956
|
+
hasScheduledAnyChunk = true;
|
|
1957
|
+
scheduledDurationMs += buffer.duration / rate * 1000;
|
|
1958
|
+
sampleConcurrency();
|
|
1917
1959
|
queueEndTime = startAt + buffer.duration / rate;
|
|
1918
1960
|
sourceNodes.add(node);
|
|
1919
1961
|
setState({
|
|
@@ -1981,6 +2023,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1981
2023
|
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1982
2024
|
});
|
|
1983
2025
|
} catch (error) {
|
|
2026
|
+
errorCount += 1;
|
|
1984
2027
|
setState({
|
|
1985
2028
|
error: error instanceof Error ? error.message : String(error)
|
|
1986
2029
|
});
|
|
@@ -2004,6 +2047,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2004
2047
|
return state.activeSourceCount;
|
|
2005
2048
|
},
|
|
2006
2049
|
close: async () => {
|
|
2050
|
+
markPlayerInactive();
|
|
2007
2051
|
unsubscribeSource();
|
|
2008
2052
|
stopQueuedPlayback({ forceClear: true });
|
|
2009
2053
|
clearInterruptTimer();
|
|
@@ -2043,6 +2087,21 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2043
2087
|
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
2044
2088
|
},
|
|
2045
2089
|
getSnapshot: () => state,
|
|
2090
|
+
getIntegritySummary: () => {
|
|
2091
|
+
const chunksReceived = source.assistantAudio.length;
|
|
2092
|
+
const chunksScheduled = state.processedChunkCount;
|
|
2093
|
+
return {
|
|
2094
|
+
chunksReceived,
|
|
2095
|
+
chunksScheduled,
|
|
2096
|
+
errorCount,
|
|
2097
|
+
gapCount,
|
|
2098
|
+
maxConcurrentPlayers: observedPeakConcurrency,
|
|
2099
|
+
maxGapMs: Math.round(maxGapMs),
|
|
2100
|
+
ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
|
|
2101
|
+
scheduledDurationMs: Math.round(scheduledDurationMs),
|
|
2102
|
+
totalGapMs: Math.round(totalGapMs)
|
|
2103
|
+
};
|
|
2104
|
+
},
|
|
2046
2105
|
interrupt: async () => {
|
|
2047
2106
|
const startedAt = Date.now();
|
|
2048
2107
|
const context = await ensureAudioContext();
|
|
@@ -2096,6 +2155,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2096
2155
|
return;
|
|
2097
2156
|
}
|
|
2098
2157
|
await audioContext.suspend();
|
|
2158
|
+
markPlayerInactive();
|
|
2099
2159
|
setState({
|
|
2100
2160
|
activeSourceCount: sourceNodes.size,
|
|
2101
2161
|
isActive: false,
|
|
@@ -2126,6 +2186,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2126
2186
|
if (context.state === "suspended") {
|
|
2127
2187
|
await context.resume();
|
|
2128
2188
|
}
|
|
2189
|
+
markPlayerActive();
|
|
2129
2190
|
setState({
|
|
2130
2191
|
activeSourceCount: sourceNodes.size,
|
|
2131
2192
|
isActive: true,
|
|
@@ -2133,6 +2194,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2133
2194
|
});
|
|
2134
2195
|
await queueSync();
|
|
2135
2196
|
} catch (error) {
|
|
2197
|
+
errorCount += 1;
|
|
2198
|
+
markPlayerInactive();
|
|
2136
2199
|
setState({
|
|
2137
2200
|
error: error instanceof Error ? error.message : String(error),
|
|
2138
2201
|
isActive: false,
|
package/dist/client/index.js
CHANGED
|
@@ -528,6 +528,7 @@ var createTimeStretcher = () => {
|
|
|
528
528
|
};
|
|
529
529
|
|
|
530
530
|
// src/client/audioPlayer.ts
|
|
531
|
+
var activeAudioPlayers = new Set;
|
|
531
532
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
532
533
|
var DEFAULT_VOLUME = 1;
|
|
533
534
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
@@ -536,6 +537,7 @@ var MAX_PLAYBACK_RATE = 2;
|
|
|
536
537
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
537
538
|
var ANALYSER_FFT_SIZE = 256;
|
|
538
539
|
var PCM_BYTE_MIDPOINT = 128;
|
|
540
|
+
var GAP_EPSILON_S = 0.03;
|
|
539
541
|
var createInitialState = () => ({
|
|
540
542
|
activeSourceCount: 0,
|
|
541
543
|
error: null,
|
|
@@ -607,6 +609,36 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
607
609
|
let interruptPromise = null;
|
|
608
610
|
let resolveInterruptPromise = null;
|
|
609
611
|
let interruptFallbackTimer = null;
|
|
612
|
+
let concurrencyCounted = false;
|
|
613
|
+
let observedPeakConcurrency = 0;
|
|
614
|
+
let scheduledDurationMs = 0;
|
|
615
|
+
let gapCount = 0;
|
|
616
|
+
let totalGapMs = 0;
|
|
617
|
+
let maxGapMs = 0;
|
|
618
|
+
let errorCount = 0;
|
|
619
|
+
let hasScheduledAnyChunk = false;
|
|
620
|
+
const sampleConcurrency = () => {
|
|
621
|
+
if (activeAudioPlayers.size > observedPeakConcurrency) {
|
|
622
|
+
observedPeakConcurrency = activeAudioPlayers.size;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
const markPlayerActive = () => {
|
|
626
|
+
if (concurrencyCounted) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
concurrencyCounted = true;
|
|
630
|
+
activeAudioPlayers.add(sampleConcurrency);
|
|
631
|
+
for (const sample of activeAudioPlayers) {
|
|
632
|
+
sample();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const markPlayerInactive = () => {
|
|
636
|
+
if (!concurrencyCounted) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
concurrencyCounted = false;
|
|
640
|
+
activeAudioPlayers.delete(sampleConcurrency);
|
|
641
|
+
};
|
|
610
642
|
const notify = () => {
|
|
611
643
|
for (const subscriber of subscribers) {
|
|
612
644
|
subscriber();
|
|
@@ -720,7 +752,17 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
720
752
|
});
|
|
721
753
|
maybeResolveInterrupt();
|
|
722
754
|
};
|
|
723
|
-
const
|
|
755
|
+
const earliestStart = context.currentTime + lookaheadSeconds;
|
|
756
|
+
const startAt = Math.max(earliestStart, queueEndTime);
|
|
757
|
+
if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
|
|
758
|
+
const gapMs = (earliestStart - queueEndTime) * 1000;
|
|
759
|
+
gapCount += 1;
|
|
760
|
+
totalGapMs += gapMs;
|
|
761
|
+
maxGapMs = Math.max(maxGapMs, gapMs);
|
|
762
|
+
}
|
|
763
|
+
hasScheduledAnyChunk = true;
|
|
764
|
+
scheduledDurationMs += buffer.duration / rate * 1000;
|
|
765
|
+
sampleConcurrency();
|
|
724
766
|
queueEndTime = startAt + buffer.duration / rate;
|
|
725
767
|
sourceNodes.add(node);
|
|
726
768
|
setState({
|
|
@@ -788,6 +830,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
788
830
|
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
789
831
|
});
|
|
790
832
|
} catch (error) {
|
|
833
|
+
errorCount += 1;
|
|
791
834
|
setState({
|
|
792
835
|
error: error instanceof Error ? error.message : String(error)
|
|
793
836
|
});
|
|
@@ -811,6 +854,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
811
854
|
return state.activeSourceCount;
|
|
812
855
|
},
|
|
813
856
|
close: async () => {
|
|
857
|
+
markPlayerInactive();
|
|
814
858
|
unsubscribeSource();
|
|
815
859
|
stopQueuedPlayback({ forceClear: true });
|
|
816
860
|
clearInterruptTimer();
|
|
@@ -850,6 +894,21 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
850
894
|
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
851
895
|
},
|
|
852
896
|
getSnapshot: () => state,
|
|
897
|
+
getIntegritySummary: () => {
|
|
898
|
+
const chunksReceived = source.assistantAudio.length;
|
|
899
|
+
const chunksScheduled = state.processedChunkCount;
|
|
900
|
+
return {
|
|
901
|
+
chunksReceived,
|
|
902
|
+
chunksScheduled,
|
|
903
|
+
errorCount,
|
|
904
|
+
gapCount,
|
|
905
|
+
maxConcurrentPlayers: observedPeakConcurrency,
|
|
906
|
+
maxGapMs: Math.round(maxGapMs),
|
|
907
|
+
ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
|
|
908
|
+
scheduledDurationMs: Math.round(scheduledDurationMs),
|
|
909
|
+
totalGapMs: Math.round(totalGapMs)
|
|
910
|
+
};
|
|
911
|
+
},
|
|
853
912
|
interrupt: async () => {
|
|
854
913
|
const startedAt = Date.now();
|
|
855
914
|
const context = await ensureAudioContext();
|
|
@@ -903,6 +962,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
903
962
|
return;
|
|
904
963
|
}
|
|
905
964
|
await audioContext.suspend();
|
|
965
|
+
markPlayerInactive();
|
|
906
966
|
setState({
|
|
907
967
|
activeSourceCount: sourceNodes.size,
|
|
908
968
|
isActive: false,
|
|
@@ -933,6 +993,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
933
993
|
if (context.state === "suspended") {
|
|
934
994
|
await context.resume();
|
|
935
995
|
}
|
|
996
|
+
markPlayerActive();
|
|
936
997
|
setState({
|
|
937
998
|
activeSourceCount: sourceNodes.size,
|
|
938
999
|
isActive: true,
|
|
@@ -940,6 +1001,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
940
1001
|
});
|
|
941
1002
|
await queueSync();
|
|
942
1003
|
} catch (error) {
|
|
1004
|
+
errorCount += 1;
|
|
1005
|
+
markPlayerInactive();
|
|
943
1006
|
setState({
|
|
944
1007
|
error: error instanceof Error ? error.message : String(error),
|
|
945
1008
|
isActive: false,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1471,6 +1471,20 @@ export type VoiceAudioPlayerSource = {
|
|
|
1471
1471
|
assistantAudio: VoiceStreamState["assistantAudio"];
|
|
1472
1472
|
subscribe: (subscriber: () => void) => () => void;
|
|
1473
1473
|
};
|
|
1474
|
+
/** Post-call playback integrity, for verifying the assistant audio actually
|
|
1475
|
+
* played cleanly (no overlap, no dropped/stalled packets) — emit it as a trace
|
|
1476
|
+
* so a garbled call is provable, not guessed. `ok` is the all-clear roll-up. */
|
|
1477
|
+
export type VoiceAudioIntegrity = {
|
|
1478
|
+
ok: boolean;
|
|
1479
|
+
chunksReceived: number;
|
|
1480
|
+
chunksScheduled: number;
|
|
1481
|
+
scheduledDurationMs: number;
|
|
1482
|
+
gapCount: number;
|
|
1483
|
+
totalGapMs: number;
|
|
1484
|
+
maxGapMs: number;
|
|
1485
|
+
maxConcurrentPlayers: number;
|
|
1486
|
+
errorCount: number;
|
|
1487
|
+
};
|
|
1474
1488
|
export type VoiceAudioPlayer = {
|
|
1475
1489
|
close: () => Promise<void>;
|
|
1476
1490
|
error: string | null;
|
|
@@ -1478,6 +1492,8 @@ export type VoiceAudioPlayer = {
|
|
|
1478
1492
|
* driving a visualizer from the actual voice. 0 when idle / no analyser. */
|
|
1479
1493
|
getOutputLevel: () => number;
|
|
1480
1494
|
getSnapshot: () => VoiceAudioPlayerState;
|
|
1495
|
+
/** Post-call playback-integrity roll-up (overlap, gaps, drops). */
|
|
1496
|
+
getIntegritySummary: () => VoiceAudioIntegrity;
|
|
1481
1497
|
activeSourceCount: number;
|
|
1482
1498
|
isActive: boolean;
|
|
1483
1499
|
isPlaying: boolean;
|
package/dist/testing/index.js
CHANGED
|
@@ -1731,6 +1731,7 @@ var createTimeStretcher = () => {
|
|
|
1731
1731
|
};
|
|
1732
1732
|
|
|
1733
1733
|
// src/client/audioPlayer.ts
|
|
1734
|
+
var activeAudioPlayers = new Set;
|
|
1734
1735
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1735
1736
|
var DEFAULT_VOLUME = 1;
|
|
1736
1737
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
@@ -1739,6 +1740,7 @@ var MAX_PLAYBACK_RATE = 2;
|
|
|
1739
1740
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1740
1741
|
var ANALYSER_FFT_SIZE = 256;
|
|
1741
1742
|
var PCM_BYTE_MIDPOINT = 128;
|
|
1743
|
+
var GAP_EPSILON_S = 0.03;
|
|
1742
1744
|
var createInitialState = () => ({
|
|
1743
1745
|
activeSourceCount: 0,
|
|
1744
1746
|
error: null,
|
|
@@ -1810,6 +1812,36 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1810
1812
|
let interruptPromise = null;
|
|
1811
1813
|
let resolveInterruptPromise = null;
|
|
1812
1814
|
let interruptFallbackTimer = null;
|
|
1815
|
+
let concurrencyCounted = false;
|
|
1816
|
+
let observedPeakConcurrency = 0;
|
|
1817
|
+
let scheduledDurationMs = 0;
|
|
1818
|
+
let gapCount = 0;
|
|
1819
|
+
let totalGapMs = 0;
|
|
1820
|
+
let maxGapMs = 0;
|
|
1821
|
+
let errorCount = 0;
|
|
1822
|
+
let hasScheduledAnyChunk = false;
|
|
1823
|
+
const sampleConcurrency = () => {
|
|
1824
|
+
if (activeAudioPlayers.size > observedPeakConcurrency) {
|
|
1825
|
+
observedPeakConcurrency = activeAudioPlayers.size;
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
const markPlayerActive = () => {
|
|
1829
|
+
if (concurrencyCounted) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
concurrencyCounted = true;
|
|
1833
|
+
activeAudioPlayers.add(sampleConcurrency);
|
|
1834
|
+
for (const sample of activeAudioPlayers) {
|
|
1835
|
+
sample();
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
const markPlayerInactive = () => {
|
|
1839
|
+
if (!concurrencyCounted) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
concurrencyCounted = false;
|
|
1843
|
+
activeAudioPlayers.delete(sampleConcurrency);
|
|
1844
|
+
};
|
|
1813
1845
|
const notify = () => {
|
|
1814
1846
|
for (const subscriber of subscribers) {
|
|
1815
1847
|
subscriber();
|
|
@@ -1923,7 +1955,17 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1923
1955
|
});
|
|
1924
1956
|
maybeResolveInterrupt();
|
|
1925
1957
|
};
|
|
1926
|
-
const
|
|
1958
|
+
const earliestStart = context.currentTime + lookaheadSeconds;
|
|
1959
|
+
const startAt = Math.max(earliestStart, queueEndTime);
|
|
1960
|
+
if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
|
|
1961
|
+
const gapMs = (earliestStart - queueEndTime) * 1000;
|
|
1962
|
+
gapCount += 1;
|
|
1963
|
+
totalGapMs += gapMs;
|
|
1964
|
+
maxGapMs = Math.max(maxGapMs, gapMs);
|
|
1965
|
+
}
|
|
1966
|
+
hasScheduledAnyChunk = true;
|
|
1967
|
+
scheduledDurationMs += buffer.duration / rate * 1000;
|
|
1968
|
+
sampleConcurrency();
|
|
1927
1969
|
queueEndTime = startAt + buffer.duration / rate;
|
|
1928
1970
|
sourceNodes.add(node);
|
|
1929
1971
|
setState({
|
|
@@ -1991,6 +2033,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1991
2033
|
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1992
2034
|
});
|
|
1993
2035
|
} catch (error) {
|
|
2036
|
+
errorCount += 1;
|
|
1994
2037
|
setState({
|
|
1995
2038
|
error: error instanceof Error ? error.message : String(error)
|
|
1996
2039
|
});
|
|
@@ -2014,6 +2057,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2014
2057
|
return state.activeSourceCount;
|
|
2015
2058
|
},
|
|
2016
2059
|
close: async () => {
|
|
2060
|
+
markPlayerInactive();
|
|
2017
2061
|
unsubscribeSource();
|
|
2018
2062
|
stopQueuedPlayback({ forceClear: true });
|
|
2019
2063
|
clearInterruptTimer();
|
|
@@ -2053,6 +2097,21 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2053
2097
|
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
2054
2098
|
},
|
|
2055
2099
|
getSnapshot: () => state,
|
|
2100
|
+
getIntegritySummary: () => {
|
|
2101
|
+
const chunksReceived = source.assistantAudio.length;
|
|
2102
|
+
const chunksScheduled = state.processedChunkCount;
|
|
2103
|
+
return {
|
|
2104
|
+
chunksReceived,
|
|
2105
|
+
chunksScheduled,
|
|
2106
|
+
errorCount,
|
|
2107
|
+
gapCount,
|
|
2108
|
+
maxConcurrentPlayers: observedPeakConcurrency,
|
|
2109
|
+
maxGapMs: Math.round(maxGapMs),
|
|
2110
|
+
ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
|
|
2111
|
+
scheduledDurationMs: Math.round(scheduledDurationMs),
|
|
2112
|
+
totalGapMs: Math.round(totalGapMs)
|
|
2113
|
+
};
|
|
2114
|
+
},
|
|
2056
2115
|
interrupt: async () => {
|
|
2057
2116
|
const startedAt = Date.now();
|
|
2058
2117
|
const context = await ensureAudioContext();
|
|
@@ -2106,6 +2165,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2106
2165
|
return;
|
|
2107
2166
|
}
|
|
2108
2167
|
await audioContext.suspend();
|
|
2168
|
+
markPlayerInactive();
|
|
2109
2169
|
setState({
|
|
2110
2170
|
activeSourceCount: sourceNodes.size,
|
|
2111
2171
|
isActive: false,
|
|
@@ -2136,6 +2196,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2136
2196
|
if (context.state === "suspended") {
|
|
2137
2197
|
await context.resume();
|
|
2138
2198
|
}
|
|
2199
|
+
markPlayerActive();
|
|
2139
2200
|
setState({
|
|
2140
2201
|
activeSourceCount: sourceNodes.size,
|
|
2141
2202
|
isActive: true,
|
|
@@ -2143,6 +2204,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2143
2204
|
});
|
|
2144
2205
|
await queueSync();
|
|
2145
2206
|
} catch (error) {
|
|
2207
|
+
errorCount += 1;
|
|
2208
|
+
markPlayerInactive();
|
|
2146
2209
|
setState({
|
|
2147
2210
|
error: error instanceof Error ? error.message : String(error),
|
|
2148
2211
|
isActive: false,
|