@jswork/howler-audio-manager 1.0.1 → 1.0.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/afei/github/howler-audio-manager/dist/index.esm.js","../src/manager.ts"],"names":["Howl","Howler","AudioManager","options","inject","effectCache","Map","urlAudioCache","preloadPromises","activeUrlHowl","globalVolume","listeners","HowlConstructor","HowlClass","HowlerInstance","HowlerObj","defaultVolume","setGlobalVolume","hasEffect","key","has","loadEffect","url","get","unload","howl","src","preload","on","emit","type","_id","error","set","loadEffects","map","Object","keys","forEach","typedKey","isNaN","Number","playEffect","console","warn","String","playing","stop","volume","off","once","soundId","onStart","onEnd","onStop","onError","play","stopEffect","removeEffect","delete","preloadUrl","existing","state","Promise","resolve","pending","createUrlHowl","promise","reject","settle","then","preloadUrls","urls","all","playUrl","isSingleton","singleton","loop","stopUrl","event","handler","Set","add","destroy","payload","err","mute","muted","stopAll","clear","default"],"mappings":"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","sourcesContent":["/*!\n * name: @jswork/howler-audio-manager\n * description: Howler.js audio manager, enum-driven sounds for H5.\n * homepage: https://js.work\n * version: 1.0.1\n * date: 2026-10-20 18:54:06\n * license: MIT\n */\n\n\n// src/manager.ts\nimport { Howl, Howler } from \"howler\";\nvar AudioManager = class {\n constructor(options, inject) {\n this.effectCache = /* @__PURE__ */ new Map();\n this.urlAudioCache = /* @__PURE__ */ new Map();\n this.preloadPromises = /* @__PURE__ */ new Map();\n this.activeUrlHowl = null;\n this.globalVolume = 1;\n this.listeners = /* @__PURE__ */ new Map();\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === \"number\") {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n hasEffect(key) {\n return this.effectCache.has(key);\n }\n loadEffect(key, url) {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key).unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"effect\", key, url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.emit(\"error\", { type: \"effect\", key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n loadEffects(map) {\n Object.keys(map).forEach((key) => {\n const typedKey = isNaN(Number(key)) ? key : Number(key);\n this.loadEffect(typedKey, map[key]);\n });\n }\n playEffect(key, options) {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return void 0;\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"effect\", key, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"effect\", key, soundId });\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"effect\", key, soundId });\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"effect\", key, soundId, error });\n });\n return howl.play();\n }\n stopEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n removeEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n preloadUrl(url) {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === \"loaded\") {\n return Promise.resolve();\n }\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n const howl = existing && existing.state() !== \"unloaded\" ? existing : this.createUrlHowl(url);\n const promise = new Promise((resolve, reject) => {\n howl.once(\"load\", () => {\n resolve();\n });\n howl.once(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n return promise;\n }\n preloadUrls(urls) {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n createUrlHowl(url) {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"url\", url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n this.emit(\"error\", { type: \"url\", url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n playUrl(url, options) {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n const isSingleton = options?.singleton !== false;\n if (isSingleton && this.activeUrlHowl && this.activeUrlHowl !== howl && this.activeUrlHowl.playing()) {\n this.activeUrlHowl.stop();\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (options?.loop !== void 0) {\n howl.loop(options.loop);\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"url\", url, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"url\", url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n return howl.play();\n }\n stopUrl(url) {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n on(event, handler) {\n const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n emit(event, payload) {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n setGlobalVolume(volume) {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n mute(muted) {\n this.HowlerInstance.mute(muted);\n }\n stopAll() {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n destroy() {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n};\nexport {\n AudioManager,\n AudioManager as default\n};\n","import { Howl, Howler } from 'howler';\nimport type {\n AudioManagerOptions,\n PlayOptions,\n PlayUrlOptions,\n AudioEventType,\n AudioEventPayload,\n EventSubscription,\n SoundResourceMap\n} from './types';\n\nexport class AudioManager<K extends string | number = string> {\n private effectCache = new Map<K, Howl>();\n private urlAudioCache = new Map<string, Howl>();\n private preloadPromises = new Map<string, Promise<void>>();\n private activeUrlHowl: Howl | null = null;\n private globalVolume = 1.0;\n private listeners = new Map<AudioEventType, Set<(payload: AudioEventPayload<K>) => void>>();\n\n private HowlConstructor: typeof Howl;\n private HowlerInstance: typeof Howler;\n\n constructor(\n options?: AudioManagerOptions,\n inject?: { HowlClass?: typeof Howl; HowlerObj?: typeof Howler }\n ) {\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === 'number') {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n\n hasEffect(key: K): boolean {\n return this.effectCache.has(key);\n }\n\n loadEffect(key: K, url: string): void {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key)!.unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'effect', key, url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.emit('error', { type: 'effect', key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n\n loadEffects(map: SoundResourceMap<K>): void {\n Object.keys(map).forEach((key) => {\n const typedKey = (isNaN(Number(key)) ? key : Number(key)) as K;\n this.loadEffect(typedKey, map[key as any]);\n });\n }\n\n playEffect(key: K, options?: PlayOptions): number | undefined {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return undefined;\n }\n\n if (howl.playing()) {\n howl.stop();\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'effect', key, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'effect', key, soundId });\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'effect', key, soundId });\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'effect', key, soundId, error });\n });\n\n return howl.play();\n }\n\n stopEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n\n removeEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n\n preloadUrl(url: string): Promise<void> {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === 'loaded') {\n return Promise.resolve();\n }\n\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n\n // A howl in a terminal failed state (unloaded after error) must be replaced\n // so a fresh instance can retry the load.\n const howl = existing && existing.state() !== 'unloaded' ? existing : this.createUrlHowl(url);\n\n const promise = new Promise<void>((resolve, reject) => {\n howl.once('load', () => {\n resolve();\n });\n\n howl.once('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n\n return promise;\n }\n\n preloadUrls(urls: string[]): Promise<void[]> {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n\n private createUrlHowl(url: string): Howl {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'url', url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n this.emit('error', { type: 'url', url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n\n playUrl(url: string, options?: PlayUrlOptions): number {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n\n // Singleton mutex: stop currently active URL audio if singleton !== false\n const isSingleton = options?.singleton !== false;\n if (\n isSingleton &&\n this.activeUrlHowl &&\n this.activeUrlHowl !== howl &&\n this.activeUrlHowl.playing()\n ) {\n this.activeUrlHowl.stop();\n }\n\n // Overlap prevention on the same audio instance\n if (howl.playing()) {\n howl.stop();\n }\n\n if (options?.loop !== undefined) {\n howl.loop(options.loop);\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'url', url, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'url', url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n return howl.play();\n }\n\n stopUrl(url: string): void {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n\n on(event: AudioEventType, handler: (payload: AudioEventPayload<K>) => void): EventSubscription {\n const set = this.listeners.get(event) ?? new Set<(payload: AudioEventPayload<K>) => void>();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n\n protected emit(event: AudioEventType, payload: AudioEventPayload<K>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n\n setGlobalVolume(volume: number): void {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n\n mute(muted: boolean): void {\n this.HowlerInstance.mute(muted);\n }\n\n stopAll(): void {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n\n destroy(): void {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/afei/github/howler-audio-manager/dist/index.esm.js","../src/manager.ts","../src/react.tsx"],"names":["Howl","Howler","AudioManager","options","inject","effectCache","Map","urlAudioCache","preloadPromises","activeUrlHowl","globalVolume","listeners","HowlConstructor","HowlClass","HowlerInstance","HowlerObj","defaultVolume","setGlobalVolume","hasEffect","key","has","loadEffect","url","get","unload","howl","src","preload","on","emit","type","_id","error","set","loadEffects","map","Object","keys","forEach","typedKey","isNaN","Number","playEffect","console","warn","String","playing","stop","volume","off","once","soundId","onStart","onEnd","onStop","onError","play","stopEffect","removeEffect","delete","preloadUrl","existing","state","Promise","resolve","pending","createUrlHowl","promise","reject","settle","then","preloadUrls","urls","all","playUrl","isSingleton","singleton","loop","stopUrl","event","handler","Set","add","destroy","payload","err","getGlobalVolume","mute","muted","stopAll","clear","createContext","useCallback","useContext","useEffect","useRef","useState","jsx","AudioManagerContext","AudioManagerProvider","manager","children","Provider","value","useAudio","Error","useAudioEvent","handlerRef","current","subscription","useGlobalVolume","setVolumeState","setVolume","next","usePreload","urlsKey","JSON","stringify","status","setState","list","parse","length","cancelled","default"],"mappings":"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","sourcesContent":["/*!\n * name: @jswork/howler-audio-manager\n * description: Howler.js audio manager, enum-driven sounds for H5.\n * homepage: https://js.work\n * version: 1.0.2\n * date: 2026-10-20 20:03:06\n * license: MIT\n */\n\n\n// src/manager.ts\nimport { Howl, Howler } from \"howler\";\nvar AudioManager = class {\n constructor(options, inject) {\n this.effectCache = /* @__PURE__ */ new Map();\n this.urlAudioCache = /* @__PURE__ */ new Map();\n this.preloadPromises = /* @__PURE__ */ new Map();\n this.activeUrlHowl = null;\n this.globalVolume = 1;\n this.listeners = /* @__PURE__ */ new Map();\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === \"number\") {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n hasEffect(key) {\n return this.effectCache.has(key);\n }\n loadEffect(key, url) {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key).unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"effect\", key, url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.emit(\"error\", { type: \"effect\", key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n loadEffects(map) {\n Object.keys(map).forEach((key) => {\n const typedKey = isNaN(Number(key)) ? key : Number(key);\n this.loadEffect(typedKey, map[key]);\n });\n }\n playEffect(key, options) {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return void 0;\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"effect\", key, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"effect\", key, soundId });\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"effect\", key, soundId });\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"effect\", key, soundId, error });\n });\n return howl.play();\n }\n stopEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n removeEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n preloadUrl(url) {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === \"loaded\") {\n return Promise.resolve();\n }\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n const howl = existing && existing.state() !== \"unloaded\" ? existing : this.createUrlHowl(url);\n const promise = new Promise((resolve, reject) => {\n howl.once(\"load\", () => {\n resolve();\n });\n howl.once(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n return promise;\n }\n preloadUrls(urls) {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n createUrlHowl(url) {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"url\", url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n this.emit(\"error\", { type: \"url\", url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n playUrl(url, options) {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n const isSingleton = options?.singleton !== false;\n if (isSingleton && this.activeUrlHowl && this.activeUrlHowl !== howl && this.activeUrlHowl.playing()) {\n this.activeUrlHowl.stop();\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (options?.loop !== void 0) {\n howl.loop(options.loop);\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"url\", url, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"url\", url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n return howl.play();\n }\n stopUrl(url) {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n on(event, handler) {\n const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n emit(event, payload) {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n getGlobalVolume() {\n return this.globalVolume;\n }\n setGlobalVolume(volume) {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n mute(muted) {\n this.HowlerInstance.mute(muted);\n }\n stopAll() {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n destroy() {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n};\n\n// src/react.tsx\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from \"react\";\nimport { jsx } from \"react/jsx-runtime\";\nvar AudioManagerContext = createContext(null);\nfunction AudioManagerProvider({\n manager,\n children\n}) {\n return /* @__PURE__ */ jsx(AudioManagerContext.Provider, { value: manager, children });\n}\nfunction useAudio() {\n const manager = useContext(AudioManagerContext);\n if (!manager) {\n throw new Error(\"useAudio must be used within an <AudioManagerProvider>.\");\n }\n return manager;\n}\nfunction useAudioEvent(event, handler) {\n const manager = useAudio();\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n const subscription = manager.on(event, (payload) => handlerRef.current(payload));\n return () => subscription.destroy();\n }, [manager, event]);\n}\nfunction useGlobalVolume() {\n const manager = useAudio();\n const [volume, setVolumeState] = useState(() => manager.getGlobalVolume());\n const setVolume = useCallback(\n (next) => {\n manager.setGlobalVolume(next);\n setVolumeState(next);\n },\n [manager]\n );\n return [volume, setVolume];\n}\nfunction usePreload(urls) {\n const manager = useAudio();\n const urlsKey = JSON.stringify(urls);\n const [state, setState] = useState({ status: \"idle\", error: null });\n useEffect(() => {\n const list = JSON.parse(urlsKey);\n if (list.length === 0) {\n setState({ status: \"idle\", error: null });\n return;\n }\n let cancelled = false;\n setState({ status: \"loading\", error: null });\n manager.preloadUrls(list).then(\n () => {\n if (!cancelled) setState({ status: \"loaded\", error: null });\n },\n (error) => {\n if (!cancelled) setState({ status: \"error\", error });\n }\n );\n return () => {\n cancelled = true;\n };\n }, [manager, urlsKey]);\n return state;\n}\nexport {\n AudioManager,\n AudioManagerProvider,\n AudioManager as default,\n useAudio,\n useAudioEvent,\n useGlobalVolume,\n usePreload\n};\n","import { Howl, Howler } from 'howler';\nimport type {\n AudioManagerOptions,\n PlayOptions,\n PlayUrlOptions,\n AudioEventType,\n AudioEventPayload,\n EventSubscription,\n SoundResourceMap\n} from './types';\n\nexport class AudioManager<K extends string | number = string> {\n private effectCache = new Map<K, Howl>();\n private urlAudioCache = new Map<string, Howl>();\n private preloadPromises = new Map<string, Promise<void>>();\n private activeUrlHowl: Howl | null = null;\n private globalVolume = 1.0;\n private listeners = new Map<AudioEventType, Set<(payload: AudioEventPayload<K>) => void>>();\n\n private HowlConstructor: typeof Howl;\n private HowlerInstance: typeof Howler;\n\n constructor(\n options?: AudioManagerOptions,\n inject?: { HowlClass?: typeof Howl; HowlerObj?: typeof Howler }\n ) {\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === 'number') {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n\n hasEffect(key: K): boolean {\n return this.effectCache.has(key);\n }\n\n loadEffect(key: K, url: string): void {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key)!.unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'effect', key, url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.emit('error', { type: 'effect', key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n\n loadEffects(map: SoundResourceMap<K>): void {\n Object.keys(map).forEach((key) => {\n const typedKey = (isNaN(Number(key)) ? key : Number(key)) as K;\n this.loadEffect(typedKey, map[key as any]);\n });\n }\n\n playEffect(key: K, options?: PlayOptions): number | undefined {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return undefined;\n }\n\n if (howl.playing()) {\n howl.stop();\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'effect', key, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'effect', key, soundId });\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'effect', key, soundId });\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'effect', key, soundId, error });\n });\n\n return howl.play();\n }\n\n stopEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n\n removeEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n\n preloadUrl(url: string): Promise<void> {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === 'loaded') {\n return Promise.resolve();\n }\n\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n\n // A howl in a terminal failed state (unloaded after error) must be replaced\n // so a fresh instance can retry the load.\n const howl = existing && existing.state() !== 'unloaded' ? existing : this.createUrlHowl(url);\n\n const promise = new Promise<void>((resolve, reject) => {\n howl.once('load', () => {\n resolve();\n });\n\n howl.once('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n\n return promise;\n }\n\n preloadUrls(urls: string[]): Promise<void[]> {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n\n private createUrlHowl(url: string): Howl {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'url', url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n this.emit('error', { type: 'url', url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n\n playUrl(url: string, options?: PlayUrlOptions): number {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n\n // Singleton mutex: stop currently active URL audio if singleton !== false\n const isSingleton = options?.singleton !== false;\n if (\n isSingleton &&\n this.activeUrlHowl &&\n this.activeUrlHowl !== howl &&\n this.activeUrlHowl.playing()\n ) {\n this.activeUrlHowl.stop();\n }\n\n // Overlap prevention on the same audio instance\n if (howl.playing()) {\n howl.stop();\n }\n\n if (options?.loop !== undefined) {\n howl.loop(options.loop);\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'url', url, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'url', url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n return howl.play();\n }\n\n stopUrl(url: string): void {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n\n on(event: AudioEventType, handler: (payload: AudioEventPayload<K>) => void): EventSubscription {\n const set = this.listeners.get(event) ?? new Set<(payload: AudioEventPayload<K>) => void>();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n\n protected emit(event: AudioEventType, payload: AudioEventPayload<K>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n\n getGlobalVolume(): number {\n return this.globalVolume;\n }\n\n setGlobalVolume(volume: number): void {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n\n mute(muted: boolean): void {\n this.HowlerInstance.mute(muted);\n }\n\n stopAll(): void {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n\n destroy(): void {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n}\n","import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { AudioManager } from './manager';\nimport type { AudioEventType, AudioEventPayload } from './types';\n\nconst AudioManagerContext = createContext<AudioManager<any> | null>(null);\n\nexport interface AudioManagerProviderProps<K extends string | number = string> {\n manager: AudioManager<K>;\n children?: ReactNode;\n}\n\nexport function AudioManagerProvider<K extends string | number = string>({\n manager,\n children\n}: AudioManagerProviderProps<K>) {\n return (\n <AudioManagerContext.Provider value={manager}>\n {children}\n </AudioManagerContext.Provider>\n );\n}\n\nexport function useAudio<K extends string | number = string>(): AudioManager<K> {\n const manager = useContext(AudioManagerContext);\n if (!manager) {\n throw new Error('useAudio must be used within an <AudioManagerProvider>.');\n }\n return manager as unknown as AudioManager<K>;\n}\n\nexport function useAudioEvent<K extends string | number = string>(\n event: AudioEventType,\n handler: (payload: AudioEventPayload<K>) => void\n): void {\n const manager = useAudio<K>();\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const subscription = manager.on(event, (payload) => handlerRef.current(payload));\n return () => subscription.destroy();\n }, [manager, event]);\n}\n\nexport function useGlobalVolume(): [number, (volume: number) => void] {\n const manager = useAudio();\n const [volume, setVolumeState] = useState(() => manager.getGlobalVolume());\n\n const setVolume = useCallback(\n (next: number) => {\n manager.setGlobalVolume(next);\n setVolumeState(next);\n },\n [manager]\n );\n\n return [volume, setVolume];\n}\n\nexport interface PreloadState {\n status: 'idle' | 'loading' | 'loaded' | 'error';\n error: unknown;\n}\n\nexport function usePreload(urls: string[]): PreloadState {\n const manager = useAudio();\n const urlsKey = JSON.stringify(urls);\n const [state, setState] = useState<PreloadState>({ status: 'idle', error: null });\n\n useEffect(() => {\n const list: string[] = JSON.parse(urlsKey);\n if (list.length === 0) {\n setState({ status: 'idle', error: null });\n return;\n }\n\n let cancelled = false;\n setState({ status: 'loading', error: null });\n\n manager.preloadUrls(list).then(\n () => {\n if (!cancelled) setState({ status: 'loaded', error: null });\n },\n (error) => {\n if (!cancelled) setState({ status: 'error', error });\n }\n );\n\n return () => {\n cancelled = true;\n };\n }, [manager, urlsKey]);\n\n return state;\n}\n\n\n"]}
package/dist/index.umd.js CHANGED
@@ -1,3 +1,11 @@
1
+ function _array_like_to_array(arr, len) {
2
+ if (len == null || len > arr.length) len = arr.length;
3
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
4
+ return arr2;
5
+ }
6
+ function _array_with_holes(arr) {
7
+ if (Array.isArray(arr)) return arr;
8
+ }
1
9
  function _class_call_check(instance, Constructor) {
2
10
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
3
11
  }
