testando medida de latencia
Deploy / Deploy (push) Has been cancelled Details

This commit is contained in:
JotaChina 2026-02-24 18:37:46 -03:00
parent 43a66c4f53
commit 477b693b79
4 changed files with 239 additions and 4634543 deletions

4634497
_data/all.yml

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ import { renderAudioEditor } from "./audio/audio_ui.js";
import { adjustValue, enforceNumericInput, DEFAULT_PROJECT_XML, secondsToSongTicks, snapSongTicks, LMMS_TICKS_PER_BAR } from "./utils.js"; import { adjustValue, enforceNumericInput, DEFAULT_PROJECT_XML, secondsToSongTicks, snapSongTicks, LMMS_TICKS_PER_BAR } from "./utils.js";
import { ZOOM_LEVELS } from "./config.js"; import { ZOOM_LEVELS } from "./config.js";
import { loadProjectFromServer } from "./file.js"; import { loadProjectFromServer } from "./file.js";
import { sendAction, joinRoom, setUserName } from "./socket.js"; import { sendAction, joinRoom, setUserName, getOrFetchUsername } from "./socket.js";
import { renderActivePatternToBlob, renderProjectAndDownload } from "./pattern/pattern_audio.js"; import { renderActivePatternToBlob, renderProjectAndDownload } from "./pattern/pattern_audio.js";
import { showToast } from "./ui.js"; import { showToast } from "./ui.js";
import { toggleRecording } from "./recording.js" import { toggleRecording } from "./recording.js"
@ -696,6 +696,14 @@ document.addEventListener("DOMContentLoaded", () => {
const defaultName = `sessao-${Math.random() const defaultName = `sessao-${Math.random()
.toString(36) .toString(36)
.substring(2, 7)}`; .substring(2, 7)}`;
// 🔥 O PEDÁGIO: Pede o login ANTES de avisar o servidor!
const myName = await getOrFetchUsername();
// Agora sim envia com os dados completos
socket.emit("join_room", {
roomName: novaSala,
userName: myName
});
const roomName = prompt( const roomName = prompt(
"Digite um nome para a sala compartilhada:", "Digite um nome para a sala compartilhada:",
defaultName defaultName

View File

@ -12,6 +12,8 @@ const pino = require("pino");
const os = require("os"); const os = require("os");
const crypto = require("crypto"); const crypto = require("crypto");
const { spawn } = require("child_process"); const { spawn } = require("child_process");
// --- RASTREAMENTO DE USUÁRIOS ---
const activeUsers = {}; // Mapeia socket.id -> { username, room }
//import { LOG_SERVER } from "../utils.js" //import { LOG_SERVER } from "../utils.js"
const LOG_SERVER = `/var/www/html/trens/src_mmpSearch/logs/creation_logs/server` const LOG_SERVER = `/var/www/html/trens/src_mmpSearch/logs/creation_logs/server`
@ -422,50 +424,77 @@ function applyAuthoritativeAction(roomName, action) {
io.on("connection", (socket) => { io.on("connection", (socket) => {
console.log(`Novo usuário conectado: ${socket.id}`); console.log(`Novo usuário conectado: ${socket.id}`);
// ======================================= // =========================================================
// JOIN ROOM // ENTRADA, IDENTIDADE E SINCRONIZAÇÃO DE SALA
// ======================================= // =========================================================
socket.on("join_room", async (data) => { socket.on("join_room", async (data) => {
const { roomName, userName } = data || {}; const { roomName, userName } = data || {};
if (!roomName) { if (!roomName) {
console.warn(`join_room inválido de ${socket.id} (sem roomName)`); console.warn(`join_room inválido de ${socket.id}`);
return; return;
} }
// Registra a identidade
const finalUserName = userName || "(sem nome)";
activeUsers[socket.id] = { username: finalUserName, room: roomName };
await socket.join(roomName); await socket.join(roomName);
console.log( console.log(`[ROOM] 🟢 Usuário ${finalUserName} (${socket.id}) entrou na sala: ${roomName}`);
`Usuário ${userName || "(sem nome)"} (${
socket.id // Avisa os outros para mostrar o Popup Verde
}) entrou na sala: ${roomName}` socket.to(roomName).emit("user_joined", { username: finalUserName, id: socket.id });
);
// Atualiza o monitor de console
const clientsInRoom = Array.from(io.sockets.adapter.rooms.get(roomName) || []);
io.to(roomName).emit("room_status", {
room: roomName, count: clientsInRoom.length, users: clientsInRoom
});
logMyRooms(socket, "JOIN"); logMyRooms(socket, "JOIN");
// envia estado salvo do projeto (XML) — compat V4
const room = ensureRoom(roomName); const room = ensureRoom(roomName);
if (room.projectXml) { if (room.projectXml) {
socket.emit("load_project_state", room.projectXml); socket.emit("load_project_state", room.projectXml);
console.log(
`Estado XML enviado para ${userName || socket.id} na sala ${roomName}`
);
} else {
console.log(`Sala ${roomName} sem XML salvo ainda.`);
} }
// envia snapshot autoritativo do editor de áudio
socket.emit("action_broadcast", { socket.emit("action_broadcast", {
action: { action: { type: "AUDIO_SNAPSHOT", snapshot: room.audio, __seq: room.seq, __target: socket.id },
type: "AUDIO_SNAPSHOT",
snapshot: room.audio,
__seq: room.seq,
__target: socket.id,
},
}); });
socket.to(roomName).emit("feedback", `Usuário ${finalUserName} entrou na sala.`);
socket
.to(roomName)
.emit("feedback", `Usuário ${userName || socket.id} entrou na sala.`);
}); });
// --- GATILHO DE DESPERTAR ÚNICO ---
socket.on("request_full_sync", ({ room }) => {
const targetRoom = ensureRoom(room);
if (targetRoom && targetRoom.projectXml) {
console.log(`[SYNC] Cliente ${socket.id} acordou e pediu Full Sync da sala ${room}`);
socket.emit("load_project_state", targetRoom.projectXml);
}
});
// =========================================================
// SAÍDA E LIMPEZA UNIFICADA (Aviso e Desconexão)
// =========================================================
socket.on("disconnecting", () => {
const user = activeUsers[socket.id];
if (user) {
console.log(`[ROOM] 🔴 ${user.username} saiu da sala ${user.room}`);
socket.to(user.room).emit("user_left", { username: user.username });
}
});
socket.on("disconnect", () => {
delete activeUsers[socket.id]; // Limpa a identidade
console.log(`Cliente desconectado: ${socket.id}`);
// Remove locks de renderização
for (const [roomName, lockId] of renderLocks.entries()) {
if (lockId === socket.id) {
renderLocks.delete(roomName);
console.log(`Lock de render da sala ${roomName} liberado.`);
}
}
});
// ======================================= // =======================================
// CLOCK SYNC // CLOCK SYNC
// ======================================= // =======================================
@ -627,6 +656,7 @@ io.on("connection", (socket) => {
}); });
socket.on("disconnect", () => { socket.on("disconnect", () => {
delete activeUsers[socket.id]; // Limpa a identidade da memória do servidor
console.log(`Usuário desconectado: ${socket.id}`); console.log(`Usuário desconectado: ${socket.id}`);
}); });
}); });
@ -745,6 +775,29 @@ function run(cmd, args, { timeoutMs, cwd } = {}) {
}); });
} }
// --- ENDPOINT DE MONITORAMENTO DE SALAS (ADMIN) ---
app.get("/api/admin/salas", (req, res) => {
const roomsData = {};
// Itera por todas as salas ativas na memória do Socket.IO
io.sockets.adapter.rooms.forEach((value, key) => {
// O Socket.IO cria uma "sala" automática para cada usuário.
// Vamos filtrar para mostrar apenas as salas reais do seu sistema.
if (!io.sockets.sockets.has(key)) {
roomsData[key] = {
total_usuarios: value.size,
ids_conectados: Array.from(value)
};
}
});
res.json({
salas_ativas: Object.keys(roomsData).length,
detalhes: roomsData
});
});
// --------------------------------------------------
// trava simples pra não renderizar a mesma sala em paralelo // trava simples pra não renderizar a mesma sala em paralelo
const renderLocks = new Map(); const renderLocks = new Map();

