@q9labsai/chalk-react 4.1.2 → 4.1.3

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.
@@ -9661,15 +9661,64 @@ function diffSoundCues(previous, next) {
9661
9661
  }
9662
9662
  return cues;
9663
9663
  }
9664
+ var AUTOPLAY_UNLOCK_EVENTS = ["pointerdown", "touchend", "click", "keydown"];
9665
+ function isAutoplayBlocked(cause) {
9666
+ return typeof DOMException !== "undefined" && cause instanceof DOMException && cause.name === "NotAllowedError";
9667
+ }
9668
+ function supportsSinkSelection(element) {
9669
+ return "setSinkId" in element && typeof element.setSinkId === "function";
9670
+ }
9664
9671
  function soundSourceFor(cue, canPlay) {
9665
9672
  const asset = CHALK_SOUND_ASSETS[cue];
9666
9673
  return canPlay(asset.opus.mimeType) ? asset.opus.url : asset.mp3.url;
9667
9674
  }
9668
- function createSoundPlayer({ volume = 0.6, onError } = {}) {
9675
+ function createSoundPlayer({ volume = 0.6, outputDeviceId, onError, unlockTarget = typeof window === "undefined" ? null : window } = {}) {
9669
9676
  const elements = /* @__PURE__ */ new Map();
9670
9677
  const lastPlayed = /* @__PURE__ */ new Map();
9678
+ const blocked = /* @__PURE__ */ new Set();
9679
+ const playbackInFlight = /* @__PURE__ */ new Set();
9671
9680
  const probe = typeof Audio === "undefined" ? null : new Audio();
9672
9681
  const canPlay = (mimeType) => probe !== null && probe.canPlayType(mimeType) !== "";
9682
+ let listeningForUnlock = false;
9683
+ const removeUnlockListeners = () => {
9684
+ if (!unlockTarget || !listeningForUnlock) return;
9685
+ for (const event of AUTOPLAY_UNLOCK_EVENTS) unlockTarget.removeEventListener(event, retryBlocked, true);
9686
+ listeningForUnlock = false;
9687
+ };
9688
+ const addUnlockListeners = () => {
9689
+ if (!unlockTarget || listeningForUnlock) return;
9690
+ for (const event of AUTOPLAY_UNLOCK_EVENTS) unlockTarget.addEventListener(event, retryBlocked, { capture: true, passive: true });
9691
+ listeningForUnlock = true;
9692
+ };
9693
+ const reportPlaybackFailure = (cue, cause) => {
9694
+ if (isAutoplayBlocked(cause)) {
9695
+ blocked.add(cue);
9696
+ addUnlockListeners();
9697
+ }
9698
+ onError?.(cue, cause);
9699
+ };
9700
+ const playElement = (cue, element) => {
9701
+ if (playbackInFlight.has(cue)) return;
9702
+ playbackInFlight.add(cue);
9703
+ element.currentTime = 0;
9704
+ element.play().then(
9705
+ () => {
9706
+ playbackInFlight.delete(cue);
9707
+ blocked.delete(cue);
9708
+ if (blocked.size === 0) removeUnlockListeners();
9709
+ },
9710
+ (cause) => {
9711
+ playbackInFlight.delete(cue);
9712
+ reportPlaybackFailure(cue, cause);
9713
+ }
9714
+ );
9715
+ };
9716
+ function retryBlocked() {
9717
+ for (const cue of blocked) {
9718
+ const element = elements.get(cue);
9719
+ if (element) playElement(cue, element);
9720
+ }
9721
+ }
9673
9722
  return {
9674
9723
  play: (cue) => {
9675
9724
  if (probe === null) return;
@@ -9681,12 +9730,15 @@ function createSoundPlayer({ volume = 0.6, onError } = {}) {
9681
9730
  element = new Audio(soundSourceFor(cue, canPlay));
9682
9731
  element.preload = "auto";
9683
9732
  element.volume = volume;
9733
+ if (outputDeviceId && supportsSinkSelection(element) && element.sinkId !== outputDeviceId) void element.setSinkId(outputDeviceId).catch(() => void 0);
9684
9734
  elements.set(cue, element);
9685
9735
  }
9686
- element.currentTime = 0;
9687
- element.play().catch((cause) => onError?.(cue, cause));
9736
+ playElement(cue, element);
9688
9737
  },
9689
9738
  dispose: () => {
9739
+ removeUnlockListeners();
9740
+ blocked.clear();
9741
+ playbackInFlight.clear();
9690
9742
  for (const element of elements.values()) {
9691
9743
  element.pause();
9692
9744
  element.removeAttribute("src");
@@ -9697,13 +9749,14 @@ function createSoundPlayer({ volume = 0.6, onError } = {}) {
9697
9749
  }
9698
9750
 
9699
9751
  // src/internal/useSoundCues.ts
9700
- function useSoundCues(enabled, createPlayer = () => createSoundPlayer()) {
9752
+ function useSoundCues(enabled, createPlayer = createSoundPlayer) {
9701
9753
  const client = useSpaceClient();
9754
+ const outputDeviceId = useMedia().selection.speaker ?? void 0;
9702
9755
  const createPlayerRef = useRef20(createPlayer);
9703
9756
  createPlayerRef.current = createPlayer;
9704
9757
  useEffect32(() => {
9705
9758
  if (!enabled) return;
9706
- const player = createPlayerRef.current();
9759
+ const player = createPlayerRef.current({ outputDeviceId });
9707
9760
  let previous = client.getSnapshot();
9708
9761
  const unsubscribe = client.subscribe(() => {
9709
9762
  const next = client.getSnapshot();
@@ -9714,7 +9767,7 @@ function useSoundCues(enabled, createPlayer = () => createSoundPlayer()) {
9714
9767
  unsubscribe();
9715
9768
  player.dispose();
9716
9769
  };
9717
- }, [client, enabled]);
9770
+ }, [client, enabled, outputDeviceId]);
9718
9771
  }
9719
9772
 
9720
9773
  // src/components/composite/ReactionPicker.tsx
@@ -35,7 +35,7 @@ import {
35
35
  SettingsPanel,
36
36
  Stage,
37
37
  buildStageItems
38
- } from "../chunk-FTWI75A2.js";
38
+ } from "../chunk-4SU35WSH.js";
39
39
  import "../chunk-ZEZLA34O.js";
40
40
  export {
41
41
  AdmissionPanel,
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  useSelf,
36
36
  useSpaceClient,
37
37
  useWhiteboard
38
- } from "./chunk-FTWI75A2.js";
38
+ } from "./chunk-4SU35WSH.js";
39
39
  import "./chunk-ZEZLA34O.js";
40
40
  export {
41
41
  COSMIC_CHALK_THEME,
@@ -15,14 +15,18 @@ export interface SoundPlayer {
15
15
  readonly play: (cue: SoundCue) => void;
16
16
  readonly dispose: () => void;
17
17
  }
18
+ type SoundUnlockTarget = Pick<EventTarget, "addEventListener" | "removeEventListener">;
18
19
  /** Picks the Opus file when the browser can decode it, otherwise the MP3. */
19
20
  export declare function soundSourceFor(cue: SoundCue, canPlay: (mimeType: string) => boolean): string;
20
21
  /**
21
22
  * Lazily creates one `HTMLAudioElement` per cue and replays it from the start. Playback failures
22
23
  * (autoplay policy before the first gesture, network) are reported through `onError` and never thrown.
23
24
  */
24
- export declare function createSoundPlayer({ volume, onError }?: {
25
+ export declare function createSoundPlayer({ volume, outputDeviceId, onError, unlockTarget }?: {
25
26
  volume?: number;
27
+ outputDeviceId?: string;
26
28
  onError?: (cue: SoundCue, cause: unknown) => void;
29
+ unlockTarget?: SoundUnlockTarget | null;
27
30
  }): SoundPlayer;
31
+ export {};
28
32
  //# sourceMappingURL=sound-cues.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sound-cues.d.ts","sourceRoot":"","sources":["../../src/internal/sound-cues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAEvD,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,iEAAgG,CAAC;AAEjI,8GAA8G;AAC9G,eAAO,MAAM,eAAe,MAAM,CAAC;AAMnC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,QAAQ,EAAE,CA2B/F;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAG5F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAY,EAAE,OAAO,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAO,GAAG,WAAW,CA8BrJ"}
1
+ {"version":3,"file":"sound-cues.d.ts","sourceRoot":"","sources":["../../src/internal/sound-cues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAEvD,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,iEAAgG,CAAC;AAEjI,8GAA8G;AAC9G,eAAO,MAAM,eAAe,MAAM,CAAC;AAMnC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,QAAQ,EAAE,CA2B/F;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AASD,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,CAAC;AAUvF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAG5F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAY,EAAE,cAAc,EAAE,OAAO,EAAE,YAA4D,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;CAAO,GAAG,WAAW,CAgFrS"}
@@ -1,7 +1,9 @@
1
1
  import { type SoundPlayer } from "./sound-cues";
2
2
  /**
3
3
  * Plays the join / leave / message / hand-raise / reaction cues for snapshot changes on the
4
- * current client while `enabled`. Autoplay refusals before the first user gesture are ignored.
4
+ * current client while `enabled`. Autoplay refusals are retried after the next user gesture.
5
5
  */
6
- export declare function useSoundCues(enabled: boolean, createPlayer?: () => SoundPlayer): void;
6
+ export declare function useSoundCues(enabled: boolean, createPlayer?: (options: {
7
+ readonly outputDeviceId?: string;
8
+ }) => SoundPlayer): void;
7
9
  //# sourceMappingURL=useSoundCues.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSoundCues.d.ts","sourceRoot":"","sources":["../../src/internal/useSoundCues.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAElF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,GAAE,MAAM,WAAuC,GAAG,IAAI,CAmBhH"}
1
+ {"version":3,"file":"useSoundCues.d.ts","sourceRoot":"","sources":["../../src/internal/useSoundCues.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAElF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,GAAE,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,WAA+B,GAAG,IAAI,CAoBrJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q9labsai/chalk-react",
3
- "version": "4.1.2",
3
+ "version": "4.1.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,10 +61,10 @@
61
61
  "@base-ui/react": "^1.7.0",
62
62
  "@hugeicons/core-free-icons": "^4.2.3",
63
63
  "@hugeicons/react": "^1.1.9",
64
- "@q9labsai/chalk-client": "^4.1.2",
65
- "@q9labsai/chalk-ui": "^4.1.2",
66
- "@q9labsai/chalk-whiteboard": "^4.1.2",
67
- "@q9labsai/facehash": "^4.1.2",
64
+ "@q9labsai/chalk-client": "^4.1.3",
65
+ "@q9labsai/chalk-ui": "^4.1.3",
66
+ "@q9labsai/chalk-whiteboard": "^4.1.3",
67
+ "@q9labsai/facehash": "^4.1.3",
68
68
  "class-variance-authority": "^0.7.1",
69
69
  "clsx": "^2.1.1",
70
70
  "sonner": "^2.0.8",