@myned-ai/avatar-chat-widget 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1790,7 +1790,15 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1790
1790
|
return _AudioContextManagerImpl._instance;
|
|
1791
1791
|
}
|
|
1792
1792
|
/**
|
|
1793
|
-
*
|
|
1793
|
+
* Set the sample rate for when the AudioContext is eventually created.
|
|
1794
|
+
* Call this early (e.g., from server config) before any audio session starts.
|
|
1795
|
+
*/
|
|
1796
|
+
setSampleRate(sampleRate) {
|
|
1797
|
+
this._sampleRate = sampleRate;
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Get the shared AudioContext. Creates lazily if needed.
|
|
1801
|
+
* Prefer ensureAudioReady() from user-gesture handlers to guarantee iOS unlock.
|
|
1794
1802
|
* @param sampleRate Optional sample rate (only used on first creation)
|
|
1795
1803
|
*/
|
|
1796
1804
|
getContext(sampleRate) {
|
|
@@ -1800,6 +1808,27 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1800
1808
|
if (sampleRate) {
|
|
1801
1809
|
this._sampleRate = sampleRate;
|
|
1802
1810
|
}
|
|
1811
|
+
return this.createContext();
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Single idempotent entry point: create (if needed) + resume AudioContext.
|
|
1815
|
+
* Call this synchronously from any user-gesture handler (pointerdown, touchend,
|
|
1816
|
+
* click, keydown) to guarantee iOS audio unlock.
|
|
1817
|
+
*/
|
|
1818
|
+
ensureAudioReady() {
|
|
1819
|
+
if (!this._context) {
|
|
1820
|
+
this.createContext();
|
|
1821
|
+
}
|
|
1822
|
+
if (this._context && this._context.state !== "running") {
|
|
1823
|
+
this.resume().catch((error2) => {
|
|
1824
|
+
log$c.warn("ensureAudioReady resume failed (will retry on next gesture):", error2);
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Internal: create the AudioContext and wire up fallback document listeners.
|
|
1830
|
+
*/
|
|
1831
|
+
createContext() {
|
|
1803
1832
|
try {
|
|
1804
1833
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
1805
1834
|
if (!AudioContextClass) {
|
|
@@ -1808,12 +1837,14 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1808
1837
|
this._context = new AudioContextClass({
|
|
1809
1838
|
sampleRate: this._sampleRate,
|
|
1810
1839
|
latencyHint: "interactive"
|
|
1811
|
-
// Optimize for real-time
|
|
1812
1840
|
});
|
|
1813
1841
|
log$c.info(`AudioContext created: sampleRate=${this._context.sampleRate}, state=${this._context.state}`);
|
|
1814
1842
|
this.setupResumeListener();
|
|
1815
1843
|
this._context.onstatechange = () => {
|
|
1816
1844
|
log$c.debug(`AudioContext state changed: ${this._context?.state}`);
|
|
1845
|
+
if (this._context?.state === "running") {
|
|
1846
|
+
this.removeResumeListeners();
|
|
1847
|
+
}
|
|
1817
1848
|
};
|
|
1818
1849
|
} catch (error2) {
|
|
1819
1850
|
errorBoundary.handleError(error2, "audio-context-manager");
|
|
@@ -1822,26 +1853,29 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1822
1853
|
return this._context;
|
|
1823
1854
|
}
|
|
1824
1855
|
/**
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1856
|
+
* Fallback document-level listeners for audio unlock.
|
|
1857
|
+
* These are a safety net — primary unlock happens via ensureAudioReady()
|
|
1858
|
+
* called from widget gesture handlers.
|
|
1827
1859
|
*/
|
|
1828
1860
|
setupResumeListener() {
|
|
1829
1861
|
if (this._isResumeListenerAdded) {
|
|
1830
1862
|
return;
|
|
1831
1863
|
}
|
|
1832
|
-
this._listenerEvents = ["
|
|
1833
|
-
this._interactionHandler =
|
|
1834
|
-
this.
|
|
1835
|
-
|
|
1864
|
+
this._listenerEvents = ["pointerdown", "touchend", "keydown"];
|
|
1865
|
+
this._interactionHandler = () => {
|
|
1866
|
+
if (this._context && this._context.state !== "running") {
|
|
1867
|
+
this._context.resume().catch(() => {
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1836
1870
|
};
|
|
1837
1871
|
this._listenerEvents.forEach((event) => {
|
|
1838
|
-
document.addEventListener(event, this._interactionHandler, {
|
|
1872
|
+
document.addEventListener(event, this._interactionHandler, { passive: true });
|
|
1839
1873
|
});
|
|
1840
1874
|
this._isResumeListenerAdded = true;
|
|
1841
|
-
log$c.debug("Audio resume listeners added");
|
|
1875
|
+
log$c.debug("Audio resume fallback listeners added");
|
|
1842
1876
|
}
|
|
1843
1877
|
/**
|
|
1844
|
-
* Remove resume listeners (
|
|
1878
|
+
* Remove resume listeners (only after context is running, or on cleanup)
|
|
1845
1879
|
*/
|
|
1846
1880
|
removeResumeListeners() {
|
|
1847
1881
|
if (this._interactionHandler) {
|
|
@@ -1850,6 +1884,8 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1850
1884
|
});
|
|
1851
1885
|
this._interactionHandler = null;
|
|
1852
1886
|
this._listenerEvents = [];
|
|
1887
|
+
this._isResumeListenerAdded = false;
|
|
1888
|
+
log$c.debug("Audio resume fallback listeners removed");
|
|
1853
1889
|
}
|
|
1854
1890
|
}
|
|
1855
1891
|
/**
|
|
@@ -1870,6 +1906,7 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1870
1906
|
try {
|
|
1871
1907
|
await this._context.resume();
|
|
1872
1908
|
log$c.info("AudioContext resumed successfully");
|
|
1909
|
+
this.removeResumeListeners();
|
|
1873
1910
|
} catch (error2) {
|
|
1874
1911
|
log$c.error("Failed to resume AudioContext:", error2);
|
|
1875
1912
|
errorBoundary.handleError(error2, "audio-context-manager");
|
|
@@ -1998,7 +2035,7 @@ class AudioOutput {
|
|
|
1998
2035
|
this.chunkDurationMs = 100;
|
|
1999
2036
|
this.audioBuffer = new CircularBuffer(CONFIG.audio.output.maxBufferFrames);
|
|
2000
2037
|
this.adaptiveBuffer = new AdaptiveBuffer(100, 50, 500);
|
|
2001
|
-
AudioContextManager.
|
|
2038
|
+
AudioContextManager.setSampleRate(this.sampleRate);
|
|
2002
2039
|
}
|
|
2003
2040
|
/**
|
|
2004
2041
|
* Get the shared AudioContext
|
|
@@ -2011,6 +2048,7 @@ class AudioOutput {
|
|
|
2011
2048
|
*/
|
|
2012
2049
|
setDefaultSampleRate(sampleRate) {
|
|
2013
2050
|
this.sampleRate = sampleRate;
|
|
2051
|
+
AudioContextManager.setSampleRate(sampleRate);
|
|
2014
2052
|
log$b.info(`Output sample rate set to ${sampleRate}Hz`);
|
|
2015
2053
|
}
|
|
2016
2054
|
startSession(sessionId, sampleRate) {
|
|
@@ -2024,6 +2062,7 @@ class AudioOutput {
|
|
|
2024
2062
|
this.isPlaying = false;
|
|
2025
2063
|
this.nextPlayTime = 0;
|
|
2026
2064
|
AudioContextManager.resume().catch(() => {
|
|
2065
|
+
log$b.warn("AudioContext resume failed in startSession — will retry on next user gesture");
|
|
2027
2066
|
});
|
|
2028
2067
|
}
|
|
2029
2068
|
addAudioChunk(data, timestamp) {
|
|
@@ -2535,7 +2574,7 @@ class SyncPlayback {
|
|
|
2535
2574
|
this.floatBuffer = new Float32Array(this.maxSamplesPerFrame);
|
|
2536
2575
|
this.visibilityHandler = this.handleVisibilityChange.bind(this);
|
|
2537
2576
|
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
2538
|
-
AudioContextManager.
|
|
2577
|
+
AudioContextManager.setSampleRate(this.sampleRate);
|
|
2539
2578
|
}
|
|
2540
2579
|
/**
|
|
2541
2580
|
* Get the shared AudioContext
|
|
@@ -2572,6 +2611,7 @@ class SyncPlayback {
|
|
|
2572
2611
|
*/
|
|
2573
2612
|
setDefaultSampleRate(sampleRate) {
|
|
2574
2613
|
this.sampleRate = sampleRate;
|
|
2614
|
+
AudioContextManager.setSampleRate(sampleRate);
|
|
2575
2615
|
log$8.info(`SyncPlayback sample rate set to ${sampleRate}Hz`);
|
|
2576
2616
|
}
|
|
2577
2617
|
startSession(sessionId, sampleRate) {
|
|
@@ -2585,6 +2625,7 @@ class SyncPlayback {
|
|
|
2585
2625
|
this.sampleRate = sampleRate;
|
|
2586
2626
|
}
|
|
2587
2627
|
AudioContextManager.resume().catch(() => {
|
|
2628
|
+
log$8.warn("AudioContext resume failed in startSession — will retry on next user gesture");
|
|
2588
2629
|
});
|
|
2589
2630
|
try {
|
|
2590
2631
|
const ctx = this.audioContext;
|
|
@@ -5884,6 +5925,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5884
5925
|
}
|
|
5885
5926
|
});
|
|
5886
5927
|
chatInput.addEventListener("focus", () => {
|
|
5928
|
+
AudioContextManager.ensureAudioReady();
|
|
5887
5929
|
const root = this.shadow.querySelector(".widget-root");
|
|
5888
5930
|
if (root) root.classList.add("input-focused");
|
|
5889
5931
|
});
|
|
@@ -5914,6 +5956,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5914
5956
|
const handleChipClick = (e) => {
|
|
5915
5957
|
const target = e.target;
|
|
5916
5958
|
if (target.classList.contains("suggestion-chip")) {
|
|
5959
|
+
AudioContextManager.ensureAudioReady();
|
|
5917
5960
|
this.markHasMessages();
|
|
5918
5961
|
hideSuggestions();
|
|
5919
5962
|
const text = target.textContent;
|
|
@@ -5927,11 +5970,13 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5927
5970
|
quickReplies?.addEventListener("click", handleChipClick);
|
|
5928
5971
|
avatarSuggestions?.addEventListener("click", handleChipClick);
|
|
5929
5972
|
micBtn?.addEventListener("click", () => {
|
|
5973
|
+
AudioContextManager.ensureAudioReady();
|
|
5930
5974
|
this.markHasMessages();
|
|
5931
5975
|
hideSuggestions();
|
|
5932
5976
|
});
|
|
5933
5977
|
chatInput.addEventListener("keydown", (e) => {
|
|
5934
5978
|
if (e.key === "Enter") {
|
|
5979
|
+
AudioContextManager.ensureAudioReady();
|
|
5935
5980
|
this.markHasMessages();
|
|
5936
5981
|
hideSuggestions();
|
|
5937
5982
|
setTimeout(() => {
|
|
@@ -6133,6 +6178,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
6133
6178
|
*/
|
|
6134
6179
|
async expand() {
|
|
6135
6180
|
if (!this._isCollapsed) return;
|
|
6181
|
+
AudioContextManager.ensureAudioReady();
|
|
6136
6182
|
this._isCollapsed = false;
|
|
6137
6183
|
this.classList.remove("collapsed");
|
|
6138
6184
|
this.style.width = `${this.config.width}px`;
|