diff --git a/client/src/components/AudioIndicator.css b/client/src/components/AudioIndicator.css
index 2a47385..fce3933 100644
--- a/client/src/components/AudioIndicator.css
+++ b/client/src/components/AudioIndicator.css
@@ -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);
}
diff --git a/client/src/components/AudioIndicator.jsx b/client/src/components/AudioIndicator.jsx
index ae5170e..2900a65 100644
--- a/client/src/components/AudioIndicator.jsx
+++ b/client/src/components/AudioIndicator.jsx
@@ -11,17 +11,9 @@ export default function AudioIndicator({ level, isTalking }) {
{isTalking ? 'Votre micro' : 'Audio entrant'}
- {Math.round(normalizedLevel)}%
-
-
- {/* Bars VU-mètre style */}
+ {/* VU-mètre barres */}
{[...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' : ''}`}
/>
);
})}
diff --git a/client/src/hooks/useLiveKit.js b/client/src/hooks/useLiveKit.js
index e5063a0..5c1abcc 100644
--- a/client/src/hooks/useLiveKit.js
+++ b/client/src/hooks/useLiveKit.js
@@ -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 () => {