@thewhateverapp/tile-sdk 0.20.0 → 0.20.2

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.
@@ -0,0 +1,48 @@
1
+ type HowlStatic = any;
2
+ export interface SoundOptions {
3
+ volume?: number;
4
+ loop?: boolean;
5
+ rate?: number;
6
+ sprite?: Record<string, [number, number]>;
7
+ onload?: () => void;
8
+ onloaderror?: (id: number, error: any) => void;
9
+ onplay?: () => void;
10
+ onend?: () => void;
11
+ onstop?: () => void;
12
+ onpause?: () => void;
13
+ }
14
+ export interface SoundController {
15
+ play: (spriteId?: string) => void;
16
+ pause: () => void;
17
+ stop: () => void;
18
+ volume: (vol?: number) => number | void;
19
+ rate: (rate?: number) => number | void;
20
+ loop: (loop?: boolean) => boolean | void;
21
+ playing: () => boolean;
22
+ duration: () => number;
23
+ state: () => 'unloaded' | 'loading' | 'loaded';
24
+ unload: () => void;
25
+ }
26
+ /**
27
+ * Hook for playing sound effects
28
+ *
29
+ * @param src - URL to the audio file
30
+ * @param options - Howler options
31
+ * @returns Sound controller
32
+ */
33
+ export declare function useSound(src: string, options?: SoundOptions): SoundController;
34
+ /**
35
+ * Hook for playing background music with auto-play on mount
36
+ *
37
+ * @param src - URL to the music file
38
+ * @param options - Howler options (loop defaults to true for music)
39
+ * @returns Sound controller
40
+ */
41
+ export declare function useMusic(src: string, options?: SoundOptions): SoundController;
42
+ /**
43
+ * Re-export Howl for advanced use cases
44
+ * Returns null during SSR, the Howl class after client-side load
45
+ */
46
+ export declare function getHowl(): Promise<HowlStatic | null>;
47
+ export {};
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AA4BA,KAAK,UAAU,GAAG,GAAG,CAAC;AA6BtB,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACxC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACvC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,eAAe,CAoGjF;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,eAAe,CAgBjF;AAED;;;GAGG;AACH,wBAAsB,OAAO,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAI1D"}
@@ -0,0 +1,182 @@
1
+ 'use client';
2
+ /**
3
+ * Audio SDK for tile-sdk
4
+ *
5
+ * SSR-safe wrapper around Howler.js with React hooks for easy audio management.
6
+ * Handles dynamic import, lifecycle management, and provides a clean API.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { useSound, useMusic } from '@thewhateverapp/tile-sdk/audio';
11
+ *
12
+ * function MyGame() {
13
+ * const jumpSound = useSound('/sounds/jump.mp3');
14
+ * const bgMusic = useMusic('/music/theme.mp3', { volume: 0.3, loop: true });
15
+ *
16
+ * return (
17
+ * <button onClick={() => jumpSound.play()}>
18
+ * Jump
19
+ * </button>
20
+ * );
21
+ * }
22
+ * ```
23
+ */
24
+ import { useEffect, useRef, useState } from 'react';
25
+ let Howl = null;
26
+ let howlerLoaded = false;
27
+ let howlerPromise = null;
28
+ /**
29
+ * Load Howler dynamically (client-side only)
30
+ */
31
+ async function loadHowler() {
32
+ if (typeof window === 'undefined') {
33
+ throw new Error('Howler can only be loaded in the browser');
34
+ }
35
+ if (howlerLoaded)
36
+ return;
37
+ if (howlerPromise) {
38
+ return howlerPromise;
39
+ }
40
+ howlerPromise = import('howler').then((module) => {
41
+ Howl = module.Howl;
42
+ howlerLoaded = true;
43
+ });
44
+ return howlerPromise;
45
+ }
46
+ /**
47
+ * Hook for playing sound effects
48
+ *
49
+ * @param src - URL to the audio file
50
+ * @param options - Howler options
51
+ * @returns Sound controller
52
+ */
53
+ export function useSound(src, options = {}) {
54
+ const [isLoaded, setIsLoaded] = useState(false);
55
+ const howlRef = useRef(null);
56
+ useEffect(() => {
57
+ let cancelled = false;
58
+ loadHowler().then(() => {
59
+ if (cancelled || !Howl)
60
+ return;
61
+ const howl = new Howl({
62
+ src: [src],
63
+ volume: options.volume ?? 1.0,
64
+ loop: options.loop ?? false,
65
+ rate: options.rate ?? 1.0,
66
+ sprite: options.sprite,
67
+ onload: () => {
68
+ setIsLoaded(true);
69
+ options.onload?.();
70
+ },
71
+ onloaderror: options.onloaderror,
72
+ onplay: options.onplay,
73
+ onend: options.onend,
74
+ onstop: options.onstop,
75
+ onpause: options.onpause,
76
+ });
77
+ howlRef.current = howl;
78
+ }).catch(err => {
79
+ console.error('[useSound] Failed to load Howler:', err);
80
+ });
81
+ return () => {
82
+ cancelled = true;
83
+ if (howlRef.current) {
84
+ howlRef.current.unload();
85
+ howlRef.current = null;
86
+ }
87
+ };
88
+ }, [src]);
89
+ return {
90
+ play: (spriteId) => {
91
+ if (howlRef.current) {
92
+ howlRef.current.play(spriteId);
93
+ }
94
+ },
95
+ pause: () => {
96
+ if (howlRef.current) {
97
+ howlRef.current.pause();
98
+ }
99
+ },
100
+ stop: () => {
101
+ if (howlRef.current) {
102
+ howlRef.current.stop();
103
+ }
104
+ },
105
+ volume: (vol) => {
106
+ if (howlRef.current) {
107
+ if (vol !== undefined) {
108
+ howlRef.current.volume(vol);
109
+ }
110
+ else {
111
+ return howlRef.current.volume();
112
+ }
113
+ }
114
+ },
115
+ rate: (rate) => {
116
+ if (howlRef.current) {
117
+ if (rate !== undefined) {
118
+ howlRef.current.rate(rate);
119
+ }
120
+ else {
121
+ return howlRef.current.rate();
122
+ }
123
+ }
124
+ },
125
+ loop: (loop) => {
126
+ if (howlRef.current) {
127
+ if (loop !== undefined) {
128
+ howlRef.current.loop(loop);
129
+ }
130
+ else {
131
+ return howlRef.current.loop();
132
+ }
133
+ }
134
+ },
135
+ playing: () => {
136
+ return howlRef.current ? howlRef.current.playing() : false;
137
+ },
138
+ duration: () => {
139
+ return howlRef.current ? howlRef.current.duration() : 0;
140
+ },
141
+ state: () => {
142
+ return howlRef.current ? howlRef.current.state() : 'unloaded';
143
+ },
144
+ unload: () => {
145
+ if (howlRef.current) {
146
+ howlRef.current.unload();
147
+ howlRef.current = null;
148
+ }
149
+ },
150
+ };
151
+ }
152
+ /**
153
+ * Hook for playing background music with auto-play on mount
154
+ *
155
+ * @param src - URL to the music file
156
+ * @param options - Howler options (loop defaults to true for music)
157
+ * @returns Sound controller
158
+ */
159
+ export function useMusic(src, options = {}) {
160
+ const sound = useSound(src, {
161
+ loop: true, // Music usually loops
162
+ ...options,
163
+ });
164
+ const hasAutoPlayed = useRef(false);
165
+ useEffect(() => {
166
+ if (sound.state() === 'loaded' && !hasAutoPlayed.current) {
167
+ hasAutoPlayed.current = true;
168
+ sound.play();
169
+ }
170
+ }, [sound.state()]);
171
+ return sound;
172
+ }
173
+ /**
174
+ * Re-export Howl for advanced use cases
175
+ * Returns null during SSR, the Howl class after client-side load
176
+ */
177
+ export async function getHowl() {
178
+ if (typeof window === 'undefined')
179
+ return null;
180
+ await loadHowler();
181
+ return Howl;
182
+ }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay/index.js';
12
12
  export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, CuePoint, SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './react/overlay/index.js';
