import type { ICustomSound } from '@rocket.chat/core-typings'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { CustomSoundContext, useStream, useUserPreference } from '@rocket.chat/ui-contexts'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect, useMemo, useRef, type ReactNode } from 'react'; import { defaultSounds, getCustomSoundURL, formatVolume } from './lib'; import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { useUserSoundPreferences } from '../../hooks/useUserSoundPreferences'; type CustomSoundProviderProps = { children?: ReactNode; }; const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => { const audioRefs = useRef([]); const queryClient = useQueryClient(); const streamAll = useStream('notify-all'); const newRoomNotification = useUserPreference('newRoomNotification') || 'door'; const newMessageNotification = useUserPreference('newMessageNotification') || 'chime'; const { notificationsSoundVolume, voipRingerVolume } = useUserSoundPreferences(); const { data: list } = useQuery({ queryFn: async () => { const customSoundsList = await sdk.call('listCustomSounds'); if (!customSoundsList.length) { return defaultSounds; } return [...customSoundsList.map((sound) => ({ ...sound, src: getCustomSoundURL(sound) })), ...defaultSounds]; }, queryKey: ['listCustomSounds'], initialData: defaultSounds, }); const play = useEffectEvent((soundId: ICustomSound['_id'], { volume = 1, loop = false } = {}) => { stop(soundId); const item = list?.find(({ _id }) => _id === soundId); if (!item?.src) { console.error('Unable to play sound', soundId); return; } const audio = new Audio(item.src); audio.volume = volume; audio.loop = loop; audio.id = soundId; audio.play(); audioRefs.current = [...audioRefs.current, audio]; return () => { stop(soundId); }; }); const pause = useEffectEvent((soundId: ICustomSound['_id']) => { const current = audioRefs.current?.find(({ id }) => id === soundId); if (current) { current.pause(); audioRefs.current = audioRefs.current.filter(({ id }) => id !== soundId); } }); const stop = useEffectEvent((soundId: ICustomSound['_id']) => { const current = audioRefs.current?.find(({ id }) => id === soundId); if (current) { current.load(); audioRefs.current = audioRefs.current.filter(({ id }) => id !== soundId); } }); const contextValue = useMemo(() => { const notificationSounds = { playNewRoom: () => play(newRoomNotification, { loop: false, volume: formatVolume(notificationsSoundVolume) }), playNewRoomLoop: () => play(newRoomNotification, { loop: true, volume: formatVolume(notificationsSoundVolume) }), playNewMessage: () => play(newMessageNotification, { loop: false, volume: formatVolume(notificationsSoundVolume) }), playNewMessageCustom: (soundId: ICustomSound['_id']) => play(soundId, { loop: false, volume: formatVolume(notificationsSoundVolume) }), stopNewRoom: () => stop(newRoomNotification), stopNewMessage: () => stop(newMessageNotification), }; const voipSounds = { playRinger: () => play('telephone', { loop: true, volume: formatVolume(voipRingerVolume) }), playDialer: () => play('outbound-call-ringing', { loop: true, volume: formatVolume(voipRingerVolume) }), playCallEnded: () => play('call-ended', { loop: false, volume: formatVolume(voipRingerVolume) }), stopRinger: () => stop('telephone'), stopDialer: () => stop('outbound-call-ringing'), stopCallEnded: () => stop('call-ended'), stopAll: () => { stop('telephone'); stop('outbound-call-ringing'); stop('call-ended'); }, }; const callSounds = { playRinger: () => play('ringtone', { loop: true, volume: formatVolume(voipRingerVolume) }), playDialer: () => play('dialtone', { loop: true, volume: formatVolume(voipRingerVolume) }), stopRinger: () => stop('ringtone'), stopDialer: () => stop('dialtone'), }; return { list, notificationSounds, callSounds, voipSounds, play, pause, stop, }; }, [list, newMessageNotification, newRoomNotification, notificationsSoundVolume, pause, play, stop, voipRingerVolume]); useEffect(() => { return streamAll('public-info', ([key]) => { switch (key) { case 'updateCustomSound': queryClient.invalidateQueries({ queryKey: ['listCustomSounds'] }); break; case 'deleteCustomSound': queryClient.invalidateQueries({ queryKey: ['listCustomSounds'] }); break; } }); }, [queryClient, streamAll]); return ; }; export default CustomSoundProvider;