feat(sounds): self-made Ogg cues + watch/mute/pop fixes
Replace ripped Discord audio with self-authored Ogg Vorbis files (avoids copyright on open-source launch) and fix three cue bugs: - Anti-pop envelope: playSound now applies a 10ms fade-in (+ tail fade-out for one-shots) so buffers no longer start at a non-zero amplitude. Kills the pop on the looping call cues. Mirrors playTestTone. - Deafen no longer plays mute+deafen at once: toggleDeafen flips isMuted and isDeafened atomically, so SoundController saw both transitions. New tested pure helper selectVoiceStateSound() suppresses the side-effect mute cue. - Viewer-side watch feedback: stream_user_joined/left now play locally on the watcher's own machine for explicit Watch/Stop actions, via handleViewerWatchToggle. Direct local playback (not a watchingStreams diff) keeps auto-teardown silent and works identically on Safari and Electron. Docs: docs/systems/sounds.md updated (Ogg, dual-audience cue rows, mute/deafen selection, viewer feedback, anti-pop envelope).
This commit is contained in:
@@ -5,6 +5,8 @@ import { useContextMenuStore, type ContextMenuItem } from '../../stores/contextM
|
||||
import { getActiveRoom, setStreamSubscription } from '../../hooks/useLiveKit';
|
||||
import { stopScreenShare, changeScreenShare } from '../../utils/screenShare';
|
||||
import { encodeStreamWatch } from '../../utils/streamWatchProtocol';
|
||||
import { AudioManager } from '../../audio/AudioManager';
|
||||
import { getSfxVolume } from '../../utils/sfx';
|
||||
import { ScreenShareSettingsPopover } from './ScreenShareSettingsPopover';
|
||||
import { useVoiceParticipantMeta } from '../../hooks/useVoiceParticipantMeta';
|
||||
import type { StreamTile as StreamTileType } from '../../hooks/useLiveKit';
|
||||
@@ -141,7 +143,26 @@ function StreamAttenuationItem() {
|
||||
);
|
||||
}
|
||||
|
||||
function broadcastStreamWatch(streamerUserId: string, watching: boolean): void {
|
||||
/**
|
||||
* Handles a deliberate viewer watch / stop-watch action: plays the local
|
||||
* feedback cue on the viewer's OWN machine and notifies the streamer so their
|
||||
* streamer-side watcher set updates.
|
||||
*
|
||||
* Called only from explicit user-action sites (the "Watch Stream" button and
|
||||
* the watch/stop context-menu items) — never from automatic teardown paths
|
||||
* (streamer stops sharing, participant disconnect), which must stay silent on
|
||||
* the viewer side. The local cue is played directly rather than derived from a
|
||||
* `watchingStreams` diff precisely so those teardown paths don't trigger it.
|
||||
*
|
||||
* The cue plays unconditionally (independent of the data-channel round-trip to
|
||||
* the streamer), so the viewer gets identical feedback on every platform —
|
||||
* Safari and the Electron desktop app alike.
|
||||
*/
|
||||
function handleViewerWatchToggle(streamerUserId: string, watching: boolean): void {
|
||||
AudioManager.getInstance().playSound(
|
||||
watching ? 'stream_user_joined' : 'stream_user_left',
|
||||
{ volume: getSfxVolume() },
|
||||
);
|
||||
const room = getActiveRoom();
|
||||
if (!room) return;
|
||||
const payload = encodeStreamWatch({ type: 'stream_watch', target: streamerUserId, watching });
|
||||
@@ -275,7 +296,7 @@ export function StreamTile({ tile, large }: StreamTileProps) {
|
||||
onClick: () => {
|
||||
useVoiceStore.getState().unwatchStream(userId);
|
||||
setStreamSubscription(getActiveRoom(), identity, false);
|
||||
broadcastStreamWatch(userId, false);
|
||||
handleViewerWatchToggle(userId, false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -289,7 +310,7 @@ export function StreamTile({ tile, large }: StreamTileProps) {
|
||||
onClick: () => {
|
||||
useVoiceStore.getState().watchStream(userId);
|
||||
setStreamSubscription(getActiveRoom(), identity, true);
|
||||
broadcastStreamWatch(userId, true);
|
||||
handleViewerWatchToggle(userId, true);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -333,7 +354,7 @@ export function StreamTile({ tile, large }: StreamTileProps) {
|
||||
const handleWatch = useCallback(() => {
|
||||
useVoiceStore.getState().watchStream(userId);
|
||||
setStreamSubscription(getActiveRoom(), participant.identity, true);
|
||||
broadcastStreamWatch(userId, true);
|
||||
handleViewerWatchToggle(userId, true);
|
||||
}, [userId, participant.identity]);
|
||||
|
||||
const hasVideo = liveScreenTrack !== null;
|
||||
|
||||
Reference in New Issue
Block a user