View File

@ -50,6 +50,42 @@ import { updateStepUI, renderPatternEditor } from "./pattern/pattern_ui.js";
import { PORT_SOCK } from "./config.js"; import { PORT_SOCK } from "./config.js";
import { DEFAULT_PROJECT_XML } from "./utils.js" import { DEFAULT_PROJECT_XML } from "./utils.js"
// --- NO TOPO DO SEU socket.js ---
export let USER_NAME = null;
export let currentRoom = null;
let roomSeq = 0;
let lastActionToken = 0;
let isOfflineMode = false;
// --- FUNÇÃO DE AUTENTICAÇÃO INTELIGENTE ---
export async function getOrFetchUsername() {
// Se já pegamos o nome antes, não pede de novo
if (USER_NAME) return USER_NAME;
try {
const res = await fetch('/api/check_auth', { credentials: 'include' });
const data = await res.json();
if (data.logged_in) {
USER_NAME = data.user;
return USER_NAME;
}
} catch (e) {
console.warn("API de Auth indisponível, caindo para modo visitante.");
}
// Se não estiver logado, barra a tela e pede apelido
let name = prompt("Você não está logado. Digite seu apelido para entrar na DAW Online:");
if (!name || name.trim() === "") {
name = "Produtor_" + Math.floor(Math.random() * 1000);
}
USER_NAME = name.trim();
return USER_NAME;
}
// --- TELEMETRIA PARA A DISSERTAÇÃO ---
window.jamTelemetry = [];
let lastReceivedSeq = 0;
function _getRackIdFallback() { function _getRackIdFallback() {
// 1) pega do primeiro bassline que já tenha instrumentSourceId // 1) pega do primeiro bassline que já tenha instrumentSourceId
const b = (appState.pattern.tracks || []).find( const b = (appState.pattern.tracks || []).find(
@ -101,8 +137,7 @@ const socket = io(`https://alice.ufsj.edu.br:${PORT_SOCK}`, {
withCredentials: true, withCredentials: true,
}); });
let USER_NAME = `Alicer-${Math.floor(Math.random() * 9999)}`; // -------------------------------------
let currentRoom = null;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// CLOCK SYNC — Sincroniza relógio cliente-servidor // CLOCK SYNC — Sincroniza relógio cliente-servidor
@ -177,27 +212,45 @@ export function sendActionSafe(action) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// CONEXÃO / JOIN / LOGS // CONEXÃO / JOIN / LOGS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
socket.on("connect", () => {
console.log(`Conectado ao servidor com ID: ${socket.id}`);
showToast("✅ Conectado ao servidor", "success");
if (USER_NAME.startsWith("Alicer-")) {
USER_NAME = `Alicer-${socket.id.substring(0, 4)}`;
}
const urlRoom = new URLSearchParams(window.location.search).get("room");
currentRoom = urlRoom || null;
if (currentRoom) { // --- MONITORAMENTO DE PRESENÇA ---
console.log(`Modo Online. Sala detectada: ${currentRoom}`); socket.on("room_status", (data) => {
console.log(
`%c[MONITOR DE SALA] 👥 ${data.count} pessoa(s) na sala '${data.room}'`,
'background: #222; color: #bada55; font-size: 16px; font-weight: bold; padding: 4px;'
);
console.log("IDs conectados:", data.users);
// Se quiser um aviso visual na tela da DAW:
if(data.count > 1) {
showToast(`👥 Agora há ${data.count} produtores na sala!`, "success");
}
});
// ---------------------------------
// --- SUBSTITUA SEU socket.on("connect") POR ESTE ---
socket.on("connect", async () => {
console.log(`Conectado ao servidor com ID: ${socket.id}`);
const urlRoom = new URLSearchParams(window.location.search).get("room");
if (urlRoom) {
currentRoom = urlRoom;
console.log(`Modo Online. Sala detectada na URL: ${currentRoom}`);
showToast(`🎧 Conectado à sala ${currentRoom}`, "info"); showToast(`🎧 Conectado à sala ${currentRoom}`, "info");
// Mostra o botão se estiver online
const syncModeBtn = document.getElementById("sync-mode-btn"); // Como tem sala na URL, EXIGE o login/apelido agora
//if (syncModeBtn) syncModeBtn.style.display = ""; // Garante visibilidade const myName = await getOrFetchUsername();
socket.emit("join_room", {
roomName: currentRoom,
userName: myName
});
} else { } else {
console.log("Modo Local. Conectado, mas não em uma sala."); // Entrou na DAW pura, não pede nada ainda.
console.log("Modo Local. Aguardando criação de sala.");
showToast("🔌 Modo local (fora de sala)", "warning"); showToast("🔌 Modo local (fora de sala)", "warning");
// Esconde se offline
const syncModeBtn = document.getElementById("sync-mode-btn");
//if (syncModeBtn) syncModeBtn.style.display = "none";
} }
syncServerTime(); syncServerTime();
@ -238,16 +291,18 @@ socket.on("system_update", (data) => {
socket.on("load_project_state", async (projectXml) => { socket.on("load_project_state", async (projectXml) => {
console.log("Recebendo estado salvo da sala..."); console.log("Recebendo estado salvo da sala...");
showToast("🔄 Recebendo estado atual da sala...", "info", 4000); showToast("🔄 Recebendo estado atual da sala...", "info", 4000);
if (isLoadingProject) return; if (isLoadingProject) return;
isLoadingProject = true; isLoadingProject = true;
try { try {
// 1. Carrega o XML que veio do servidor (pode estar desatualizado) // 1. Carrega o XML que veio do servidor (pode estar desatualizado)
await parseMmpContent(projectXml); await parseMmpContent(projectXml);
// 🔥 CORREÇÃO: Força a restauração da sessão LOCAL logo após carregar o XML // 🔥 CORREÇÃO: Força a restauração da sessão LOCAL logo após carregar o XML
// Isso garante que suas alterações locais (BPM, steps) "ganhem" do servidor // Isso garante que suas alterações locais (BPM, steps) "ganhem" do servidor
if (window.ROOM_NAME) { if (currentRoom) {
const raw = sessionStorage.getItem(`temp_state_${window.ROOM_NAME}`); const raw = sessionStorage.getItem(`temp_state_${currentRoom}`);
if (raw) { if (raw) {
// Se existe 'raw', confiamos que é o estado mais recente do usuário. // Se existe 'raw', confiamos que é o estado mais recente do usuário.
console.log("Re-aplicando sessão local (mesmo se vazia)..."); console.log("Re-aplicando sessão local (mesmo se vazia)...");
@ -256,6 +311,7 @@ socket.on("load_project_state", async (projectXml) => {
} }
renderAll(); renderAll();
saveStateToSession();
showToast("🎵 Projeto carregado com sucesso", "success"); showToast("🎵 Projeto carregado com sucesso", "success");
const hasAudio = const hasAudio =
@ -357,6 +413,8 @@ export function sendAction(action) {
const token = (++lastActionToken).toString(); const token = (++lastActionToken).toString();
action.__token = token; action.__token = token;
// Usa o relógio sincronizado com o servidor
action.__send_time = Date.now() + serverOffsetMs;
action.__senderId = socket.id; action.__senderId = socket.id;
action.__senderName = USER_NAME; action.__senderName = USER_NAME;
@ -537,6 +595,42 @@ socket.on("feedback", (msg) => {
socket.on("action_broadcast", (payload) => { socket.on("action_broadcast", (payload) => {
const action = payload && payload.type ? payload : payload?.action || payload; const action = payload && payload.type ? payload : payload?.action || payload;
// --- INÍCIO DA COLETA DE DADOS ---
const receiveTime = Date.now() + serverOffsetMs;
// 1. Calcula a latência (se foi outro usuário que mandou)
let latencyMs = 0;
if (action.__send_time && action.userId !== socket.id) {
latencyMs = receiveTime - action.__send_time;
}
// 2. Verifica a Ordem dos Pacotes (Jitter/Perda)
let isOutOfOrder = false;
let missingPackets = 0;
if (action.__seq) {
if (lastReceivedSeq !== 0 && action.__seq !== lastReceivedSeq + 1) {
isOutOfOrder = true;
missingPackets = Math.abs(action.__seq - lastReceivedSeq) - 1;
}
lastReceivedSeq = action.__seq;
}
// 3. Salva no array se não for a sua própria mensagem voltando
// TRAVA DE SEGURANÇA: Se o array não existir ainda, cria na hora!
window.jamTelemetry = window.jamTelemetry || [];
if (action.userId !== socket.id) {
window.jamTelemetry.push({
type: action.type,
seq: action.__seq,
latency_ms: latencyMs.toFixed(2),
out_of_order: isOutOfOrder,
missing_packets: missingPackets,
timestamp: new Date().toISOString()
});
}
// --- FIM DA COLETA DE DADOS ---
if (action && action.__token) { if (action && action.__token) {
processedTokens.add(action.__token); processedTokens.add(action.__token);
if (lastBroadcastTimeout && pendingToken === action.__token) { if (lastBroadcastTimeout && pendingToken === action.__token) {
@ -1608,5 +1702,39 @@ async function handleActionBroadcast(action) {
} }
} }
// ============================================================================
// RECUPERAÇÃO DE ESTADO (TAB SLEEP / WAKE UP)
// ============================================================================
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
console.log("%c[SISTEMA] Aba reativada! Solicitando estado...", "color: #00ff00; background: #222; padding: 4px;");
// Usa o currentRoom oficial
if (socket.connected && currentRoom) {
// Deleta o cache local velho
sessionStorage.removeItem(`temp_state_${currentRoom}`);
// Pede o estado fresquinho
socket.emit("request_full_sync", { room: currentRoom });
showToast("🔄 Sincronizando com a sala...", "info");
}
}
});
// ============================================================================
// NOTIFICAÇÕES DE PRESENÇA NA SALA
// ============================================================================
socket.on("user_joined", ({ username }) => {
// Toca um sonzinho rápido e mostra o popup verde
showToast(`👋 ${username} entrou na sala!`, "success", 4000);
console.log(`[PRESENÇA] ${username} conectou-se.`);
});
socket.on("user_left", ({ username }) => {
// Mostra o popup amarelo de aviso
showToast(`🏃 ${username} saiu da sala.`, "warning", 4000);
console.log(`[PRESENÇA] ${username} desconectou-se.`);
});
// EXPORTS // EXPORTS
export { socket }; export { socket };