Files
upstream-rocketchat/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

146 lines
5.2 KiB
TypeScript

import type { ICustomSound } from '@rocket.chat/core-typings';
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { CustomSoundContext, useEndpoint, 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 { useUserSoundPreferences } from '../../hooks/useUserSoundPreferences';
export type CustomSoundProviderProps = {
children?: ReactNode;
};
const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => {
const audioRefs = useRef<HTMLAudioElement[]>([]);
const queryClient = useQueryClient();
const streamAll = useStream('notify-all');
const getCustomSounds = useEndpoint('GET', '/v1/custom-sounds.list');
const newRoomNotification = useUserPreference<string>('newRoomNotification') || 'door';
const newMessageNotification = useUserPreference<string>('newMessageNotification') || 'chime';
const { notificationsSoundVolume, voipRingerVolume } = useUserSoundPreferences();
const { data: list } = useQuery({
queryFn: async (): Promise<Omit<ICustomSound, '_updatedAt'>[]> => {
// `/v1/custom-sounds.list` is paginated, so we page through all results to
// load every custom sound into the provider (the legacy `listCustomSounds`
// method returned them all at once).
const sounds: Awaited<ReturnType<typeof getCustomSounds>>['sounds'] = [];
let total = Infinity;
while (sounds.length < total) {
const page = await getCustomSounds({ count: 100, offset: sounds.length });
total = page.total;
sounds.push(...page.sounds);
if (!page.sounds.length) {
break;
}
}
if (!sounds.length) {
return defaultSounds;
}
return [...sounds.map(({ _updatedAt: _, ...sound }) => ({ ...sound, src: getCustomSoundURL(sound) })), ...defaultSounds];
},
queryKey: ['listCustomSounds'],
initialData: defaultSounds,
});
const play = useStableCallback((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 = useStableCallback((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 = useStableCallback((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 <CustomSoundContext.Provider value={contextValue}>{children}</CustomSoundContext.Provider>;
};
export default CustomSoundProvider;