mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-24 19:19:21 +00:00
Port reactionaudiorenderer to useAudioContext
This commit is contained in:
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useEffect } from "react";
|
import { ReactNode, useEffect } from "react";
|
||||||
import { debounce, filter, interval, skip, throttle } from "rxjs";
|
import { debounce, filter, interval, throttle } from "rxjs";
|
||||||
import { CallViewModel } from "../state/CallViewModel";
|
import { CallViewModel } from "../state/CallViewModel";
|
||||||
import joinCallSoundMp3 from "../sound/join_call.mp3";
|
import joinCallSoundMp3 from "../sound/join_call.mp3";
|
||||||
import joinCallSoundOgg from "../sound/join_call.ogg";
|
import joinCallSoundOgg from "../sound/join_call.ogg";
|
||||||
|
|||||||
@@ -5,70 +5,56 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useEffect, useRef } from "react";
|
import { ReactNode, useDeferredValue, useEffect } from "react";
|
||||||
|
|
||||||
import { useReactions } from "../useReactions";
|
import { useReactions } from "../useReactions";
|
||||||
import {
|
import { playReactionsSound, useSetting } from "../settings/settings";
|
||||||
playReactionsSound,
|
import { ReactionSet } from "../reactions";
|
||||||
soundEffectVolumeSetting as effectSoundVolumeSetting,
|
import { prefetchSounds, useAudioContext } from "../useAudioContext";
|
||||||
useSetting,
|
|
||||||
} from "../settings/settings";
|
const SoundMap = Object.fromEntries(
|
||||||
import { GenericReaction, ReactionSet } from "../reactions";
|
ReactionSet.filter((v) => v.sound !== undefined).map((v) => [
|
||||||
|
v.name,
|
||||||
|
v.sound!,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const Sounds = prefetchSounds(SoundMap);
|
||||||
|
|
||||||
export function ReactionsAudioRenderer(): ReactNode {
|
export function ReactionsAudioRenderer(): ReactNode {
|
||||||
const { reactions } = useReactions();
|
const { reactions } = useReactions();
|
||||||
const [shouldPlay] = useSetting(playReactionsSound);
|
const [shouldPlay] = useSetting(playReactionsSound);
|
||||||
const [effectSoundVolume] = useSetting(effectSoundVolumeSetting);
|
const audioEngineCtx = useAudioContext({
|
||||||
const audioElements = useRef<Record<string, HTMLAudioElement | null>>({});
|
sounds: Sounds,
|
||||||
|
latencyHint: "interactive",
|
||||||
|
});
|
||||||
|
const oldReactions = useDeferredValue(reactions);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!audioElements.current) {
|
if (!audioEngineCtx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldPlay) {
|
if (!shouldPlay) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const oldReactionSet = new Set(
|
||||||
|
Object.values(oldReactions).map((r) => r.name),
|
||||||
|
);
|
||||||
for (const reactionName of new Set(
|
for (const reactionName of new Set(
|
||||||
Object.values(reactions).map((r) => r.name),
|
Object.values(reactions).map((r) => r.name),
|
||||||
)) {
|
)) {
|
||||||
const audioElement =
|
if (oldReactionSet.has(reactionName)) {
|
||||||
audioElements.current[reactionName] ?? audioElements.current.generic;
|
// Don't replay old reactions
|
||||||
if (audioElement?.paused) {
|
return;
|
||||||
audioElement.volume = effectSoundVolume;
|
}
|
||||||
void audioElement.play();
|
if (SoundMap[reactionName]) {
|
||||||
|
audioEngineCtx.playSound(reactionName);
|
||||||
|
} else {
|
||||||
|
// Fallback sounds.
|
||||||
|
audioEngineCtx.playSound("generic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [audioElements, shouldPlay, reactions, effectSoundVolume]);
|
}, [shouldPlay, oldReactions, reactions]);
|
||||||
|
return <></>;
|
||||||
// Do not render any audio elements if playback is disabled. Will save
|
|
||||||
// audio file fetches.
|
|
||||||
if (!shouldPlay) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We load all audio elements ahead of time to allow the cache
|
|
||||||
// to be populated, rather than risk a cache miss and have the audio
|
|
||||||
// be delayed.
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{[GenericReaction, ...ReactionSet].map(
|
|
||||||
(r) =>
|
|
||||||
r.sound && (
|
|
||||||
<audio
|
|
||||||
ref={(el) => (audioElements.current[r.name] = el)}
|
|
||||||
data-testid={r.name}
|
|
||||||
key={r.name}
|
|
||||||
preload="auto"
|
|
||||||
hidden
|
|
||||||
>
|
|
||||||
<source src={r.sound.ogg} type="audio/ogg; codecs=vorbis" />
|
|
||||||
{r.sound.mp3 ? (
|
|
||||||
<source src={r.sound.mp3} type="audio/mpeg" />
|
|
||||||
) : null}
|
|
||||||
</audio>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from "./settings/settings";
|
} from "./settings/settings";
|
||||||
import { useMediaDevices } from "./livekit/MediaDevicesContext";
|
import { useMediaDevices } from "./livekit/MediaDevicesContext";
|
||||||
|
|
||||||
type SoundDefinition = { mp3: string; ogg: string };
|
type SoundDefinition = { mp3?: string; ogg: string };
|
||||||
|
|
||||||
async function fetchBuffer(filename: string) {
|
async function fetchBuffer(filename: string) {
|
||||||
// Load an audio file
|
// Load an audio file
|
||||||
@@ -64,7 +64,10 @@ export async function prefetchSounds<S extends string>(
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(sounds).map(async ([name, file]) => {
|
Object.entries(sounds).map(async ([name, file]) => {
|
||||||
const { mp3, ogg } = file as SoundDefinition;
|
const { mp3, ogg } = file as SoundDefinition;
|
||||||
buffers[name] = await fetchBuffer(PreferredFormat === "ogg" ? ogg : mp3);
|
// Use preferred format, fallback to ogg if no mp3 is provided.
|
||||||
|
buffers[name] = await fetchBuffer(
|
||||||
|
PreferredFormat === "ogg" ? ogg : (mp3 ?? ogg),
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return buffers as Record<S, ArrayBuffer>;
|
return buffers as Record<S, ArrayBuffer>;
|
||||||
|
|||||||
Reference in New Issue
Block a user