@@ -15,16 +23,54 @@ function _create_class(Constructor, protoProps, staticProps) {
15
23
  if (staticProps) _defineProperties(Constructor, staticProps);
16
24
  return Constructor;
17
25
  }
26
+ function _iterable_to_array_limit(arr, i) {
27
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
28
+ if (_i == null) return;
29
+ var _arr = [];
30
+ var _n = true;
31
+ var _d = false;
32
+ var _s, _e;
33
+ try {
34
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
35
+ _arr.push(_s.value);
36
+ if (i && _arr.length === i) break;
37
+ }
38
+ } catch (err) {
39
+ _d = true;
40
+ _e = err;
41
+ } finally{
42
+ try {
43
+ if (!_n && _i["return"] != null) _i["return"]();
44
+ } finally{
45
+ if (_d) throw _e;
46
+ }
47
+ }
48
+ return _arr;
49
+ }
50
+ function _non_iterable_rest() {
51
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
52
+ }
53
+ function _sliced_to_array(arr, i) {
54
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
55
+ }
18
56
  function _type_of(obj) {
19
57
  "@swc/helpers - typeof";
20
58
  return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
21
59
  }
60
+ function _unsupported_iterable_to_array(o, minLen) {
61
+ if (!o) return;
62
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
63
+ var n = Object.prototype.toString.call(o).slice(8, -1);
64
+ if (n === "Object" && o.constructor) n = o.constructor.name;
65
+ if (n === "Map" || n === "Set") return Array.from(n);
66
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
67
+ }
22
68
  /*!
23
69
  * name: @jswork/howler-audio-manager
24
70
  * description: Howler.js audio manager, enum-driven sounds for H5.
25
71
  * homepage: https://js.work
26
- * version: 1.0.1
27
- * date: 2026-10-20 18:54:06
72
+ * version: 1.0.2
73
+ * date: 2026-10-20 20:03:06
28
74
  * license: MIT
29
75
  */ (function(g, f) {
30
76
  if ((typeof exports === "undefined" ? "undefined" : _type_of(exports)) == "object" && (typeof module === "undefined" ? "undefined" : _type_of(module)) < "u") {
@@ -93,8 +139,23 @@ function _type_of(obj) {
93
139
  AudioManager: function AudioManager1() {
94
140
  return AudioManager;
95
141
  },
142
+ AudioManagerProvider: function AudioManagerProvider1() {
143
+ return AudioManagerProvider;
144
+ },
96
145
  default: function _default() {
97
146
  return AudioManager;
147
+ },
148
+ useAudio: function useAudio1() {
149
+ return useAudio;
150
+ },
151
+ useAudioEvent: function useAudioEvent1() {
152
+ return useAudioEvent;
153
+ },
154
+ useGlobalVolume: function useGlobalVolume1() {
155
+ return useGlobalVolume;
156
+ },
157
+ usePreload: function usePreload1() {
158
+ return usePreload;
98
159
  }
99
160
  });
100
161
  module1.exports = __toCommonJS(index_exports);
@@ -436,6 +497,12 @@ function _type_of(obj) {
436
497
  }
437
498
  }
438
499
  },
