zsk-use-sound 1.0.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/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# zsk-use-sound
|
|
2
|
+
|
|
3
|
+
A TypeScript-first React hook for sound effects, inspired by the excellent `use-sound` package and extended with a browser-generated fallback mode.
|
|
4
|
+
|
|
5
|
+
## Why this package
|
|
6
|
+
|
|
7
|
+
This hook gives you one API for two sound strategies:
|
|
8
|
+
|
|
9
|
+
- File mode: Use audio files (mp3, wav, etc.) powered by Howler.
|
|
10
|
+
- Generated mode: If no file is provided, it generates a click-like sound in the browser using the Web Audio API.
|
|
11
|
+
|
|
12
|
+
This is useful when you want sound feedback but do not always want to bundle and manage sound assets.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add zsk-use-sound
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install zsk-use-sound
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Peer dependency:
|
|
27
|
+
|
|
28
|
+
- React 18+
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { useSound } from 'zsk-use-sound'
|
|
34
|
+
|
|
35
|
+
export function SaveButton() {
|
|
36
|
+
const [play] = useSound({
|
|
37
|
+
file: '/sounds/click.mp3',
|
|
38
|
+
volume: 0.5,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return <button onClick={() => play()}>Save</button>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### useSound(options?)
|
|
48
|
+
|
|
49
|
+
Returns a tuple:
|
|
50
|
+
|
|
51
|
+
- play: function to trigger sound playback
|
|
52
|
+
- exposedData: helper controls and runtime metadata
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const [play, exposedData] = useSound(options)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Options
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
type BuiltInSoundType = 'click' | 'soft-click' | 'tick'
|
|
62
|
+
|
|
63
|
+
interface UseSoundOptions {
|
|
64
|
+
file?: string | string[]
|
|
65
|
+
type?: BuiltInSoundType
|
|
66
|
+
id?: string
|
|
67
|
+
volume?: number
|
|
68
|
+
playbackRate?: number
|
|
69
|
+
interrupt?: boolean
|
|
70
|
+
soundEnabled?: boolean
|
|
71
|
+
sprite?: Record<string, [number, number]>
|
|
72
|
+
onload?: (this: Howl) => void
|
|
73
|
+
|
|
74
|
+
// Plus delegated Howler options
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Behavior:
|
|
79
|
+
|
|
80
|
+
- If file is passed: hook runs in file mode using Howler.
|
|
81
|
+
- If file is omitted: hook runs in generated mode and uses built-in Web Audio click profiles.
|
|
82
|
+
- Default type is click.
|
|
83
|
+
|
|
84
|
+
### play(options?)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
interface PlayOptions {
|
|
88
|
+
id?: string
|
|
89
|
+
forceSoundEnabled?: boolean
|
|
90
|
+
playbackRate?: number
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Exposed data
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface ExposedData {
|
|
98
|
+
sound: Howl | null
|
|
99
|
+
stop: (id?: number) => void
|
|
100
|
+
pause: (id?: number) => void
|
|
101
|
+
duration: number | null
|
|
102
|
+
mode: 'file' | 'generated'
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage Patterns
|
|
107
|
+
|
|
108
|
+
### 1) File mode (use-sound style)
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { useSound } from 'zsk-use-sound'
|
|
112
|
+
|
|
113
|
+
export function NotificationButton() {
|
|
114
|
+
const [play, { stop, mode }] = useSound({
|
|
115
|
+
file: ['/sounds/ping.wav', '/sounds/ping.mp3'],
|
|
116
|
+
volume: 0.4,
|
|
117
|
+
interrupt: true,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
<button onClick={() => play()}>Play</button>
|
|
123
|
+
<button onClick={() => stop()}>Stop</button>
|
|
124
|
+
<small>mode: {mode}</small>
|
|
125
|
+
</>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2) Generated mode (no file)
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { useSound } from 'zsk-use-sound'
|
|
134
|
+
|
|
135
|
+
export function UIInteractions() {
|
|
136
|
+
const [play, { mode }] = useSound({
|
|
137
|
+
type: 'soft-click',
|
|
138
|
+
volume: 0.6,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return <button onClick={() => play()}>Click me ({mode})</button>
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3) Global sound toggle
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { useSound } from 'zsk-use-sound'
|
|
149
|
+
|
|
150
|
+
export function MutedAwareAction({ isEnabled }: { isEnabled: boolean }) {
|
|
151
|
+
const [play] = useSound({
|
|
152
|
+
file: '/sounds/action.mp3',
|
|
153
|
+
soundEnabled: isEnabled,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return <button onClick={() => play()}>Action</button>
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Why Howler
|
|
161
|
+
|
|
162
|
+
Howler is used for file playback mode because it offers:
|
|
163
|
+
|
|
164
|
+
- Cross-browser audio consistency: normalizes browser differences.
|
|
165
|
+
- Flexible source handling: supports source arrays and format fallback.
|
|
166
|
+
- Sprite support: multiple sounds in one file with IDs.
|
|
167
|
+
- Stable playback controls: volume, rate, stop, pause, and event hooks.
|
|
168
|
+
- Mature ecosystem: widely used and battle-tested.
|
|
169
|
+
|
|
170
|
+
In short, Howler handles the hard edges of browser audio so your hook API stays simple.
|
|
171
|
+
|
|
172
|
+
## How this package is better for this use case
|
|
173
|
+
|
|
174
|
+
Compared to a direct baseline implementation, this package adds:
|
|
175
|
+
|
|
176
|
+
- Dual-mode behavior: file-based audio plus generated fallback in one hook.
|
|
177
|
+
- No-any TypeScript API: explicit options and return types.
|
|
178
|
+
- Better type surface for consumers: exported public types directly from the package.
|
|
179
|
+
- Built-in sound presets: click, soft-click, tick.
|
|
180
|
+
- Runtime mode visibility: exposedData.mode tells you how playback is happening.
|
|
181
|
+
|
|
182
|
+
## Credit
|
|
183
|
+
|
|
184
|
+
This package is inspired by:
|
|
185
|
+
|
|
186
|
+
- Josh Comeau's original use-sound project: https://github.com/joshwcomeau/use-sound
|
|
187
|
+
|
|
188
|
+
Many API ideas (tuple return shape, play options, exposed control object, and Howler-based file mode approach) are adapted from that excellent work.
|
|
189
|
+
|
|
190
|
+
Thank you to Josh Comeau for creating and sharing the original library.
|
|
191
|
+
|
|
192
|
+
## TypeScript Exports
|
|
193
|
+
|
|
194
|
+
You can import runtime and type APIs directly:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { useSound } from 'zsk-use-sound'
|
|
198
|
+
import type {
|
|
199
|
+
BuiltInSoundType,
|
|
200
|
+
UseSoundOptions,
|
|
201
|
+
PlayOptions,
|
|
202
|
+
ExposedData,
|
|
203
|
+
ReturnedValue,
|
|
204
|
+
} from 'zsk-use-sound'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Notes
|
|
208
|
+
|
|
209
|
+
- Generated mode relies on Web Audio API availability in the browser.
|
|
210
|
+
- Browsers often require user interaction before sound can play.
|
|
211
|
+
- For SSR environments, playback happens client-side only.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Howl, HowlOptions } from 'howler';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
webkitAudioContext?: typeof AudioContext;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export type BuiltInSoundType = 'click' | 'soft-click' | 'tick';
|
|
8
|
+
export type SpriteMap = Record<string, [number, number]>;
|
|
9
|
+
type DelegatedHowlOptions = Omit<HowlOptions, 'src' | 'volume' | 'rate' | 'onload' | 'sprite'>;
|
|
10
|
+
export interface UseSoundOptions extends DelegatedHowlOptions {
|
|
11
|
+
file?: string | string[];
|
|
12
|
+
type?: BuiltInSoundType;
|
|
13
|
+
id?: string;
|
|
14
|
+
volume?: number;
|
|
15
|
+
playbackRate?: number;
|
|
16
|
+
interrupt?: boolean;
|
|
17
|
+
soundEnabled?: boolean;
|
|
18
|
+
sprite?: SpriteMap;
|
|
19
|
+
onload?: (this: Howl) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface PlayOptions {
|
|
22
|
+
id?: string;
|
|
23
|
+
forceSoundEnabled?: boolean;
|
|
24
|
+
playbackRate?: number;
|
|
25
|
+
}
|
|
26
|
+
export type PlayFunction = (options?: PlayOptions) => void;
|
|
27
|
+
export interface ExposedData {
|
|
28
|
+
sound: Howl | null;
|
|
29
|
+
stop: (id?: number) => void;
|
|
30
|
+
pause: (id?: number) => void;
|
|
31
|
+
duration: number | null;
|
|
32
|
+
mode: 'file' | 'generated';
|
|
33
|
+
}
|
|
34
|
+
export type ReturnedValue = [PlayFunction, ExposedData];
|
|
35
|
+
export default function useSound({ file, type, id, volume, playbackRate, soundEnabled, interrupt, sprite, onload, ...delegated }?: UseSoundOptions): ReturnedValue;
|
|
36
|
+
export { useSound };
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAG/C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,kBAAkB,CAAC,EAAE,OAAO,YAAY,CAAA;KACzC;CACF;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,CAAA;AAE9D,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAExD,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAA;AAE9F,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,IAAI,CAAA;AAE1D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;CAC3B;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;AAwCvD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,IAAI,EACJ,IAAc,EACd,EAAE,EACF,MAAU,EACV,YAAgB,EAChB,YAAmB,EACnB,SAAiB,EACjB,MAAM,EACN,MAAM,EACN,GAAG,SAAS,EACb,GAAE,eAAoB,GAAG,aAAa,CA6NtC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
const GENERATED_PROFILES = {
|
|
3
|
+
click: {
|
|
4
|
+
filterHz: 5000,
|
|
5
|
+
decaySeconds: 0.015,
|
|
6
|
+
gain: 0.6,
|
|
7
|
+
noiseSeconds: 0.02,
|
|
8
|
+
},
|
|
9
|
+
'soft-click': {
|
|
10
|
+
filterHz: 3200,
|
|
11
|
+
decaySeconds: 0.02,
|
|
12
|
+
gain: 0.35,
|
|
13
|
+
noiseSeconds: 0.018,
|
|
14
|
+
},
|
|
15
|
+
tick: {
|
|
16
|
+
filterHz: 6400,
|
|
17
|
+
decaySeconds: 0.01,
|
|
18
|
+
gain: 0.48,
|
|
19
|
+
noiseSeconds: 0.012,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const defaultProfile = GENERATED_PROFILES.click;
|
|
23
|
+
function isBrowser() {
|
|
24
|
+
return typeof window !== 'undefined';
|
|
25
|
+
}
|
|
26
|
+
function toArray(src) {
|
|
27
|
+
return Array.isArray(src) ? src : [src];
|
|
28
|
+
}
|
|
29
|
+
export default function useSound({ file, type = 'click', id, volume = 1, playbackRate = 1, soundEnabled = true, interrupt = false, sprite, onload, ...delegated } = {}) {
|
|
30
|
+
const [sound, setSound] = useState(null);
|
|
31
|
+
const [duration, setDuration] = useState(null);
|
|
32
|
+
const isMountedRef = useRef(false);
|
|
33
|
+
const audioCtxRef = useRef(null);
|
|
34
|
+
const mode = file ? 'file' : 'generated';
|
|
35
|
+
const generatedProfile = useMemo(() => GENERATED_PROFILES[type] ?? defaultProfile, [type]);
|
|
36
|
+
const closeAudioContext = useCallback(() => {
|
|
37
|
+
if (audioCtxRef.current && audioCtxRef.current.state !== 'closed') {
|
|
38
|
+
void audioCtxRef.current.close();
|
|
39
|
+
}
|
|
40
|
+
audioCtxRef.current = null;
|
|
41
|
+
}, []);
|
|
42
|
+
const playGenerated = useCallback(() => {
|
|
43
|
+
if (!isBrowser() || !soundEnabled) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const AudioContextClass = window.AudioContext ?? window.webkitAudioContext;
|
|
47
|
+
if (!AudioContextClass) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!audioCtxRef.current) {
|
|
51
|
+
audioCtxRef.current = new AudioContextClass();
|
|
52
|
+
}
|
|
53
|
+
const ctx = audioCtxRef.current;
|
|
54
|
+
if (ctx.state === 'suspended') {
|
|
55
|
+
void ctx.resume();
|
|
56
|
+
}
|
|
57
|
+
const bufferSize = Math.max(1, Math.floor(ctx.sampleRate * generatedProfile.noiseSeconds));
|
|
58
|
+
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
|
|
59
|
+
const data = buffer.getChannelData(0);
|
|
60
|
+
for (let i = 0; i < bufferSize; i += 1) {
|
|
61
|
+
data[i] = Math.random() * 2 - 1;
|
|
62
|
+
}
|
|
63
|
+
const now = ctx.currentTime;
|
|
64
|
+
const noise = ctx.createBufferSource();
|
|
65
|
+
const filter = ctx.createBiquadFilter();
|
|
66
|
+
const gainNode = ctx.createGain();
|
|
67
|
+
noise.buffer = buffer;
|
|
68
|
+
filter.type = 'bandpass';
|
|
69
|
+
filter.frequency.value = generatedProfile.filterHz;
|
|
70
|
+
gainNode.gain.setValueAtTime(generatedProfile.gain * volume, now);
|
|
71
|
+
gainNode.gain.exponentialRampToValueAtTime(0.01, now + generatedProfile.decaySeconds);
|
|
72
|
+
noise.connect(filter);
|
|
73
|
+
filter.connect(gainNode);
|
|
74
|
+
gainNode.connect(ctx.destination);
|
|
75
|
+
noise.start(now);
|
|
76
|
+
}, [generatedProfile, soundEnabled, volume]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!file || !isBrowser()) {
|
|
79
|
+
setSound(null);
|
|
80
|
+
setDuration(null);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let cancelled = false;
|
|
84
|
+
isMountedRef.current = true;
|
|
85
|
+
const initialize = async () => {
|
|
86
|
+
const mod = await import('howler');
|
|
87
|
+
if (cancelled || !isMountedRef.current) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const nextSound = new mod.Howl({
|
|
91
|
+
src: toArray(file),
|
|
92
|
+
volume,
|
|
93
|
+
rate: playbackRate,
|
|
94
|
+
sprite,
|
|
95
|
+
onload: function onLoad() {
|
|
96
|
+
if (typeof onload === 'function') {
|
|
97
|
+
onload.call(this);
|
|
98
|
+
}
|
|
99
|
+
if (!cancelled && isMountedRef.current) {
|
|
100
|
+
setDuration(this.duration() * 1000);
|
|
101
|
+
setSound(this);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
...delegated,
|
|
105
|
+
});
|
|
106
|
+
if (!cancelled && isMountedRef.current) {
|
|
107
|
+
setSound(nextSound);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
void initialize();
|
|
111
|
+
return () => {
|
|
112
|
+
cancelled = true;
|
|
113
|
+
isMountedRef.current = false;
|
|
114
|
+
setDuration(null);
|
|
115
|
+
setSound((current) => {
|
|
116
|
+
current?.unload();
|
|
117
|
+
return null;
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
}, [file, onload, playbackRate, sprite, volume, delegated]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!file) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (sound) {
|
|
126
|
+
sound.volume(volume);
|
|
127
|
+
if (!sprite) {
|
|
128
|
+
sound.rate(playbackRate);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, [file, sound, volume, playbackRate, sprite]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (file || !soundEnabled || !isBrowser()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const onMouseDown = () => {
|
|
137
|
+
playGenerated();
|
|
138
|
+
};
|
|
139
|
+
document.addEventListener('mousedown', onMouseDown);
|
|
140
|
+
return () => {
|
|
141
|
+
document.removeEventListener('mousedown', onMouseDown);
|
|
142
|
+
};
|
|
143
|
+
}, [file, playGenerated, soundEnabled]);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (file) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
return () => {
|
|
149
|
+
closeAudioContext();
|
|
150
|
+
};
|
|
151
|
+
}, [closeAudioContext, file]);
|
|
152
|
+
const play = useCallback((options) => {
|
|
153
|
+
const currentOptions = options ?? {};
|
|
154
|
+
if (mode === 'generated') {
|
|
155
|
+
if (!soundEnabled && !currentOptions.forceSoundEnabled) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
playGenerated();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!sound || (!soundEnabled && !currentOptions.forceSoundEnabled)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (interrupt) {
|
|
165
|
+
sound.stop();
|
|
166
|
+
}
|
|
167
|
+
if (typeof currentOptions.playbackRate === 'number') {
|
|
168
|
+
sound.rate(currentOptions.playbackRate);
|
|
169
|
+
}
|
|
170
|
+
sound.play(currentOptions.id ?? id);
|
|
171
|
+
}, [id, interrupt, mode, playGenerated, sound, soundEnabled]);
|
|
172
|
+
const stop = useCallback((playId) => {
|
|
173
|
+
if (mode === 'generated') {
|
|
174
|
+
closeAudioContext();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (sound) {
|
|
178
|
+
sound.stop(playId);
|
|
179
|
+
}
|
|
180
|
+
}, [closeAudioContext, mode, sound]);
|
|
181
|
+
const pause = useCallback((playId) => {
|
|
182
|
+
if (mode === 'generated') {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (sound) {
|
|
186
|
+
sound.pause(playId);
|
|
187
|
+
}
|
|
188
|
+
}, [mode, sound]);
|
|
189
|
+
return [
|
|
190
|
+
play,
|
|
191
|
+
{
|
|
192
|
+
sound,
|
|
193
|
+
stop,
|
|
194
|
+
pause,
|
|
195
|
+
duration,
|
|
196
|
+
mode,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
export { useSound };
|
|
201
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zsk-use-sound",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"npm-package",
|
|
7
|
+
"react",
|
|
8
|
+
"sounds",
|
|
9
|
+
"typescript"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/sk-izsk/zsk-react-error.git"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "bunx tsc -p tsconfig.json",
|
|
31
|
+
"lint": "oxlint . --config .oxlintrc.json",
|
|
32
|
+
"lint:fix": "oxlint . --fix --config .oxlintrc.json",
|
|
33
|
+
"format": "oxfmt . --config .oxfmtrc.json --write",
|
|
34
|
+
"format:check": "oxfmt . --config .oxfmtrc.json --check",
|
|
35
|
+
"typecheck": "bunx tsc --noEmit",
|
|
36
|
+
"clean": "rm -rf dist",
|
|
37
|
+
"verify": "bun run format && bun run lint && bun run typecheck && bun run clean && bun run build && npm pack --dry-run",
|
|
38
|
+
"prepublishOnly": "bun run verify",
|
|
39
|
+
"publish:release": "bun publish",
|
|
40
|
+
"release": "bun run verify && bun publish"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"howler": "^2.2.4"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/howler": "^2.2.12",
|
|
47
|
+
"@types/node": "^25.6.0",
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
|
+
"oxfmt": "^0.44.0",
|
|
50
|
+
"oxlint": "^1.59.0",
|
|
51
|
+
"react": "^19.2.5",
|
|
52
|
+
"react-dom": "^19.2.5",
|
|
53
|
+
"typescript": "^6.0.2"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": ">=18"
|
|
57
|
+
},
|
|
58
|
+
"packageManager": "bun@1.2.5"
|
|
59
|
+
}
|