sounds.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { isAction } from '@reduxjs/toolkit';
  2. import type { Middleware, UnknownAction } from '@reduxjs/toolkit';
  3. import ready from 'mastodon/ready';
  4. import { assetHost } from 'mastodon/utils/config';
  5. import type { RootState } from '..';
  6. interface AudioSource {
  7. src: string;
  8. type: string;
  9. }
  10. interface ActionWithMetaSound extends UnknownAction {
  11. meta: { sound: string };
  12. }
  13. function isActionWithMetaSound(action: unknown): action is ActionWithMetaSound {
  14. return (
  15. isAction(action) &&
  16. 'meta' in action &&
  17. typeof action.meta === 'object' &&
  18. !!action.meta &&
  19. 'sound' in action.meta &&
  20. typeof action.meta.sound === 'string'
  21. );
  22. }
  23. const createAudio = (sources: AudioSource[]) => {
  24. const audio = new Audio();
  25. sources.forEach(({ type, src }) => {
  26. const source = document.createElement('source');
  27. source.type = type;
  28. source.src = src;
  29. audio.appendChild(source);
  30. });
  31. return audio;
  32. };
  33. const play = (audio: HTMLAudioElement) => {
  34. if (!audio.paused) {
  35. audio.pause();
  36. if (typeof audio.fastSeek === 'function') {
  37. audio.fastSeek(0);
  38. } else {
  39. audio.currentTime = 0;
  40. }
  41. }
  42. void audio.play();
  43. };
  44. export const soundsMiddleware = (): Middleware<
  45. Record<string, never>,
  46. RootState
  47. > => {
  48. const soundCache: Record<string, HTMLAudioElement> = {};
  49. void ready(() => {
  50. soundCache.boop = createAudio([
  51. {
  52. src: `${assetHost}/sounds/boop.ogg`,
  53. type: 'audio/ogg',
  54. },
  55. {
  56. src: `${assetHost}/sounds/boop.mp3`,
  57. type: 'audio/mpeg',
  58. },
  59. ]);
  60. });
  61. return () => (next) => (action) => {
  62. if (isActionWithMetaSound(action)) {
  63. const sound = action.meta.sound;
  64. if (sound && Object.hasOwn(soundCache, sound)) {
  65. play(soundCache[sound]);
  66. }
  67. }
  68. return next(action);
  69. };
  70. };