13
13
  export { confetti } from './react/confetti.js';
14
14
  export type { ConfettiOptions } from './react/confetti.js';
15
+ export { useSound, useMusic, getHowl } from './audio/index.js';
16
+ export type { SoundOptions, SoundController } from './audio/index.js';
15
17
  export { getTileBridge, TileBridge } from './bridge/TileBridge.js';
16
18
  export type { TileMessage, TileConfig, TileTokenData, KeyboardState, VisibilityState, SafeAreaInsets } from './bridge/TileBridge.js';
17
19
  export { StateClient } from './state/StateClient.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,OAAO,EAEL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0BAA0B;AACpC,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,SAAS,EACT,iBAAiB,EACjB,YAAY,EAAE,8BAA8B;AAE5C,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EAER,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGrI,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtE,cAAc,kBAAkB,CAAC;AAGjC,cAAc,YAAY,CAAC;AAG3B,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,OAAO,EAEL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0BAA0B;AACpC,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,SAAS,EACT,iBAAiB,EACjB,YAAY,EAAE,8BAA8B;AAE5C,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EAER,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGtE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGrI,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtE,cAAc,kBAAkB,CAAC;AAGjC,cAAc,YAAY,CAAC;AAG3B,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ Slideshow, useSlideshowState, useSlideshow, // Alias for useSlideshowState