500
+ {
501
+ key: "getGlobalVolume",
502
+ value: function getGlobalVolume() {
503
+ return this.globalVolume;
504
+ }
505
+ },
439
506
  {
440
507
  key: "setGlobalVolume",
441
508
  value: function setGlobalVolume(volume) {
@@ -475,9 +542,105 @@ function _type_of(obj) {
475
542
  ]);
476
543
  return AudioManager;
477
544
  }();
545
+ // src/react.tsx
546
+ var import_react = require("react");
547
+ var import_jsx_runtime = require("react/jsx-runtime");
548
+ var AudioManagerContext = (0, import_react.createContext)(null);
549
+ function AudioManagerProvider(param) {
550
+ var manager = param.manager, children = param.children;
551
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AudioManagerContext.Provider, {
552
+ value: manager,
553
+ children: children
554
+ });
555
+ }
556
+ function useAudio() {
557
+ var manager = (0, import_react.useContext)(AudioManagerContext);
558
+ if (!manager) {
559
+ throw new Error("useAudio must be used within an <AudioManagerProvider>.");
560
+ }
561
+ return manager;
562
+ }
563
+ function useAudioEvent(event, handler) {
564
+ var manager = useAudio();
565
+ var handlerRef = (0, import_react.useRef)(handler);
566
+ handlerRef.current = handler;
567
+ (0, import_react.useEffect)(function() {
568
+ var subscription = manager.on(event, function(payload) {
569
+ return handlerRef.current(payload);
570
+ });
571
+ return function() {
572
+ return subscription.destroy();
573
+ };
574
+ }, [
575
+ manager,
576
+ event
577
+ ]);
578
+ }
579
+ function useGlobalVolume() {
580
+ var manager = useAudio();
581
+ var _ref = _sliced_to_array((0, import_react.useState)(function() {
582
+ return manager.getGlobalVolume();
583
+ }), 2), volume = _ref[0], setVolumeState = _ref[1];
584
+ var setVolume = (0, import_react.useCallback)(function(next) {
585
+ manager.setGlobalVolume(next);
586
+ setVolumeState(next);
587
+ }, [
588
+ manager
589
+ ]);
590
+ return [
591
+ volume,
592
+ setVolume
593
+ ];
594
+ }
595
+ function usePreload(urls) {
596
+ var manager = useAudio();
597
+ var urlsKey = JSON.stringify(urls);
598
+ var _ref = _sliced_to_array((0, import_react.useState)({
599
+ status: "idle",
600
+ error: null
601
+ }), 2), state = _ref[0], setState = _ref[1];
602
+ (0, import_react.useEffect)(function() {
603
+ var list = JSON.parse(urlsKey);
604
+ if (list.length === 0) {
605
+ setState({
606
+ status: "idle",
607
+ error: null
608
+ });
609
+ return;
610
+ }
611
+ var cancelled = false;
612
+ setState({
613
+ status: "loading",
614
+ error: null
615
+ });
616
+ manager.preloadUrls(list).then(function() {
617
+ if (!cancelled) setState({
618
+ status: "loaded",
619
+ error: null
620
+ });
621
+ }, function(error) {
622
+ if (!cancelled) setState({
623
+ status: "error",
624
+ error: error
625
+ });
626
+ });
627
+ return function() {
628
+ cancelled = true;
629
+ };
630
+ }, [
631
+ manager,
632
+ urlsKey
633
+ ]);
634
+ return state;
635
+ }
478
636
  // Annotate the CommonJS export names for ESM import in node:
