testando medida de latencia
Deploy / Deploy (push) Has been cancelled
Details
Deploy / Deploy (push) Has been cancelled
Details
This commit is contained in:
parent
43a66c4f53
commit
477b693b79
4634497
_data/all.yml
4634497
_data/all.yml
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
Loading…
Reference in New Issue