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 { ZOOM_LEVELS } from "./config.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 { showToast } from "./ui.js";
import { toggleRecording } from "./recording.js"
@ -696,6 +696,14 @@ document.addEventListener("DOMContentLoaded", () => {
const defaultName = `sessao-${Math.random()
.toString(36)
.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(
"Digite um nome para a sala compartilhada:",
defaultName

View File

@ -12,6 +12,8 @@ const pino = require("pino");
const os = require("os");
const crypto = require("crypto");
const { spawn } = require("child_process");
// --- RASTREAMENTO DE USUÁRIOS ---
const activeUsers = {}; // Mapeia socket.id -> { username, room }
//import { LOG_SERVER } from "../utils.js"
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) => {
console.log(`Novo usuário conectado: ${socket.id}`);
// =======================================
// JOIN ROOM
// =======================================
// =========================================================
// ENTRADA, IDENTIDADE E SINCRONIZAÇÃO DE SALA
// =========================================================
socket.on("join_room", async (data) => {
const { roomName, userName } = data || {};
if (!roomName) {
console.warn(`join_room inválido de ${socket.id} (sem roomName)`);
console.warn(`join_room inválido de ${socket.id}`);
return;
}
// Registra a identidade
const finalUserName = userName || "(sem nome)";
activeUsers[socket.id] = { username: finalUserName, room: roomName };
await socket.join(roomName);
console.log(
`Usuário ${userName || "(sem nome)"} (${
socket.id
}) entrou na sala: ${roomName}`
);
console.log(`[ROOM] 🟢 Usuário ${finalUserName} (${socket.id}) entrou na sala: ${roomName}`);
// Avisa os outros para mostrar o Popup Verde
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");
// envia estado salvo do projeto (XML) — compat V4
const room = ensureRoom(roomName);
if (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", {
action: {
type: "AUDIO_SNAPSHOT",
snapshot: room.audio,
__seq: room.seq,
__target: socket.id,
},
action: { type: "AUDIO_SNAPSHOT", snapshot: room.audio, __seq: room.seq, __target: socket.id },
});
socket
.to(roomName)
.emit("feedback", `Usuário ${userName || socket.id} entrou na sala.`);
socket.to(roomName).emit("feedback", `Usuário ${finalUserName} 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
// =======================================
@ -627,6 +656,7 @@ io.on("connection", (socket) => {
});
socket.on("disconnect", () => {
delete activeUsers[socket.id]; // Limpa a identidade da memória do servidor
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
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 { 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() {
// 1) pega do primeiro bassline que já tenha instrumentSourceId
const b = (appState.pattern.tracks || []).find(
@ -101,8 +137,7 @@ const socket = io(`https://alice.ufsj.edu.br:${PORT_SOCK}`, {
withCredentials: true,
});
let USER_NAME = `Alicer-${Math.floor(Math.random() * 9999)}`;
let currentRoom = null;
// -------------------------------------
// -----------------------------------------------------------------------------
// CLOCK SYNC — Sincroniza relógio cliente-servidor
@ -177,27 +212,45 @@ export function sendActionSafe(action) {
// -----------------------------------------------------------------------------
// 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) {
console.log(`Modo Online. Sala detectada: ${currentRoom}`);
// --- MONITORAMENTO DE PRESENÇA ---
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");
// Mostra o botão se estiver online
const syncModeBtn = document.getElementById("sync-mode-btn");
//if (syncModeBtn) syncModeBtn.style.display = ""; // Garante visibilidade
// Como tem sala na URL, EXIGE o login/apelido agora
const myName = await getOrFetchUsername();
socket.emit("join_room", {
roomName: currentRoom,
userName: myName
});
} 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");
// Esconde se offline
const syncModeBtn = document.getElementById("sync-mode-btn");
//if (syncModeBtn) syncModeBtn.style.display = "none";
}
syncServerTime();
@ -238,16 +291,18 @@ socket.on("system_update", (data) => {
socket.on("load_project_state", async (projectXml) => {
console.log("Recebendo estado salvo da sala...");
showToast("🔄 Recebendo estado atual da sala...", "info", 4000);
if (isLoadingProject) return;
isLoadingProject = true;
try {
// 1. Carrega o XML que veio do servidor (pode estar desatualizado)
await parseMmpContent(projectXml);
// 🔥 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
if (window.ROOM_NAME) {
const raw = sessionStorage.getItem(`temp_state_${window.ROOM_NAME}`);
if (currentRoom) {
const raw = sessionStorage.getItem(`temp_state_${currentRoom}`);
if (raw) {
// Se existe 'raw', confiamos que é o estado mais recente do usuário.
console.log("Re-aplicando sessão local (mesmo se vazia)...");
@ -256,6 +311,7 @@ socket.on("load_project_state", async (projectXml) => {
}
renderAll();
saveStateToSession();
showToast("🎵 Projeto carregado com sucesso", "success");
const hasAudio =
@ -357,6 +413,8 @@ export function sendAction(action) {
const token = (++lastActionToken).toString();
action.__token = token;
// Usa o relógio sincronizado com o servidor
action.__send_time = Date.now() + serverOffsetMs;
action.__senderId = socket.id;
action.__senderName = USER_NAME;
@ -537,6 +595,42 @@ socket.on("feedback", (msg) => {
socket.on("action_broadcast", (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) {
processedTokens.add(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
export { socket };