479
637
  0 && (module1.exports = {
480
- AudioManager: AudioManager
638
+ AudioManager: AudioManager,
639
+ AudioManagerProvider: AudioManagerProvider,
640
+ useAudio: useAudio,
641
+ useAudioEvent: useAudioEvent,
642
+ useGlobalVolume: useGlobalVolume,
643
+ usePreload: usePreload
481
644
  });
482
645
  if (__exports != exports1) module1.exports = exports1;
483
646
  return module1.exports;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/afei/github/howler-audio-manager/dist/index.umd.js","../src/index.ts","../src/manager.ts"],"names":["g","f","exports","module","define","amd","globalThis","self","__exports","__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","index_exports","AudioManager","default","import_howler","require","options","inject","effectCache","Map","urlAudioCache","preloadPromises","activeUrlHowl","globalVolume","listeners","HowlConstructor","HowlClass","Howl","HowlerInstance","HowlerObj","Howler","defaultVolume","setGlobalVolume","hasEffect","has","loadEffect","url","unload","howl","src","preload","on","emit","type","_id","error","set","loadEffects","map","keys","forEach","typedKey","isNaN","Number","playEffect","console","warn","String","playing","stop","volume","off","once","soundId","onStart","onEnd","onStop","onError","play","stopEffect","removeEffect","delete","preloadUrl","existing","state","Promise","resolve","pending","createUrlHowl","promise","reject","settle","then","preloadUrls","urls","playUrl","isSingleton","singleton","loop","stopUrl","event","handler","Set","add","destroy","payload","err","mute","muted","stopAll","clear"],"mappings":"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","sourcesContent":["/*!\n * name: @jswork/howler-audio-manager\n * description: Howler.js audio manager, enum-driven sounds for H5.\n * homepage: https://js.work\n * version: 1.0.1\n * date: 2026-10-20 18:54:06\n * license: MIT\n */\n(function(g,f){if(typeof exports==\"object\"&&typeof module<\"u\"){module.exports=f()}else if(\"function\"==typeof define && define.amd){define(\"urlLiteral\",f)}else {g[\"urlLiteral\"]=f()}}(typeof globalThis < \"u\" ? globalThis : typeof self < \"u\" ? self : this,function(){var exports={};var __exports=exports;var module={exports};\n\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/index.ts\nvar index_exports = {};\n__export(index_exports, {\n AudioManager: () => AudioManager,\n default: () => AudioManager\n});\nmodule.exports = __toCommonJS(index_exports);\n\n// src/manager.ts\nvar import_howler = require(\"howler\");\nvar AudioManager = class {\n constructor(options, inject) {\n this.effectCache = /* @__PURE__ */ new Map();\n this.urlAudioCache = /* @__PURE__ */ new Map();\n this.preloadPromises = /* @__PURE__ */ new Map();\n this.activeUrlHowl = null;\n this.globalVolume = 1;\n this.listeners = /* @__PURE__ */ new Map();\n this.HowlConstructor = inject?.HowlClass ?? import_howler.Howl;\n this.HowlerInstance = inject?.HowlerObj ?? import_howler.Howler;\n if (typeof options?.defaultVolume === \"number\") {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n hasEffect(key) {\n return this.effectCache.has(key);\n }\n loadEffect(key, url) {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key).unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"effect\", key, url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.emit(\"error\", { type: \"effect\", key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n loadEffects(map) {\n Object.keys(map).forEach((key) => {\n const typedKey = isNaN(Number(key)) ? key : Number(key);\n this.loadEffect(typedKey, map[key]);\n });\n }\n playEffect(key, options) {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return void 0;\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"effect\", key, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"effect\", key, soundId });\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"effect\", key, soundId });\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"effect\", key, soundId, error });\n });\n return howl.play();\n }\n stopEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n removeEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n preloadUrl(url) {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === \"loaded\") {\n return Promise.resolve();\n }\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n const howl = existing && existing.state() !== \"unloaded\" ? existing : this.createUrlHowl(url);\n const promise = new Promise((resolve, reject) => {\n howl.once(\"load\", () => {\n resolve();\n });\n howl.once(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n return promise;\n }\n preloadUrls(urls) {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n createUrlHowl(url) {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"url\", url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n this.emit(\"error\", { type: \"url\", url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n playUrl(url, options) {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n const isSingleton = options?.singleton !== false;\n if (isSingleton && this.activeUrlHowl && this.activeUrlHowl !== howl && this.activeUrlHowl.playing()) {\n this.activeUrlHowl.stop();\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (options?.loop !== void 0) {\n howl.loop(options.loop);\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"url\", url, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"url\", url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n return howl.play();\n }\n stopUrl(url) {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n on(event, handler) {\n const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n emit(event, payload) {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n setGlobalVolume(volume) {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n mute(muted) {\n this.HowlerInstance.mute(muted);\n }\n stopAll() {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n destroy() {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n};\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n AudioManager\n});\n\nif(__exports != exports)module.exports = exports;return module.exports}));\n","export * from './types';\nexport { AudioManager } from './manager';\nexport { AudioManager as default } from './manager';\n","import { Howl, Howler } from 'howler';\nimport type {\n AudioManagerOptions,\n PlayOptions,\n PlayUrlOptions,\n AudioEventType,\n AudioEventPayload,\n EventSubscription,\n SoundResourceMap\n} from './types';\n\nexport class AudioManager<K extends string | number = string> {\n private effectCache = new Map<K, Howl>();\n private urlAudioCache = new Map<string, Howl>();\n private preloadPromises = new Map<string, Promise<void>>();\n private activeUrlHowl: Howl | null = null;\n private globalVolume = 1.0;\n private listeners = new Map<AudioEventType, Set<(payload: AudioEventPayload<K>) => void>>();\n\n private HowlConstructor: typeof Howl;\n private HowlerInstance: typeof Howler;\n\n constructor(\n options?: AudioManagerOptions,\n inject?: { HowlClass?: typeof Howl; HowlerObj?: typeof Howler }\n ) {\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === 'number') {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n\n hasEffect(key: K): boolean {\n return this.effectCache.has(key);\n }\n\n loadEffect(key: K, url: string): void {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key)!.unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'effect', key, url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.emit('error', { type: 'effect', key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n\n loadEffects(map: SoundResourceMap<K>): void {\n Object.keys(map).forEach((key) => {\n const typedKey = (isNaN(Number(key)) ? key : Number(key)) as K;\n this.loadEffect(typedKey, map[key as any]);\n });\n }\n\n playEffect(key: K, options?: PlayOptions): number | undefined {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return undefined;\n }\n\n if (howl.playing()) {\n howl.stop();\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'effect', key, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'effect', key, soundId });\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'effect', key, soundId });\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'effect', key, soundId, error });\n });\n\n return howl.play();\n }\n\n stopEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n\n removeEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n\n preloadUrl(url: string): Promise<void> {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === 'loaded') {\n return Promise.resolve();\n }\n\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n\n // A howl in a terminal failed state (unloaded after error) must be replaced\n // so a fresh instance can retry the load.\n const howl = existing && existing.state() !== 'unloaded' ? existing : this.createUrlHowl(url);\n\n const promise = new Promise<void>((resolve, reject) => {\n howl.once('load', () => {\n resolve();\n });\n\n howl.once('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n\n return promise;\n }\n\n preloadUrls(urls: string[]): Promise<void[]> {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n\n private createUrlHowl(url: string): Howl {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'url', url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n this.emit('error', { type: 'url', url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n\n playUrl(url: string, options?: PlayUrlOptions): number {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n\n // Singleton mutex: stop currently active URL audio if singleton !== false\n const isSingleton = options?.singleton !== false;\n if (\n isSingleton &&\n this.activeUrlHowl &&\n this.activeUrlHowl !== howl &&\n this.activeUrlHowl.playing()\n ) {\n this.activeUrlHowl.stop();\n }\n\n // Overlap prevention on the same audio instance\n if (howl.playing()) {\n howl.stop();\n }\n\n if (options?.loop !== undefined) {\n howl.loop(options.loop);\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'url', url, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'url', url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n return howl.play();\n }\n\n stopUrl(url: string): void {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n\n on(event: AudioEventType, handler: (payload: AudioEventPayload<K>) => void): EventSubscription {\n const set = this.listeners.get(event) ?? new Set<(payload: AudioEventPayload<K>) => void>();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n\n protected emit(event: AudioEventType, payload: AudioEventPayload<K>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n\n setGlobalVolume(volume: number): void {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n\n mute(muted: boolean): void {\n this.HowlerInstance.mute(muted);\n }\n\n stopAll(): void {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n\n destroy(): void {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/afei/github/howler-audio-manager/dist/index.umd.js","../src/index.ts","../src/manager.ts","../src/react.tsx"],"names":["g","f","exports","module","define","amd","globalThis","self","__exports","__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","index_exports","AudioManager","AudioManagerProvider","default","useAudio","useAudioEvent","useGlobalVolume","usePreload","import_howler","require","options","inject","effectCache","Map","urlAudioCache","preloadPromises","activeUrlHowl","globalVolume","listeners","HowlConstructor","HowlClass","Howl","HowlerInstance","HowlerObj","Howler","defaultVolume","setGlobalVolume","hasEffect","has","loadEffect","url","unload","howl","src","preload","on","emit","type","_id","error","set","loadEffects","map","keys","forEach","typedKey","isNaN","Number","playEffect","console","warn","String","playing","stop","volume","off","once","soundId","onStart","onEnd","onStop","onError","play","stopEffect","removeEffect","delete","preloadUrl","existing","state","Promise","resolve","pending","createUrlHowl","promise","reject","settle","then","preloadUrls","urls","playUrl","isSingleton","singleton","loop","stopUrl","event","handler","Set","add","destroy","payload","err","getGlobalVolume","mute","muted","stopAll","clear","import_react","import_jsx_runtime","AudioManagerContext","createContext","manager","children","jsx","Provider","useContext","Error","handlerRef","useRef","current","useEffect","subscription","useState","setVolumeState","setVolume","useCallback","next","urlsKey","JSON","stringify","status","setState","list","parse","length","cancelled"],"mappings":"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","sourcesContent":["/*!\n * name: @jswork/howler-audio-manager\n * description: Howler.js audio manager, enum-driven sounds for H5.\n * homepage: https://js.work\n * version: 1.0.2\n * date: 2026-10-20 20:03:06\n * license: MIT\n */\n(function(g,f){if(typeof exports==\"object\"&&typeof module<\"u\"){module.exports=f()}else if(\"function\"==typeof define && define.amd){define(\"urlLiteral\",f)}else {g[\"urlLiteral\"]=f()}}(typeof globalThis < \"u\" ? globalThis : typeof self < \"u\" ? self : this,function(){var exports={};var __exports=exports;var module={exports};\n\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/index.ts\nvar index_exports = {};\n__export(index_exports, {\n AudioManager: () => AudioManager,\n AudioManagerProvider: () => AudioManagerProvider,\n default: () => AudioManager,\n useAudio: () => useAudio,\n useAudioEvent: () => useAudioEvent,\n useGlobalVolume: () => useGlobalVolume,\n usePreload: () => usePreload\n});\nmodule.exports = __toCommonJS(index_exports);\n\n// src/manager.ts\nvar import_howler = require(\"howler\");\nvar AudioManager = class {\n constructor(options, inject) {\n this.effectCache = /* @__PURE__ */ new Map();\n this.urlAudioCache = /* @__PURE__ */ new Map();\n this.preloadPromises = /* @__PURE__ */ new Map();\n this.activeUrlHowl = null;\n this.globalVolume = 1;\n this.listeners = /* @__PURE__ */ new Map();\n this.HowlConstructor = inject?.HowlClass ?? import_howler.Howl;\n this.HowlerInstance = inject?.HowlerObj ?? import_howler.Howler;\n if (typeof options?.defaultVolume === \"number\") {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n hasEffect(key) {\n return this.effectCache.has(key);\n }\n loadEffect(key, url) {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key).unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"effect\", key, url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.emit(\"error\", { type: \"effect\", key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n loadEffects(map) {\n Object.keys(map).forEach((key) => {\n const typedKey = isNaN(Number(key)) ? key : Number(key);\n this.loadEffect(typedKey, map[key]);\n });\n }\n playEffect(key, options) {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return void 0;\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"effect\", key, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"effect\", key, soundId });\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"effect\", key, soundId });\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"effect\", key, soundId, error });\n });\n return howl.play();\n }\n stopEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n removeEffect(key) {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n preloadUrl(url) {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === \"loaded\") {\n return Promise.resolve();\n }\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n const howl = existing && existing.state() !== \"unloaded\" ? existing : this.createUrlHowl(url);\n const promise = new Promise((resolve, reject) => {\n howl.once(\"load\", () => {\n resolve();\n });\n howl.once(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n return promise;\n }\n preloadUrls(urls) {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n createUrlHowl(url) {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on(\"load\", () => {\n this.emit(\"load\", { type: \"url\", url });\n });\n howl.on(\"loaderror\", (_id, error) => {\n this.urlAudioCache.delete(url);\n this.emit(\"error\", { type: \"url\", url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n playUrl(url, options) {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n const isSingleton = options?.singleton !== false;\n if (isSingleton && this.activeUrlHowl && this.activeUrlHowl !== howl && this.activeUrlHowl.playing()) {\n this.activeUrlHowl.stop();\n }\n if (howl.playing()) {\n howl.stop();\n }\n if (options?.loop !== void 0) {\n howl.loop(options.loop);\n }\n if (typeof options?.volume === \"number\") {\n howl.volume(options.volume);\n }\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n howl.off(\"play\");\n howl.off(\"end\");\n howl.off(\"stop\");\n howl.off(\"playerror\");\n howl.once(\"play\", (soundId) => {\n options?.onStart?.(soundId);\n this.emit(\"play\", { type: \"url\", url, soundId });\n });\n howl.once(\"end\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit(\"end\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"stop\", (soundId) => {\n if (typeof options?.volume === \"number\") {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit(\"stop\", { type: \"url\", url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n howl.once(\"playerror\", (soundId, error) => {\n options?.onError?.(soundId, error);\n this.emit(\"error\", { type: \"url\", url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n return howl.play();\n }\n stopUrl(url) {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n on(event, handler) {\n const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n emit(event, payload) {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n getGlobalVolume() {\n return this.globalVolume;\n }\n setGlobalVolume(volume) {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n mute(muted) {\n this.HowlerInstance.mute(muted);\n }\n stopAll() {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n destroy() {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n};\n\n// src/react.tsx\nvar import_react = require(\"react\");\nvar import_jsx_runtime = require(\"react/jsx-runtime\");\nvar AudioManagerContext = (0, import_react.createContext)(null);\nfunction AudioManagerProvider({\n manager,\n children\n}) {\n return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AudioManagerContext.Provider, { value: manager, children });\n}\nfunction useAudio() {\n const manager = (0, import_react.useContext)(AudioManagerContext);\n if (!manager) {\n throw new Error(\"useAudio must be used within an <AudioManagerProvider>.\");\n }\n return manager;\n}\nfunction useAudioEvent(event, handler) {\n const manager = useAudio();\n const handlerRef = (0, import_react.useRef)(handler);\n handlerRef.current = handler;\n (0, import_react.useEffect)(() => {\n const subscription = manager.on(event, (payload) => handlerRef.current(payload));\n return () => subscription.destroy();\n }, [manager, event]);\n}\nfunction useGlobalVolume() {\n const manager = useAudio();\n const [volume, setVolumeState] = (0, import_react.useState)(() => manager.getGlobalVolume());\n const setVolume = (0, import_react.useCallback)(\n (next) => {\n manager.setGlobalVolume(next);\n setVolumeState(next);\n },\n [manager]\n );\n return [volume, setVolume];\n}\nfunction usePreload(urls) {\n const manager = useAudio();\n const urlsKey = JSON.stringify(urls);\n const [state, setState] = (0, import_react.useState)({ status: \"idle\", error: null });\n (0, import_react.useEffect)(() => {\n const list = JSON.parse(urlsKey);\n if (list.length === 0) {\n setState({ status: \"idle\", error: null });\n return;\n }\n let cancelled = false;\n setState({ status: \"loading\", error: null });\n manager.preloadUrls(list).then(\n () => {\n if (!cancelled) setState({ status: \"loaded\", error: null });\n },\n (error) => {\n if (!cancelled) setState({ status: \"error\", error });\n }\n );\n return () => {\n cancelled = true;\n };\n }, [manager, urlsKey]);\n return state;\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n AudioManager,\n AudioManagerProvider,\n useAudio,\n useAudioEvent,\n useGlobalVolume,\n usePreload\n});\n\nif(__exports != exports)module.exports = exports;return module.exports}));\n","export * from './types';\nexport { AudioManager } from './manager';\nexport { AudioManager as default } from './manager';\nexport * from './react';\n\n","import { Howl, Howler } from 'howler';\nimport type {\n AudioManagerOptions,\n PlayOptions,\n PlayUrlOptions,\n AudioEventType,\n AudioEventPayload,\n EventSubscription,\n SoundResourceMap\n} from './types';\n\nexport class AudioManager<K extends string | number = string> {\n private effectCache = new Map<K, Howl>();\n private urlAudioCache = new Map<string, Howl>();\n private preloadPromises = new Map<string, Promise<void>>();\n private activeUrlHowl: Howl | null = null;\n private globalVolume = 1.0;\n private listeners = new Map<AudioEventType, Set<(payload: AudioEventPayload<K>) => void>>();\n\n private HowlConstructor: typeof Howl;\n private HowlerInstance: typeof Howler;\n\n constructor(\n options?: AudioManagerOptions,\n inject?: { HowlClass?: typeof Howl; HowlerObj?: typeof Howler }\n ) {\n this.HowlConstructor = inject?.HowlClass ?? Howl;\n this.HowlerInstance = inject?.HowlerObj ?? Howler;\n if (typeof options?.defaultVolume === 'number') {\n this.setGlobalVolume(options.defaultVolume);\n }\n }\n\n hasEffect(key: K): boolean {\n return this.effectCache.has(key);\n }\n\n loadEffect(key: K, url: string): void {\n if (this.effectCache.has(key)) {\n this.effectCache.get(key)!.unload();\n }\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'effect', key, url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.emit('error', { type: 'effect', key, url, error });\n });\n this.effectCache.set(key, howl);\n }\n\n loadEffects(map: SoundResourceMap<K>): void {\n Object.keys(map).forEach((key) => {\n const typedKey = (isNaN(Number(key)) ? key : Number(key)) as K;\n this.loadEffect(typedKey, map[key as any]);\n });\n }\n\n playEffect(key: K, options?: PlayOptions): number | undefined {\n const howl = this.effectCache.get(key);\n if (!howl) {\n console.warn(`[AudioManager] Effect not found for key: ${String(key)}.`);\n return undefined;\n }\n\n if (howl.playing()) {\n howl.stop();\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'effect', key, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'effect', key, soundId });\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'effect', key, soundId });\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'effect', key, soundId, error });\n });\n\n return howl.play();\n }\n\n stopEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.stop();\n }\n }\n\n removeEffect(key: K): void {\n const howl = this.effectCache.get(key);\n if (howl) {\n howl.unload();\n this.effectCache.delete(key);\n }\n }\n\n preloadUrl(url: string): Promise<void> {\n const existing = this.urlAudioCache.get(url);\n if (existing && existing.state() === 'loaded') {\n return Promise.resolve();\n }\n\n const pending = this.preloadPromises.get(url);\n if (pending) {\n return pending;\n }\n\n // A howl in a terminal failed state (unloaded after error) must be replaced\n // so a fresh instance can retry the load.\n const howl = existing && existing.state() !== 'unloaded' ? existing : this.createUrlHowl(url);\n\n const promise = new Promise<void>((resolve, reject) => {\n howl.once('load', () => {\n resolve();\n });\n\n howl.once('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n reject(error);\n });\n });\n\n this.preloadPromises.set(url, promise);\n const settle = () => {\n if (this.preloadPromises.get(url) === promise) {\n this.preloadPromises.delete(url);\n }\n };\n promise.then(settle, settle);\n\n return promise;\n }\n\n preloadUrls(urls: string[]): Promise<void[]> {\n return Promise.all(urls.map((url) => this.preloadUrl(url)));\n }\n\n private createUrlHowl(url: string): Howl {\n const howl = new this.HowlConstructor({\n src: [url],\n preload: true\n });\n howl.on('load', () => {\n this.emit('load', { type: 'url', url });\n });\n howl.on('loaderror', (_id: number, error: unknown) => {\n this.urlAudioCache.delete(url);\n this.emit('error', { type: 'url', url, error });\n });\n this.urlAudioCache.set(url, howl);\n return howl;\n }\n\n playUrl(url: string, options?: PlayUrlOptions): number {\n const howl = this.urlAudioCache.get(url) || this.createUrlHowl(url);\n\n // Singleton mutex: stop currently active URL audio if singleton !== false\n const isSingleton = options?.singleton !== false;\n if (\n isSingleton &&\n this.activeUrlHowl &&\n this.activeUrlHowl !== howl &&\n this.activeUrlHowl.playing()\n ) {\n this.activeUrlHowl.stop();\n }\n\n // Overlap prevention on the same audio instance\n if (howl.playing()) {\n howl.stop();\n }\n\n if (options?.loop !== undefined) {\n howl.loop(options.loop);\n }\n\n if (typeof options?.volume === 'number') {\n howl.volume(options.volume);\n }\n\n if (isSingleton) {\n this.activeUrlHowl = howl;\n }\n\n // Clear handlers from previous plays to avoid stale/duplicate callbacks.\n howl.off('play');\n howl.off('end');\n howl.off('stop');\n howl.off('playerror');\n\n howl.once('play', (soundId: number) => {\n options?.onStart?.(soundId);\n this.emit('play', { type: 'url', url, soundId });\n });\n\n howl.once('end', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onEnd?.(soundId);\n this.emit('end', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl && !howl.loop()) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('stop', (soundId: number) => {\n if (typeof options?.volume === 'number') {\n howl.volume(1);\n }\n options?.onStop?.(soundId);\n this.emit('stop', { type: 'url', url, soundId });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n howl.once('playerror', (soundId: number, error: unknown) => {\n options?.onError?.(soundId, error);\n this.emit('error', { type: 'url', url, soundId, error });\n if (this.activeUrlHowl === howl) {\n this.activeUrlHowl = null;\n }\n });\n\n return howl.play();\n }\n\n stopUrl(url: string): void {\n const howl = this.urlAudioCache.get(url);\n if (howl) {\n howl.stop();\n }\n }\n\n on(event: AudioEventType, handler: (payload: AudioEventPayload<K>) => void): EventSubscription {\n const set = this.listeners.get(event) ?? new Set<(payload: AudioEventPayload<K>) => void>();\n this.listeners.set(event, set);\n set.add(handler);\n return {\n destroy: () => {\n set.delete(handler);\n }\n };\n }\n\n protected emit(event: AudioEventType, payload: AudioEventPayload<K>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[AudioManager] Error in event listener for ${event}:`, err);\n }\n });\n }\n }\n\n getGlobalVolume(): number {\n return this.globalVolume;\n }\n\n setGlobalVolume(volume: number): void {\n this.globalVolume = volume;\n this.HowlerInstance.volume(volume);\n }\n\n mute(muted: boolean): void {\n this.HowlerInstance.mute(muted);\n }\n\n stopAll(): void {\n this.HowlerInstance.stop();\n this.activeUrlHowl = null;\n }\n\n destroy(): void {\n this.effectCache.forEach((howl) => howl.unload());\n this.urlAudioCache.forEach((howl) => howl.unload());\n this.effectCache.clear();\n this.urlAudioCache.clear();\n this.preloadPromises.clear();\n this.listeners.clear();\n this.activeUrlHowl = null;\n }\n}\n","import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { AudioManager } from './manager';\nimport type { AudioEventType, AudioEventPayload } from './types';\n\nconst AudioManagerContext = createContext<AudioManager<any> | null>(null);\n\nexport interface AudioManagerProviderProps<K extends string | number = string> {\n manager: AudioManager<K>;\n children?: ReactNode;\n}\n\nexport function AudioManagerProvider<K extends string | number = string>({\n manager,\n children\n}: AudioManagerProviderProps<K>) {\n return (\n <AudioManagerContext.Provider value={manager}>\n {children}\n </AudioManagerContext.Provider>\n );\n}\n\nexport function useAudio<K extends string | number = string>(): AudioManager<K> {\n const manager = useContext(AudioManagerContext);\n if (!manager) {\n throw new Error('useAudio must be used within an <AudioManagerProvider>.');\n }\n return manager as unknown as AudioManager<K>;\n}\n\nexport function useAudioEvent<K extends string | number = string>(\n event: AudioEventType,\n handler: (payload: AudioEventPayload<K>) => void\n): void {\n const manager = useAudio<K>();\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const subscription = manager.on(event, (payload) => handlerRef.current(payload));\n return () => subscription.destroy();\n }, [manager, event]);\n}\n\nexport function useGlobalVolume(): [number, (volume: number) => void] {\n const manager = useAudio();\n const [volume, setVolumeState] = useState(() => manager.getGlobalVolume());\n\n const setVolume = useCallback(\n (next: number) => {\n manager.setGlobalVolume(next);\n setVolumeState(next);\n },\n [manager]\n );\n\n return [volume, setVolume];\n}\n\nexport interface PreloadState {\n status: 'idle' | 'loading' | 'loaded' | 'error';\n error: unknown;\n}\n\nexport function usePreload(urls: string[]): PreloadState {\n const manager = useAudio();\n const urlsKey = JSON.stringify(urls);\n const [state, setState] = useState<PreloadState>({ status: 'idle', error: null });\n\n useEffect(() => {\n const list: string[] = JSON.parse(urlsKey);\n if (list.length === 0) {\n setState({ status: 'idle', error: null });\n return;\n }\n\n let cancelled = false;\n setState({ status: 'loading', error: null });\n\n manager.preloadUrls(list).then(\n () => {\n if (!cancelled) setState({ status: 'loaded', error: null });\n },\n (error) => {\n if (!cancelled) setState({ status: 'error', error });\n }\n );\n\n return () => {\n cancelled = true;\n };\n }, [manager, urlsKey]);\n\n return state;\n}\n\n\n"]}
package/package.json CHANGED
@@ -1,53 +1,68 @@
1
1
  {
2
- "name": "@jswork/howler-audio-manager",
3
- "version": "1.0.1",
4
2
  "description": "Howler.js audio manager, enum-driven sounds for H5.",
5
- "npmClient": "pnpm",
6
- "main": "dist/index.cjs.js",
7
- "module": "dist/index.esm.js",
8
- "types": "dist/index.d.ts",
9
- "files": [
10
- "src",
11
- "dist"
12
- ],
13
- "prettier": "@jswork/style-guide/prettier",
14
- "scripts": {
15
- "build": "tsup",
16
- "test": "bun test",
17
- "release": "release-it"
18
- },
19
- "license": "MIT",
20
- "peerDependencies": {
21
- "howler": "^2.2.4"
22
- },
23
- "peerDependenciesMeta": {
24
- "howler": {
25
- "optional": false
26
- }
27
- },
28
3
  "devDependencies": {
4
+ "@happy-dom/global-registrator": "^21.0.3",
29
5
  "@jswork/style-guide": "^1.0.3",
30
6
  "@jswork/tsup-banner": "^1.0.4",
31
7
  "@swc/core": "^1.5.5",
8
+ "@testing-library/react": "^16.3.3",
32
9
  "@types/howler": "^2.2.12",
33
10
  "@types/jest": "^29.5.12",
34
11
  "@types/node": "^20.12.11",
12
+ "@types/react": "^18.3.31",
13
+ "@types/react-dom": "^18.3.7",
35
14
  "esbuild-plugin-replace": "^1.4.0",
36
15
  "esbuild-plugin-umd-wrapper": "^3.0.0",
37
16
  "howler": "^2.2.4",
38
17
  "prettier": "^3.3.3",
18
+ "react": "^18.3.1",
19
+ "react-dom": "^18.3.1",
39
20
  "tsup": "^8.2.4",
40
21
  "typescript": "^4.9.5"
41
22
  },
42
- "publishConfig": {
43
- "access": "public",
44
- "registry": "https://registry.npmjs.org"
45
- },
23
+ "files": [
24
+ "src",
25
+ "dist"
26
+ ],
46
27
  "homepage": "https://js.work",
47
28
  "keywords": [
48
29
  "howler",
49
30
  "audio-manager",
50
31
  "h5-audio",
51
- "sound-effects"
52
- ]
32
+ "sound-effects",
33
+ "audio-management",
34
+ "howlerjs",
35
+ "dual-caching",
36
+ "audio-playback",
37
+ "memory-leak-prevention"
38
+ ],
39
+ "license": "MIT",
40
+ "main": "dist/index.cjs.js",
41
+ "module": "dist/index.esm.js",
42
+ "name": "@jswork/howler-audio-manager",
43
+ "npmClient": "pnpm",
44
+ "peerDependencies": {
45
+ "howler": "^2.2.4",
46
+ "react": ">=16.8.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "howler": {
50
+ "optional": false
51
+ },
52
+ "react": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "prettier": "@jswork/style-guide/prettier",
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "registry": "https://registry.npmjs.org"
60
+ },
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "release": "release-it",
64
+ "test": "bun test"
65
+ },
66
+ "types": "dist/index.d.ts",
67
+ "version": "1.0.2"
53
68
  }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './types';
2
2
  export { AudioManager } from './manager';
3
3
  export { AudioManager as default } from './manager';
4
+ export * from './react';
5
+