mirror of
https://github.com/R0m1k3/LiveFlow.git
synced 2026-10-22 22:48:14 +02:00
iPhone : verrou d'écran (anti-veille), rafraîchissement de la liste, diarisation rééquilibrée
- Wake Lock pendant l'enregistrement : empêche le téléphone de se mettre en veille (qui suspendait micro/WebSocket) ; repris au retour au premier plan - La liste des réunions se rafraîchit au retour sur l'onglet/fenêtre et toutes les 20 s : une réunion faite sur l'iPhone apparaît sur le PC sans recharger - Diarisation : seuil par défaut rééquilibré (0.40) — avait été rendu trop conservateur, ne séparait plus deux personnes ; règle NEW_SPEAKER assouplie https://claude.ai/code/session_01YHMp3EKzr4s6o8w1ygxuUe
This commit is contained in:
3 files changed
+31
-4
No files matched your search
+3
-3
@@ -114,14 +114,14 @@ class SpeakerDiarizer:
|
|||||||
# sous le seuil (zone morte) ET que le segment est assez long pour donner
|
# sous le seuil (zone morte) ET que le segment est assez long pour donner
|
||||||
# une empreinte fiable. Sinon le segment est rattaché au meilleur profil.
|
# une empreinte fiable. Sinon le segment est rattaché au meilleur profil.
|
||||||
# Cela évite qu'un même locuteur soit éclaté en Locuteur 3, 4, 5...
|
# Cela évite qu'un même locuteur soit éclaté en Locuteur 3, 4, 5...
|
||||||
NEW_SPEAKER_MARGIN = 0.20 # écart sous le seuil pour autoriser un nouveau
|
NEW_SPEAKER_MARGIN = 0.05 # écart sous le seuil pour autoriser un nouveau
|
||||||
NEW_SPEAKER_MIN_S = 1.2 # durée mini d'un segment pour créer un locuteur
|
NEW_SPEAKER_MIN_S = 1.0 # durée mini d'un segment pour créer un locuteur
|
||||||
STICKY_MARGIN = 0.05 # adhérence au dernier locuteur si scores proches
|
STICKY_MARGIN = 0.05 # adhérence au dernier locuteur si scores proches
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
model: EmbeddingModel,
|
model: EmbeddingModel,
|
||||||
threshold: float = 0.35,
|
threshold: float = 0.40,
|
||||||
max_speakers: int = 8,
|
max_speakers: int = 8,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._model = model
|
self._model = model
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ def clean_transcript(text: str) -> str:
|
|||||||
|
|
||||||
# Diarisation (identification des locuteurs) : embeddings ECAPA-TDNN sur CPU
|
# Diarisation (identification des locuteurs) : embeddings ECAPA-TDNN sur CPU
|
||||||
DIARIZATION = os.environ.get("DIARIZATION", "off").strip().lower() == "on"
|
DIARIZATION = os.environ.get("DIARIZATION", "off").strip().lower() == "on"
|
||||||
DIARIZATION_THRESHOLD = float(os.environ.get("DIARIZATION_THRESHOLD", "0.35"))
|
DIARIZATION_THRESHOLD = float(os.environ.get("DIARIZATION_THRESHOLD", "0.40"))
|
||||||
DIARIZATION_MAX_SPEAKERS = int(os.environ.get("DIARIZATION_MAX_SPEAKERS", "8"))
|
DIARIZATION_MAX_SPEAKERS = int(os.environ.get("DIARIZATION_MAX_SPEAKERS", "8"))
|
||||||
|
|
||||||
LIVEFLOW_USER = os.environ.get("LIVEFLOW_USER", "admin")
|
LIVEFLOW_USER = os.environ.get("LIVEFLOW_USER", "admin")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const state = {
|
|||||||
silentWarned: false,
|
silentWarned: false,
|
||||||
sourceNode: null,
|
sourceNode: null,
|
||||||
paused: false,
|
paused: false,
|
||||||
|
wakeLock: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// couleurs des étiquettes de locuteurs (classes .speaker-0 à .speaker-7)
|
// couleurs des étiquettes de locuteurs (classes .speaker-0 à .speaker-7)
|
||||||
@@ -137,6 +138,7 @@ async function startRecording() {
|
|||||||
state.heardSound = false;
|
state.heardSound = false;
|
||||||
state.silentWarned = false;
|
state.silentWarned = false;
|
||||||
state.timerInterval = setInterval(updateTimer, 500);
|
state.timerInterval = setInterval(updateTimer, 500);
|
||||||
|
acquireWakeLock(); // garde l'écran allumé (téléphone)
|
||||||
$('record-btn').textContent = '■ Arrêter';
|
$('record-btn').textContent = '■ Arrêter';
|
||||||
$('record-btn').classList.add('recording');
|
$('record-btn').classList.add('recording');
|
||||||
$('pause-btn').classList.remove('hidden', 'paused');
|
$('pause-btn').classList.remove('hidden', 'paused');
|
||||||
@@ -186,6 +188,7 @@ function flushPcm() {
|
|||||||
function stopRecording(abrupt = false) {
|
function stopRecording(abrupt = false) {
|
||||||
state.recording = false;
|
state.recording = false;
|
||||||
clearInterval(state.timerInterval);
|
clearInterval(state.timerInterval);
|
||||||
|
releaseWakeLock();
|
||||||
if (state.sourceNode) state.sourceNode.disconnect();
|
if (state.sourceNode) state.sourceNode.disconnect();
|
||||||
if (state.workletNode) state.workletNode.disconnect();
|
if (state.workletNode) state.workletNode.disconnect();
|
||||||
if (state.mediaStream) state.mediaStream.getTracks().forEach((t) => t.stop());
|
if (state.mediaStream) state.mediaStream.getTracks().forEach((t) => t.stop());
|
||||||
@@ -366,6 +369,30 @@ async function copyTranscript() {
|
|||||||
|
|
||||||
// --------------------------------------------------------------------- init
|
// --------------------------------------------------------------------- init
|
||||||
|
|
||||||
|
// Verrou d'écran : empêche le téléphone de se mettre en veille pendant
|
||||||
|
// l'enregistrement (sinon micro/WebSocket suspendus). iOS 16.4+, Android.
|
||||||
|
async function acquireWakeLock() {
|
||||||
|
try {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
state.wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
}
|
||||||
|
} catch (e) { /* refusé / non supporté : tant pis */ }
|
||||||
|
}
|
||||||
|
function releaseWakeLock() {
|
||||||
|
try { state.wakeLock && state.wakeLock.release(); } catch (e) {}
|
||||||
|
state.wakeLock = null;
|
||||||
|
}
|
||||||
|
// iOS relâche le verrou quand l'onglet repasse au premier plan : on le reprend
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
if (state.recording) acquireWakeLock();
|
||||||
|
else loadMeetings(); // rafraîchit la liste en revenant sur l'onglet
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('focus', () => { if (!state.recording) loadMeetings(); });
|
||||||
|
// rafraîchit régulièrement la liste (réunions faites depuis un autre appareil)
|
||||||
|
setInterval(() => { if (!state.recording) loadMeetings(); }, 20000);
|
||||||
|
|
||||||
$('record-btn').onclick = () => (state.recording ? stopRecording() : startRecording());
|
$('record-btn').onclick = () => (state.recording ? stopRecording() : startRecording());
|
||||||
$('pause-btn').onclick = togglePause;
|
$('pause-btn').onclick = togglePause;
|
||||||
$('logout-btn').onclick = async () => { await fetch('/api/logout', { method: 'POST' }); location.href = '/login'; };
|
$('logout-btn').onclick = async () => { await fetch('/api/logout', { method: 'POST' }); location.href = '/login'; };
|
||||||
|
|||||||
Reference in new issue
Block a user