17
17
  OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay/index.js';
18
18
  // Confetti - CSS-based, mobile WebView safe
19
19
  export { confetti } from './react/confetti.js';
20
+ // Audio hooks - SSR-safe Howler.js wrappers
21
+ export { useSound, useMusic, getHowl } from './audio/index.js';
20
22
  // Bridge for secure communication
21
23
  export { getTileBridge, TileBridge } from './bridge/TileBridge.js';
22
24
  // State API client
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "SDK for building interactive tiles on The Whatever App platform",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -13,6 +13,10 @@
13
13
  "types": "./dist/excalibur/index.d.ts",
14
14
  "import": "./dist/excalibur/index.js"
15
15
  },
16
+ "./audio": {
17
+ "types": "./dist/audio/index.d.ts",
18
+ "import": "./dist/audio/index.js"
19
+ },
16
20
  "./spec": {
17
21
  "types": "./dist/spec/index.d.ts",
18
22
  "import": "./dist/spec/index.js"
@@ -55,20 +59,26 @@
55
59
  "peerDependencies": {
56
60
  "react": "^18.0.0",
57
61
  "react-dom": "^18.0.0",
58
- "excalibur": "^0.29.0"
62
+ "excalibur": "^0.29.0",
63
+ "howler": "^2.2.0"
59
64
  },
60
65
  "peerDependenciesMeta": {
61
66
  "excalibur": {
62
67
  "optional": true
68
+ },
69
+ "howler": {
70
+ "optional": true
63
71
  }
64
72
  },
65
73
  "devDependencies": {
74
+ "@types/howler": "^2.2.11",
66
75
  "@types/matter-js": "^0.19.0",
67
76
  "@types/node": "^20.0.0",
68
77
  "@types/react": "^18.2.48",
69
78
  "@types/react-dom": "^18.2.18",
70
79
  "eslint": "^9.39.1",
71
80
  "excalibur": "^0.29.3",
81
+ "howler": "^2.2.4",
72
82
  "next": "^14.2.0",
73
83
  "tsx": "^4.7.0",
74
84
  "typescript": "^5.3.3"