feat: VU-mètre audio entrant fonctionnel + simplification UI
Modifications : - Ajout analyseur audio pour pistes distantes (remoteAnalyserRef) - setupRemoteAudioAnalyser() appelé sur TrackSubscribed - analyseAudioLevel() alterne automatiquement entre micro local et audio entrant - useEffect redémarre analyse quand isTalking change - Cleanup complet des 2 contextes audio (local + remote) UI VU-mètre : - Suppression jauge redondante (gardé uniquement barres) - Barres uniformes (même hauteur) au lieu d'effet égaliseur - Couleurs distinctes : vert (audio entrant) vs bleu/rouge (micro) - Jaune > 75%, rouge clignotant > 90% Tests validés : ✅ VU-mètre micro local : fonctionne ✅ VU-mètre audio entrant : fonctionne (fix principal) ✅ Alternance automatique talking/listening 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,18 +56,20 @@
|
||||
|
||||
.audio-bar {
|
||||
flex: 1;
|
||||
background: var(--color-bg);
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
transition: all 0.1s ease;
|
||||
min-height: 4px;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.audio-bar.active {
|
||||
height: 100%;
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.audio-bar.active.talking {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.audio-bar.active.warning {
|
||||
background: var(--color-warning);
|
||||
}
|
||||
|
||||
@@ -11,17 +11,9 @@ export default function AudioIndicator({ level, isTalking }) {
|
||||
<div className="audio-indicator-container">
|
||||
<div className="audio-indicator-label">
|
||||
<span>{isTalking ? 'Votre micro' : 'Audio entrant'}</span>
|
||||
<span className="audio-level-value">{Math.round(normalizedLevel)}%</span>
|
||||
</div>
|
||||
|
||||
<div className="audio-indicator-bar">
|
||||
<div
|
||||
className={`audio-indicator-fill ${isTalking ? 'talking' : ''}`}
|
||||
style={{ width: `${normalizedLevel}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bars VU-mètre style */}
|
||||
{/* VU-mètre barres */}
|
||||
<div className="audio-bars">
|
||||
{[...Array(20)].map((_, i) => {
|
||||
const threshold = (i + 1) * 5;
|
||||
@@ -34,7 +26,7 @@ export default function AudioIndicator({ level, isTalking }) {
|
||||
key={i}
|
||||
className={`audio-bar ${isActive ? 'active' : ''} ${
|
||||
isActive && isDanger ? 'danger' : isActive && isWarning ? 'warning' : ''
|
||||
}`}
|
||||
} ${isTalking ? 'talking' : ''}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -17,6 +17,11 @@ export default function useLiveKit() {
|
||||
const animationFrameRef = useRef(null);
|
||||
const isAudioUnlockedRef = useRef(false);
|
||||
|
||||
// Analyseur audio pour pistes distantes (audio entrant)
|
||||
const remoteAudioContextRef = useRef(null);
|
||||
const remoteAnalyserRef = useRef(null);
|
||||
const remoteAnimationFrameRef = useRef(null);
|
||||
|
||||
/**
|
||||
* Connexion à la room LiveKit
|
||||
*/
|
||||
@@ -68,6 +73,9 @@ export default function useLiveKit() {
|
||||
if (track.kind === Track.Kind.Audio) {
|
||||
const audioElement = track.attach();
|
||||
document.body.appendChild(audioElement);
|
||||
|
||||
// Setup analyseur pour audio entrant
|
||||
setupRemoteAudioAnalyser(track);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,6 +100,8 @@ export default function useLiveKit() {
|
||||
// Mute par défaut (PTT)
|
||||
track.mute();
|
||||
setupAudioAnalyser(track);
|
||||
// Démarrer l'analyse audio
|
||||
analyseAudioLevel();
|
||||
console.log('✓ Track audio configuré et muted pour PTT');
|
||||
}
|
||||
});
|
||||
@@ -249,7 +259,7 @@ export default function useLiveKit() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup analyseur audio pour VU-mètre
|
||||
* Setup analyseur audio pour VU-mètre (micro local)
|
||||
*/
|
||||
const setupAudioAnalyser = (track) => {
|
||||
try {
|
||||
@@ -266,36 +276,63 @@ export default function useLiveKit() {
|
||||
audioContextRef.current = audioContext;
|
||||
analyserRef.current = analyser;
|
||||
|
||||
// Démarrer analyse
|
||||
analyseAudioLevel();
|
||||
console.log('✓ Analyseur audio local configuré');
|
||||
} catch (error) {
|
||||
console.error('Erreur setup analyser:', error);
|
||||
console.error('Erreur setup analyser local:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup analyseur audio pour pistes distantes (audio entrant)
|
||||
*/
|
||||
const setupRemoteAudioAnalyser = (track) => {
|
||||
try {
|
||||
const mediaStream = track.mediaStream;
|
||||
if (!mediaStream) return;
|
||||
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const analyser = audioContext.createAnalyser();
|
||||
const source = audioContext.createMediaStreamSource(mediaStream);
|
||||
|
||||
analyser.fftSize = 256;
|
||||
source.connect(analyser);
|
||||
|
||||
remoteAudioContextRef.current = audioContext;
|
||||
remoteAnalyserRef.current = analyser;
|
||||
|
||||
console.log('✓ Analyseur audio distant configuré');
|
||||
} catch (error) {
|
||||
console.error('Erreur setup analyser distant:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyser niveau audio (pour VU-mètre)
|
||||
* Alterne entre micro local (si talking) et audio entrant (si listening)
|
||||
*/
|
||||
const analyseAudioLevel = () => {
|
||||
if (!analyserRef.current) return;
|
||||
|
||||
const analyser = analyserRef.current;
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
|
||||
const analyseAudioLevel = useCallback(() => {
|
||||
const analyse = () => {
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
// Choisir l'analyseur selon l'état
|
||||
const analyser = isTalking ? analyserRef.current : remoteAnalyserRef.current;
|
||||
|
||||
// Calculer moyenne
|
||||
const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
|
||||
const normalized = Math.min(100, (average / 255) * 100);
|
||||
if (analyser) {
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
|
||||
setAudioLevel(normalized);
|
||||
// Calculer moyenne
|
||||
const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
|
||||
const normalized = Math.min(100, (average / 255) * 100);
|
||||
|
||||
setAudioLevel(normalized);
|
||||
} else {
|
||||
setAudioLevel(0);
|
||||
}
|
||||
|
||||
animationFrameRef.current = requestAnimationFrame(analyse);
|
||||
};
|
||||
|
||||
analyse();
|
||||
};
|
||||
}, [isTalking]);
|
||||
|
||||
/**
|
||||
* Cleanup
|
||||
@@ -306,15 +343,39 @@ export default function useLiveKit() {
|
||||
animationFrameRef.current = null;
|
||||
}
|
||||
|
||||
if (remoteAnimationFrameRef.current) {
|
||||
cancelAnimationFrame(remoteAnimationFrameRef.current);
|
||||
remoteAnimationFrameRef.current = null;
|
||||
}
|
||||
|
||||
if (audioContextRef.current) {
|
||||
audioContextRef.current.close();
|
||||
audioContextRef.current = null;
|
||||
}
|
||||
|
||||
if (remoteAudioContextRef.current) {
|
||||
remoteAudioContextRef.current.close();
|
||||
remoteAudioContextRef.current = null;
|
||||
}
|
||||
|
||||
analyserRef.current = null;
|
||||
remoteAnalyserRef.current = null;
|
||||
localTrackRef.current = null;
|
||||
};
|
||||
|
||||
// Redémarrer l'analyse audio quand isTalking change
|
||||
useEffect(() => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = null;
|
||||
}
|
||||
|
||||
// Redémarrer l'analyse si on a au moins un analyseur
|
||||
if (analyserRef.current || remoteAnalyserRef.current) {
|
||||
analyseAudioLevel();
|
||||
}
|
||||
}, [isTalking, analyseAudioLevel]);
|
||||
|
||||
// Cleanup au démontage
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user