{"version":3,"sources":["../src/index.ts","../src/use-on-mount.ts"],"sourcesContent":["import React from 'react';\n\nimport useOnMount from './use-on-mount';\n\nimport { HookOptions, PlayFunction, PlayOptions, ReturnedValue } from './types';\n\nexport default function useSound<T = any>(\n  src: string | string[],\n  {\n    id,\n    volume = 1,\n    playbackRate = 1,\n    soundEnabled = true,\n    interrupt = false,\n    onload,\n    ...delegated\n  }: HookOptions<T> = {} as HookOptions\n) {\n  const HowlConstructor = React.useRef<typeof Howl | null>(null);\n  const howlerGlobalRef = React.useRef<HowlerGlobal | null>(null);\n  const isHowlerLoaded = React.useRef(false);\n  const latestLoadId = React.useRef(0);\n  const activeSoundRef = React.useRef<Howl | null>(null);\n  const activeSpritePlaybackIds = React.useRef<Map<string, Set<number>>>(\n    new Map()\n  );\n  const isMounted = React.useRef(false);\n  const hasSprite = Boolean(delegated.sprite);\n\n  const [duration, setDuration] = React.useState<number | null>(null);\n  const [isReady, setIsReady] = React.useState(false);\n\n  const [sound, setSound] = React.useState<Howl | null>(null);\n\n  // We want to lazy-load Howler, since sounds can't play on load anyway.\n  useOnMount(() => {\n    isMounted.current = true;\n\n    import('howler').then(mod => {\n      if (!isMounted.current) {\n        return;\n      }\n\n      // Depending on the module system used, `mod` might hold\n      // the export directly, or it might be under `default`.\n      HowlConstructor.current = mod.Howl ?? mod.default.Howl;\n      howlerGlobalRef.current = mod.Howler ?? mod.default.Howler;\n      isHowlerLoaded.current = true;\n      setIsReady(true);\n    });\n\n    return () => {\n      isMounted.current = false;\n      isHowlerLoaded.current = false;\n      latestLoadId.current += 1;\n      if (activeSoundRef.current) {\n        activeSoundRef.current.stop();\n        activeSoundRef.current.unload();\n        activeSoundRef.current = null;\n      }\n      activeSpritePlaybackIds.current.clear();\n    };\n  });\n\n  // When the `src` changes, we have to do a whole thing where we recreate\n  // the Howl instance. This is because Howler doesn't expose a way to\n  // tweak the sound\n  React.useEffect(() => {\n    if (!HowlConstructor.current || !isHowlerLoaded.current) {\n      return;\n    }\n\n    const currentLoadId = latestLoadId.current + 1;\n    latestLoadId.current = currentLoadId;\n\n    const handleLoad = function(this: Howl) {\n      if (typeof onload === 'function') {\n        onload.call(this);\n      }\n\n      if (!isMounted.current || currentLoadId !== latestLoadId.current) {\n        return;\n      }\n\n      setDuration(this.duration() * 1000);\n    };\n\n    const nextSound = new HowlConstructor.current({\n      src: Array.isArray(src) ? src : [src],\n      volume,\n      rate: playbackRate,\n      onload: handleLoad,\n      ...delegated,\n    });\n\n    const previousSound = activeSoundRef.current;\n    if (previousSound) {\n      previousSound.stop();\n      previousSound.unload();\n    }\n    // Any tracked playback ids belong to the previous Howl instance.\n    // Clear them so stop/pause lookups never target stale ids.\n    activeSpritePlaybackIds.current.clear();\n    setSound(nextSound);\n    activeSoundRef.current = nextSound;\n\n    // The linter wants to run this effect whenever ANYTHING changes,\n    // but very specifically I only want to recreate the Howl instance\n    // when the `src` changes. Other changes should have no effect.\n    // Passing array to the useEffect dependencies list will result in\n    // ifinite loop so we need to stringify it, for more details check\n    // https://github.com/facebook/react/issues/14476#issuecomment-471199055\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [JSON.stringify(src), isReady]);\n\n  // Whenever volume/playbackRate are changed, change those properties\n  // on the sound instance.\n  React.useEffect(() => {\n    if (sound) {\n      sound.volume(volume);\n\n      // HACK: When a sprite is defined, `sound.rate()` throws an error, because Howler tries to reset the \"_default\" sprite, which doesn't exist. This is likely a bug within Howler, but I don’t have the bandwidth to investigate, so instead, we’re ignoring playbackRate changes when a sprite is defined.\n      if (!hasSprite) {\n        sound.rate(playbackRate);\n      }\n    }\n  }, [sound, volume, playbackRate, hasSprite]);\n\n  const play: PlayFunction = React.useCallback(\n    (options?: PlayOptions) => {\n      if (typeof options === 'undefined') {\n        options = {};\n      }\n\n      if (!sound || (!soundEnabled && !options.forceSoundEnabled)) {\n        return;\n      }\n\n      if (interrupt) {\n        sound.stop();\n        activeSpritePlaybackIds.current.clear();\n      }\n\n      if (typeof options.playbackRate !== 'undefined' && !hasSprite) {\n        sound.rate(options.playbackRate);\n      }\n\n      const spriteKey = options.id ?? id;\n      const playbackId = sound.play(spriteKey);\n\n      if (typeof spriteKey === 'string' && typeof playbackId === 'number') {\n        const playbackIdsForKey =\n          activeSpritePlaybackIds.current.get(spriteKey) ?? new Set<number>();\n\n        playbackIdsForKey.add(playbackId);\n        activeSpritePlaybackIds.current.set(spriteKey, playbackIdsForKey);\n\n        sound.once(\n          'end',\n          () => {\n            const ids = activeSpritePlaybackIds.current.get(spriteKey);\n            if (!ids) {\n              return;\n            }\n            ids.delete(playbackId);\n            if (ids.size === 0) {\n              activeSpritePlaybackIds.current.delete(spriteKey);\n            }\n          },\n          playbackId\n        );\n      }\n    },\n    [sound, soundEnabled, interrupt, hasSprite, id]\n  );\n\n  const stop = React.useCallback(\n    (id?: string) => {\n      if (!sound) {\n        return;\n      }\n\n      if (typeof id === 'string') {\n        const playbackIds = activeSpritePlaybackIds.current.get(id);\n        if (playbackIds && playbackIds.size > 0) {\n          playbackIds.forEach(playbackId => {\n            sound.stop(playbackId);\n          });\n          activeSpritePlaybackIds.current.delete(id);\n          return;\n        }\n      }\n\n      sound.stop(id as any);\n\n      if (typeof id === 'undefined') {\n        activeSpritePlaybackIds.current.clear();\n      }\n    },\n    [sound]\n  );\n\n  const pause = React.useCallback(\n    (id?: string) => {\n      if (!sound) {\n        return;\n      }\n\n      if (typeof id === 'string') {\n        const playbackIds = activeSpritePlaybackIds.current.get(id);\n        if (playbackIds && playbackIds.size > 0) {\n          playbackIds.forEach(playbackId => {\n            sound.pause(playbackId);\n          });\n          return;\n        }\n      }\n\n      sound.pause(id as any);\n    },\n    [sound]\n  );\n\n  const unlock = React.useCallback(() => {\n    if (!howlerGlobalRef.current || !howlerGlobalRef.current.ctx) {\n      return;\n    }\n\n    if (howlerGlobalRef.current.ctx.state === 'suspended') {\n      try {\n        void howlerGlobalRef.current.ctx.resume().catch(() => {\n          // Ignore resume failures; unlock is a best-effort helper.\n        });\n      } catch {\n        // Ignore sync resume errors; unlock is a best-effort helper.\n      }\n    }\n  }, []);\n\n  const returnedValue: ReturnedValue = [\n    play,\n    {\n      sound,\n      stop,\n      pause,\n      unlock,\n      duration,\n    },\n  ];\n\n  return returnedValue;\n}\n\nexport { useSound };\n","import * as React from 'react';\n\nexport default function useOnMount(callback: React.EffectCallback) {\n  React.useEffect(callback, []);\n}\n"],"mappings":"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","names":["React","React","id"]}