@adea-ai/audio 0.22.1 → 0.23.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.
- package/README.md +1 -1
- package/dist/config.js +6 -5
- package/dist/controller.js +117 -144
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/scene-music.js +8 -5
- package/dist/{react.d.ts → sound.d.ts} +3 -5
- package/dist/sound.js +99 -0
- package/package.json +10 -8
- package/src/index.ts +1 -1
- package/src/{react.tsx → sound.tsx} +46 -36
- package/tsconfig.json +2 -1
- package/dist/react.js +0 -67
package/README.md
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
//#region src/config.ts
|
|
1
2
|
/** Optional soundtrack hooks for Adea. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
3
|
+
var AUDIO_BASE_URL = "/assets/audio";
|
|
4
|
+
var MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`;
|
|
5
|
+
var MUSIC_FILES = { silent: "" };
|
|
6
|
+
//#endregion
|
|
7
|
+
export { AUDIO_BASE_URL, MUSIC_DIR, MUSIC_FILES };
|
package/dist/controller.js
CHANGED
|
@@ -1,152 +1,125 @@
|
|
|
1
|
-
import { MUSIC_DIR, MUSIC_FILES } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { MUSIC_DIR, MUSIC_FILES } from "./config.js";
|
|
2
|
+
//#region src/controller.ts
|
|
3
|
+
var MUSIC_MUTE_KEY = "adea:audio:music-muted";
|
|
4
|
+
var LEGACY_MUTE_KEY = "adea:audio:muted";
|
|
5
|
+
var DEFAULT_MUSIC_VOLUME = .8;
|
|
6
|
+
var FADE_STEP_MS = 30;
|
|
6
7
|
function clampVolume(value) {
|
|
7
|
-
|
|
8
|
+
return value <= 0 ? 0 : value > 1 ? 1 : value;
|
|
8
9
|
}
|
|
9
10
|
function readStorage(key) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
11
|
+
if (typeof window === "undefined") return null;
|
|
12
|
+
try {
|
|
13
|
+
return window.localStorage.getItem(key);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
18
17
|
}
|
|
19
18
|
function writeStorage(key, muted) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else
|
|
26
|
-
window.localStorage.removeItem(key);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// Storage can be unavailable; the in-memory mute still applies.
|
|
30
|
-
}
|
|
19
|
+
if (typeof window === "undefined") return;
|
|
20
|
+
try {
|
|
21
|
+
if (muted) window.localStorage.setItem(key, "1");
|
|
22
|
+
else window.localStorage.removeItem(key);
|
|
23
|
+
} catch {}
|
|
31
24
|
}
|
|
32
25
|
function loadMusicMuted() {
|
|
33
|
-
|
|
26
|
+
return readStorage(MUSIC_MUTE_KEY) === "1" || readStorage(LEGACY_MUTE_KEY) === "1";
|
|
34
27
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const element = this.musicElements.get(desired);
|
|
134
|
-
if (element?.paused)
|
|
135
|
-
void element.play().catch(() => undefined);
|
|
136
|
-
}
|
|
137
|
-
fadeMusic(element, target, durationMs, onDone) {
|
|
138
|
-
const start = element.volume;
|
|
139
|
-
const startTime = performance.now();
|
|
140
|
-
const interval = window.setInterval(() => {
|
|
141
|
-
const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1));
|
|
142
|
-
element.volume = start + (target - start) * progress;
|
|
143
|
-
if (progress >= 1) {
|
|
144
|
-
window.clearInterval(interval);
|
|
145
|
-
this.activeFades.delete(interval);
|
|
146
|
-
onDone?.();
|
|
147
|
-
}
|
|
148
|
-
}, FADE_STEP_MS);
|
|
149
|
-
this.activeFades.add(interval);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
export const soundController = new SoundController();
|
|
28
|
+
var SoundController = class {
|
|
29
|
+
musicElements = /* @__PURE__ */ new Map();
|
|
30
|
+
desiredMusic = null;
|
|
31
|
+
currentMusic = null;
|
|
32
|
+
activeFades = /* @__PURE__ */ new Set();
|
|
33
|
+
_musicMuted;
|
|
34
|
+
constructor() {
|
|
35
|
+
this._musicMuted = loadMusicMuted();
|
|
36
|
+
}
|
|
37
|
+
get musicMuted() {
|
|
38
|
+
return this._musicMuted;
|
|
39
|
+
}
|
|
40
|
+
/** Retry queued playback after a user gesture. */
|
|
41
|
+
async unlock() {
|
|
42
|
+
this.startDesiredMusic();
|
|
43
|
+
}
|
|
44
|
+
/** Start or switch to a looping soundtrack. */
|
|
45
|
+
playMusic(id, options = {}) {
|
|
46
|
+
if (!MUSIC_FILES[id]) return;
|
|
47
|
+
this.desiredMusic = id;
|
|
48
|
+
this.prepareMusicElement(id);
|
|
49
|
+
if (!this.musicMuted) this.startMusic(id, options);
|
|
50
|
+
}
|
|
51
|
+
setMusicMuted(muted) {
|
|
52
|
+
this._musicMuted = muted;
|
|
53
|
+
writeStorage(MUSIC_MUTE_KEY, muted);
|
|
54
|
+
for (const element of this.musicElements.values()) if (muted) element.pause();
|
|
55
|
+
if (!muted) this.startDesiredMusic();
|
|
56
|
+
}
|
|
57
|
+
toggleMusicMute() {
|
|
58
|
+
this.setMusicMuted(!this._musicMuted);
|
|
59
|
+
return this._musicMuted;
|
|
60
|
+
}
|
|
61
|
+
prepareMusicElement(id) {
|
|
62
|
+
if (this.musicElements.has(id)) return;
|
|
63
|
+
const element = new Audio(`${MUSIC_DIR}/${MUSIC_FILES[id]}`);
|
|
64
|
+
element.loop = true;
|
|
65
|
+
element.preload = "none";
|
|
66
|
+
this.musicElements.set(id, element);
|
|
67
|
+
}
|
|
68
|
+
startMusic(id, options = {}) {
|
|
69
|
+
if (this.currentMusic === id) {
|
|
70
|
+
const active = this.musicElements.get(id);
|
|
71
|
+
if (active?.paused) active.play().catch(() => void 0);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const fadeMs = options.fadeMs ?? 500;
|
|
75
|
+
const target = clampVolume(options.volume ?? DEFAULT_MUSIC_VOLUME);
|
|
76
|
+
const previousId = this.currentMusic;
|
|
77
|
+
const previous = previousId ? this.musicElements.get(previousId) ?? null : null;
|
|
78
|
+
this.currentMusic = id;
|
|
79
|
+
const element = this.musicElements.get(id);
|
|
80
|
+
if (!element) return;
|
|
81
|
+
element.volume = 0;
|
|
82
|
+
element.currentTime = 0;
|
|
83
|
+
element.play().then(() => {
|
|
84
|
+
if (this.currentMusic !== id) {
|
|
85
|
+
element.pause();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (previous) this.fadeMusic(previous, 0, fadeMs, () => {
|
|
89
|
+
previous.pause();
|
|
90
|
+
previous.currentTime = 0;
|
|
91
|
+
});
|
|
92
|
+
this.fadeMusic(element, target, fadeMs);
|
|
93
|
+
}).catch(() => {
|
|
94
|
+
if (previous) this.currentMusic = previousId;
|
|
95
|
+
else this.currentMusic = null;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
startDesiredMusic() {
|
|
99
|
+
if (this.musicMuted || !this.desiredMusic) return;
|
|
100
|
+
const desired = this.desiredMusic;
|
|
101
|
+
if (this.currentMusic !== desired) {
|
|
102
|
+
this.startMusic(desired);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const element = this.musicElements.get(desired);
|
|
106
|
+
if (element?.paused) element.play().catch(() => void 0);
|
|
107
|
+
}
|
|
108
|
+
fadeMusic(element, target, durationMs, onDone) {
|
|
109
|
+
const start = element.volume;
|
|
110
|
+
const startTime = performance.now();
|
|
111
|
+
const interval = window.setInterval(() => {
|
|
112
|
+
const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1));
|
|
113
|
+
element.volume = start + (target - start) * progress;
|
|
114
|
+
if (progress >= 1) {
|
|
115
|
+
window.clearInterval(interval);
|
|
116
|
+
this.activeFades.delete(interval);
|
|
117
|
+
onDone?.();
|
|
118
|
+
}
|
|
119
|
+
}, FADE_STEP_MS);
|
|
120
|
+
this.activeFades.add(interval);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var soundController = new SoundController();
|
|
124
|
+
//#endregion
|
|
125
|
+
export { SoundController, soundController };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from './config';
|
|
2
2
|
export { SoundController, soundController, type MusicOptions } from './controller';
|
|
3
3
|
export { musicForScene, sceneMusicTracks } from './scene-music';
|
|
4
|
-
export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from './
|
|
4
|
+
export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from './sound';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export { MusicToggle, SoundProvider, useSceneMusic, useSound
|
|
1
|
+
import { AUDIO_BASE_URL, MUSIC_FILES } from "./config.js";
|
|
2
|
+
import { SoundController, soundController } from "./controller.js";
|
|
3
|
+
import { musicForScene, sceneMusicTracks } from "./scene-music.js";
|
|
4
|
+
import { MusicToggle, SoundProvider, useSceneMusic, useSound } from "./sound.js";
|
|
5
|
+
export { AUDIO_BASE_URL, MUSIC_FILES, MusicToggle, SoundController, SoundProvider, musicForScene, sceneMusicTracks, soundController, useSceneMusic, useSound };
|
package/dist/scene-music.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
//#region src/scene-music.ts
|
|
1
2
|
/** Scene route id -> optional track. Deployments can add tracks without changing scene code. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var sceneMusicTracks = {
|
|
4
|
+
"hq-home": "silent",
|
|
5
|
+
"hq-work": "silent"
|
|
5
6
|
};
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
function musicForScene(sceneId) {
|
|
8
|
+
return sceneMusicTracks[sceneId ?? ""] ?? "silent";
|
|
8
9
|
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { musicForScene, sceneMusicTracks };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ParentProps } from 'solid-js';
|
|
2
2
|
import { soundController, type MusicOptions } from './controller';
|
|
3
3
|
import type { MusicId } from './config';
|
|
4
4
|
export type SoundContextValue = {
|
|
@@ -8,9 +8,7 @@ export type SoundContextValue = {
|
|
|
8
8
|
toggleMusicMute: () => void;
|
|
9
9
|
playMusic: (id: MusicId, options?: MusicOptions) => void;
|
|
10
10
|
};
|
|
11
|
-
export declare function SoundProvider(
|
|
12
|
-
children: ReactNode;
|
|
13
|
-
}): import("react").JSX.Element;
|
|
11
|
+
export declare function SoundProvider(props: ParentProps): import("solid-js").JSX.Element;
|
|
14
12
|
export declare function useSound(): SoundContextValue;
|
|
15
13
|
export declare function useSceneMusic(sceneId: string | null | undefined): void;
|
|
16
|
-
export declare function MusicToggle(): import("
|
|
14
|
+
export declare function MusicToggle(): import("solid-js").JSX.Element;
|
package/dist/sound.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { soundController } from "./controller.js";
|
|
2
|
+
import { musicForScene } from "./scene-music.js";
|
|
3
|
+
import { className, createComponent, delegateEvents, effect, insert, memo, setAttribute, template } from "solid-js/web";
|
|
4
|
+
import { Music, Music2 } from "lucide-solid";
|
|
5
|
+
import { createContext, createEffect, createMemo, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
|
|
6
|
+
//#region src/sound.tsx
|
|
7
|
+
var _tmpl$ = /*#__PURE__*/ template(`<button type=button>`);
|
|
8
|
+
var SoundContext = createContext(null);
|
|
9
|
+
function SoundProvider(props) {
|
|
10
|
+
const [musicMuted, setMusicMuted] = createSignal(false);
|
|
11
|
+
const [ready, setReady] = createSignal(false);
|
|
12
|
+
onMount(() => {
|
|
13
|
+
let mounted = true;
|
|
14
|
+
queueMicrotask(() => {
|
|
15
|
+
if (mounted) setMusicMuted(soundController.musicMuted);
|
|
16
|
+
});
|
|
17
|
+
const onGesture = () => {
|
|
18
|
+
soundController.unlock().then(() => {
|
|
19
|
+
if (mounted) setReady(true);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
window.addEventListener("pointerdown", onGesture, { capture: true });
|
|
23
|
+
window.addEventListener("keydown", onGesture, { capture: true });
|
|
24
|
+
window.addEventListener("touchstart", onGesture, { capture: true });
|
|
25
|
+
if (new URLSearchParams(window.location.search).has("debug")) window.__agentHqSound = soundController;
|
|
26
|
+
onCleanup(() => {
|
|
27
|
+
mounted = false;
|
|
28
|
+
window.removeEventListener("pointerdown", onGesture, { capture: true });
|
|
29
|
+
window.removeEventListener("keydown", onGesture, { capture: true });
|
|
30
|
+
window.removeEventListener("touchstart", onGesture, { capture: true });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const value = createMemo(() => ({
|
|
34
|
+
controller: soundController,
|
|
35
|
+
ready: ready(),
|
|
36
|
+
musicMuted: musicMuted(),
|
|
37
|
+
toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
|
|
38
|
+
playMusic: (id, options) => soundController.playMusic(id, options)
|
|
39
|
+
}));
|
|
40
|
+
return createComponent(SoundContext.Provider, {
|
|
41
|
+
get value() {
|
|
42
|
+
return value();
|
|
43
|
+
},
|
|
44
|
+
get children() {
|
|
45
|
+
return props.children;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function useSound() {
|
|
50
|
+
const value = useContext(SoundContext);
|
|
51
|
+
if (!value) throw new Error("useSound must be used within <SoundProvider>");
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function useSceneMusic(sceneId) {
|
|
55
|
+
createEffect(on(() => sceneId, (scene) => {
|
|
56
|
+
const timeout = window.setTimeout(() => {
|
|
57
|
+
soundController.playMusic(musicForScene(scene));
|
|
58
|
+
}, 1500);
|
|
59
|
+
onCleanup(() => window.clearTimeout(timeout));
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
function MusicButton(props) {
|
|
63
|
+
return (() => {
|
|
64
|
+
var _el$ = _tmpl$();
|
|
65
|
+
_el$.$$click = () => props.onToggle();
|
|
66
|
+
insert(_el$, (() => {
|
|
67
|
+
var _c$ = memo(() => !!props.muted);
|
|
68
|
+
return () => _c$() ? createComponent(Music, {
|
|
69
|
+
"class": "size-5",
|
|
70
|
+
"aria-hidden": "true"
|
|
71
|
+
}) : createComponent(Music2, {
|
|
72
|
+
"class": "size-5",
|
|
73
|
+
"aria-hidden": "true"
|
|
74
|
+
});
|
|
75
|
+
})());
|
|
76
|
+
effect((_p$) => {
|
|
77
|
+
var _v$ = props.muted ? "Unmute music" : "Mute music", _v$2 = props.muted, _v$3 = `inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${props.muted ? "text-muted-foreground hover:text-foreground" : "bg-primary text-primary-foreground"}`;
|
|
78
|
+
_v$ !== _p$.e && setAttribute(_el$, "aria-label", _p$.e = _v$);
|
|
79
|
+
_v$2 !== _p$.t && setAttribute(_el$, "aria-pressed", _p$.t = _v$2);
|
|
80
|
+
_v$3 !== _p$.a && className(_el$, _p$.a = _v$3);
|
|
81
|
+
return _p$;
|
|
82
|
+
}, {
|
|
83
|
+
e: void 0,
|
|
84
|
+
t: void 0,
|
|
85
|
+
a: void 0
|
|
86
|
+
});
|
|
87
|
+
return _el$;
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
90
|
+
function MusicToggle() {
|
|
91
|
+
const { musicMuted, toggleMusicMute } = useSound();
|
|
92
|
+
return createComponent(MusicButton, {
|
|
93
|
+
muted: musicMuted,
|
|
94
|
+
onToggle: toggleMusicMute
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
delegateEvents(["click"]);
|
|
98
|
+
//#endregion
|
|
99
|
+
export { MusicToggle, SoundProvider, useSceneMusic, useSound };
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/audio",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Optional, lazy music controller and
|
|
3
|
+
"version": "0.23.0",
|
|
4
|
+
"description": "Optional, lazy music controller and Solid bindings for Adea. Audio is non-blocking so scene loading never waits on sound assets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"audio",
|
|
7
7
|
"music",
|
|
8
|
-
"
|
|
8
|
+
"solid",
|
|
9
9
|
"threejs",
|
|
10
10
|
"workspace"
|
|
11
11
|
],
|
|
@@ -27,21 +27,23 @@
|
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
|
+
"solid": "./src/index.ts",
|
|
30
31
|
"development": "./src/index.ts",
|
|
31
32
|
"import": "./dist/index.js"
|
|
32
33
|
}
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"lint": "oxlint",
|
|
36
|
-
"build": "tsc -p tsconfig.json",
|
|
37
|
-
"dev": "
|
|
37
|
+
"build": "vite build && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
38
|
+
"dev": "vite build --watch",
|
|
38
39
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"lucide-
|
|
42
|
-
"
|
|
42
|
+
"lucide-solid": "^1.45.0",
|
|
43
|
+
"solid-js": "^1.9.15"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"
|
|
46
|
+
"vite": "^8.0.14",
|
|
47
|
+
"vite-plugin-solid": "^2.11.14"
|
|
46
48
|
}
|
|
47
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { Music, Music2 } from 'lucide-solid'
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createMemo,
|
|
5
|
+
createSignal,
|
|
6
|
+
on,
|
|
7
|
+
onCleanup,
|
|
8
|
+
onMount,
|
|
9
|
+
createEffect,
|
|
10
|
+
useContext,
|
|
11
|
+
type ParentProps,
|
|
12
|
+
} from 'solid-js'
|
|
5
13
|
import { soundController, type MusicOptions } from './controller'
|
|
6
14
|
import { musicForScene } from './scene-music'
|
|
7
15
|
import type { MusicId } from './config'
|
|
@@ -16,11 +24,11 @@ export type SoundContextValue = {
|
|
|
16
24
|
|
|
17
25
|
const SoundContext = createContext<SoundContextValue | null>(null)
|
|
18
26
|
|
|
19
|
-
export function SoundProvider(
|
|
20
|
-
const [musicMuted, setMusicMuted] =
|
|
21
|
-
const [ready, setReady] =
|
|
27
|
+
export function SoundProvider(props: ParentProps) {
|
|
28
|
+
const [musicMuted, setMusicMuted] = createSignal(false)
|
|
29
|
+
const [ready, setReady] = createSignal(false)
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
onMount(() => {
|
|
24
32
|
let mounted = true
|
|
25
33
|
queueMicrotask(() => {
|
|
26
34
|
if (mounted) setMusicMuted(soundController.musicMuted)
|
|
@@ -38,26 +46,23 @@ export function SoundProvider({ children }: { children: ReactNode }) {
|
|
|
38
46
|
;(window as unknown as { __agentHqSound?: typeof soundController }).__agentHqSound =
|
|
39
47
|
soundController
|
|
40
48
|
}
|
|
41
|
-
|
|
49
|
+
onCleanup(() => {
|
|
42
50
|
mounted = false
|
|
43
51
|
window.removeEventListener('pointerdown', onGesture, { capture: true })
|
|
44
52
|
window.removeEventListener('keydown', onGesture, { capture: true })
|
|
45
53
|
window.removeEventListener('touchstart', onGesture, { capture: true })
|
|
46
|
-
}
|
|
47
|
-
}
|
|
54
|
+
})
|
|
55
|
+
})
|
|
48
56
|
|
|
49
|
-
const value =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}),
|
|
57
|
-
[musicMuted, ready]
|
|
58
|
-
)
|
|
57
|
+
const value = createMemo<SoundContextValue>(() => ({
|
|
58
|
+
controller: soundController,
|
|
59
|
+
ready: ready(),
|
|
60
|
+
musicMuted: musicMuted(),
|
|
61
|
+
toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
|
|
62
|
+
playMusic: (id, options) => soundController.playMusic(id, options),
|
|
63
|
+
}))
|
|
59
64
|
|
|
60
|
-
return <SoundContext.Provider value={value}>{children}</SoundContext.Provider>
|
|
65
|
+
return <SoundContext.Provider value={value()}>{props.children}</SoundContext.Provider>
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
export function useSound(): SoundContextValue {
|
|
@@ -67,27 +72,32 @@ export function useSound(): SoundContextValue {
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
export function useSceneMusic(sceneId: string | null | undefined): void {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
createEffect(
|
|
76
|
+
on(
|
|
77
|
+
() => sceneId,
|
|
78
|
+
(scene) => {
|
|
79
|
+
const timeout = window.setTimeout(() => {
|
|
80
|
+
soundController.playMusic(musicForScene(scene))
|
|
81
|
+
}, 1500)
|
|
82
|
+
onCleanup(() => window.clearTimeout(timeout))
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
)
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
function MusicButton(
|
|
88
|
+
function MusicButton(props: { muted: boolean; onToggle: () => void }) {
|
|
79
89
|
return (
|
|
80
90
|
<button
|
|
81
91
|
type="button"
|
|
82
|
-
aria-label={muted ? 'Unmute music' : 'Mute music'}
|
|
83
|
-
aria-pressed={muted}
|
|
84
|
-
onClick={onToggle}
|
|
85
|
-
|
|
92
|
+
aria-label={props.muted ? 'Unmute music' : 'Mute music'}
|
|
93
|
+
aria-pressed={props.muted}
|
|
94
|
+
onClick={() => props.onToggle()}
|
|
95
|
+
class={`inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${props.muted ? 'text-muted-foreground hover:text-foreground' : 'bg-primary text-primary-foreground'}`}
|
|
86
96
|
>
|
|
87
|
-
{muted ? (
|
|
88
|
-
<Music
|
|
97
|
+
{props.muted ? (
|
|
98
|
+
<Music class="size-5" aria-hidden="true" />
|
|
89
99
|
) : (
|
|
90
|
-
<Music2
|
|
100
|
+
<Music2 class="size-5" aria-hidden="true" />
|
|
91
101
|
)}
|
|
92
102
|
</button>
|
|
93
103
|
)
|
package/tsconfig.json
CHANGED
package/dist/react.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import { Music, Music2 } from 'lucide-react';
|
|
5
|
-
import { soundController } from './controller';
|
|
6
|
-
import { musicForScene } from './scene-music';
|
|
7
|
-
const SoundContext = createContext(null);
|
|
8
|
-
export function SoundProvider({ children }) {
|
|
9
|
-
const [musicMuted, setMusicMuted] = useState(false);
|
|
10
|
-
const [ready, setReady] = useState(false);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
let mounted = true;
|
|
13
|
-
queueMicrotask(() => {
|
|
14
|
-
if (mounted)
|
|
15
|
-
setMusicMuted(soundController.musicMuted);
|
|
16
|
-
});
|
|
17
|
-
const onGesture = () => {
|
|
18
|
-
void soundController.unlock().then(() => {
|
|
19
|
-
if (mounted)
|
|
20
|
-
setReady(true);
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
window.addEventListener('pointerdown', onGesture, { capture: true });
|
|
24
|
-
window.addEventListener('keydown', onGesture, { capture: true });
|
|
25
|
-
window.addEventListener('touchstart', onGesture, { capture: true });
|
|
26
|
-
if (new URLSearchParams(window.location.search).has('debug')) {
|
|
27
|
-
;
|
|
28
|
-
window.__agentHqSound =
|
|
29
|
-
soundController;
|
|
30
|
-
}
|
|
31
|
-
return () => {
|
|
32
|
-
mounted = false;
|
|
33
|
-
window.removeEventListener('pointerdown', onGesture, { capture: true });
|
|
34
|
-
window.removeEventListener('keydown', onGesture, { capture: true });
|
|
35
|
-
window.removeEventListener('touchstart', onGesture, { capture: true });
|
|
36
|
-
};
|
|
37
|
-
}, []);
|
|
38
|
-
const value = useMemo(() => ({
|
|
39
|
-
controller: soundController,
|
|
40
|
-
ready,
|
|
41
|
-
musicMuted,
|
|
42
|
-
toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
|
|
43
|
-
playMusic: (id, options) => soundController.playMusic(id, options),
|
|
44
|
-
}), [musicMuted, ready]);
|
|
45
|
-
return _jsx(SoundContext.Provider, { value: value, children: children });
|
|
46
|
-
}
|
|
47
|
-
export function useSound() {
|
|
48
|
-
const value = useContext(SoundContext);
|
|
49
|
-
if (!value)
|
|
50
|
-
throw new Error('useSound must be used within <SoundProvider>');
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
export function useSceneMusic(sceneId) {
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const timeout = window.setTimeout(() => {
|
|
56
|
-
soundController.playMusic(musicForScene(sceneId));
|
|
57
|
-
}, 1500);
|
|
58
|
-
return () => window.clearTimeout(timeout);
|
|
59
|
-
}, [sceneId]);
|
|
60
|
-
}
|
|
61
|
-
function MusicButton({ muted, onToggle }) {
|
|
62
|
-
return (_jsx("button", { type: "button", "aria-label": muted ? 'Unmute music' : 'Mute music', "aria-pressed": muted, onClick: onToggle, className: `inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${muted ? 'text-muted-foreground hover:text-foreground' : 'bg-primary text-primary-foreground'}`, children: muted ? (_jsx(Music, { className: "size-5", "aria-hidden": "true" })) : (_jsx(Music2, { className: "size-5", "aria-hidden": "true" })) }));
|
|
63
|
-
}
|
|
64
|
-
export function MusicToggle() {
|
|
65
|
-
const { musicMuted, toggleMusicMute } = useSound();
|
|
66
|
-
return _jsx(MusicButton, { muted: musicMuted, onToggle: toggleMusicMute });
|
|
67
|
-
}
|