Comparar commits

...

2 Commits

Autor SHA1 Mensaje Fecha
ale
75555b8454 stats panel
Signed-off-by: ale <ale@manalejandro.com>
2025-11-24 18:03:28 +01:00
ale
18d1eb88da chat scroll
Signed-off-by: ale <ale@manalejandro.com>
2025-11-24 18:00:09 +01:00
Se han modificado 3 ficheros con 133 adiciones y 13 borrados

Ver fichero

@@ -22,7 +22,10 @@ export default function Home() {
const p2pManagerRef = useRef(null);
const [stats, setStats] = useState({
http: 0,
p2p: 0
p2p: 0,
uploadSpeed: 0,
downloadSpeed: 0,
peers: 0
});
// URLs de ejemplo - usando proxy del servidor
@@ -135,10 +138,13 @@ export default function Home() {
}, []);
const handlePeerStats = useCallback((data) => {
// Actualizar estadísticas P2P - usar los deltas que envía el componente
// Actualizar estadísticas P2P completas
setStats(prev => ({
...prev,
p2p: prev.p2p + (data.downloadSpeed || 0) * 5 // velocidad * 5 segundos = bytes descargados en este intervalo
p2p: prev.p2p + (data.downloadSpeed || 0) * 5,
uploadSpeed: data.uploadSpeed || 0,
downloadSpeed: data.downloadSpeed || 0,
peers: data.peers || 0
}));
}, []);
@@ -267,9 +273,32 @@ export default function Home() {
Streaming de video con tecnología P2P y chat en tiempo real
</p>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-sm text-gray-600">WebRTC Activo</span>
<div className="flex items-center space-x-4">
{/* Estadísticas P2P */}
{peers > 0 && (
<div className="flex items-center space-x-4 text-sm">
<div className="flex items-center space-x-1">
<span className="text-gray-600">👥</span>
<span className="font-semibold text-blue-600">{stats.peers}</span>
</div>
<div className="flex items-center space-x-1">
<span className="text-gray-600"></span>
<span className="font-semibold text-green-600">
{(stats.uploadSpeed / 1024).toFixed(1)} KB/s
</span>
</div>
<div className="flex items-center space-x-1">
<span className="text-gray-600"></span>
<span className="font-semibold text-purple-600">
{(stats.downloadSpeed / 1024).toFixed(1)} KB/s
</span>
</div>
</div>
)}
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-sm text-gray-600">WebRTC Activo</span>
</div>
</div>
</div>
</div>
@@ -376,6 +405,46 @@ export default function Home() {
</div>
)}
{/* Panel de Estadísticas P2P */}
{stats.peers > 0 && (
<div className="bg-gradient-to-br from-blue-50 to-purple-50 rounded-lg shadow-lg p-4 border-2 border-blue-200">
<h3 className="text-lg font-bold text-gray-800 mb-3 flex items-center">
<span className="mr-2">📊</span>
Estadísticas P2P en Tiempo Real
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="bg-white rounded-lg p-3 shadow">
<p className="text-xs text-gray-600 mb-1">👥 Peers Conectados</p>
<p className="text-2xl font-bold text-blue-600">{stats.peers}</p>
</div>
<div className="bg-white rounded-lg p-3 shadow">
<p className="text-xs text-gray-600 mb-1"> Subida</p>
<p className="text-xl font-bold text-green-600">
{(stats.uploadSpeed / 1024).toFixed(1)}
</p>
<p className="text-xs text-gray-500">KB/s</p>
</div>
<div className="bg-white rounded-lg p-3 shadow">
<p className="text-xs text-gray-600 mb-1"> Descarga</p>
<p className="text-xl font-bold text-purple-600">
{(stats.downloadSpeed / 1024).toFixed(1)}
</p>
<p className="text-xs text-gray-500">KB/s</p>
</div>
<div className="bg-white rounded-lg p-3 shadow">
<p className="text-xs text-gray-600 mb-1">📦 Total P2P</p>
<p className="text-lg font-bold text-orange-600">
{(stats.p2p / 1024 / 1024).toFixed(2)}
</p>
<p className="text-xs text-gray-500">MB</p>
</div>
</div>
</div>
)}
{/* Video Player */}
<div className="bg-white rounded-lg shadow-lg p-4" ref={videoPlayerRef}>
{watchingUser ? (
@@ -437,8 +506,8 @@ export default function Home() {
/>
</div>
{/* Chat */}
<div className="h-full">
{/* Chat - altura fija con scroll interno */}
<div className="h-[calc(100vh-12rem)] min-h-[600px]">
<Chat
username={username}
onUsernameChange={setUsername}

Ver fichero

@@ -195,7 +195,7 @@ export default function Chat({ username, onUsernameChange, onSocketReady, onWatc
return (
<div className="bg-white rounded-lg shadow-lg overflow-hidden flex flex-col h-full">
{/* Header */}
<div className="bg-gradient-to-r from-blue-500 to-purple-600 p-4">
<div className="bg-gradient-to-r from-blue-500 to-purple-600 p-4 flex-shrink-0">
<div className="flex items-center justify-between">
<h3 className="text-white font-bold text-lg">Chat en Vivo</h3>
<div className="flex items-center space-x-2">
@@ -207,13 +207,12 @@ export default function Chat({ username, onUsernameChange, onSocketReady, onWatc
</div>
{/* Usuarios conectados */}
<div className="bg-gray-50 p-3 border-b">
<div className="bg-gray-50 p-3 border-b flex-shrink-0">
<h4 className="text-xs font-semibold text-gray-600 mb-2">
USUARIOS CONECTADOS ({users.length})
</h4>
<div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto">
{users.map((user, index) => {
<div className="grid grid-cols-2 gap-2 max-h-32 overflow-y-auto">{users.map((user, index) => {
const isCurrentUser = user === username;
const hasThumbnail = userThumbnails[user]?.thumbnail;
const isHovered = hoveredUser === user;
@@ -329,7 +328,7 @@ export default function Chat({ username, onUsernameChange, onSocketReady, onWatc
</div>
{/* Input de mensaje */}
<form onSubmit={sendMessage} className="p-4 bg-white border-t">
<form onSubmit={sendMessage} className="p-4 bg-white border-t flex-shrink-0">
<div className="flex space-x-2">
<input
type="text"

Ver fichero

@@ -257,11 +257,56 @@ const P2PManager = forwardRef(({
});
peer.on('connect', () => {
console.log(`✅ Peer conectado con ${targetUser}`);
setPeers(prev => {
const newPeers = [...prev, targetUser];
if (onPeersUpdateRef.current) onPeersUpdateRef.current(newPeers);
return newPeers;
});
// Iniciar monitoreo de estadísticas WebRTC cada 1 segundo
const statsMonitor = setInterval(async () => {
if (!peer._pc || peer.destroyed) {
clearInterval(statsMonitor);
return;
}
try {
const stats = await peer._pc.getStats();
let bytesReceived = 0;
let bytesSent = 0;
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
bytesReceived += report.bytesReceived || 0;
}
if (report.type === 'outbound-rtp' && report.mediaType === 'video') {
bytesSent += report.bytesSent || 0;
}
});
// Guardar stats anteriores para calcular delta
if (!peer._lastStats) {
peer._lastStats = { bytesReceived, bytesSent, timestamp: Date.now() };
} else {
const deltaTime = (Date.now() - peer._lastStats.timestamp) / 1000;
const deltaReceived = bytesReceived - peer._lastStats.bytesReceived;
const deltaSent = bytesSent - peer._lastStats.bytesSent;
if (deltaTime > 0) {
downloadBytes.current += deltaReceived;
uploadBytes.current += deltaSent;
}
peer._lastStats = { bytesReceived, bytesSent, timestamp: Date.now() };
}
} catch (err) {
// Ignorar errores de stats
}
}, 1000);
// Guardar el interval en el peer para limpiarlo después
peer._statsMonitor = statsMonitor;
});
peer.on('data', (data) => {
@@ -277,6 +322,10 @@ const P2PManager = forwardRef(({
});
peer.on('close', () => {
console.log(`🔌 Peer cerrado con ${targetUser}`);
if (peer._statsMonitor) {
clearInterval(peer._statsMonitor);
}
if (peersRef.current[targetUser] === peer) {
delete peersRef.current[targetUser];
delete remoteStreamsRef.current[targetUser];
@@ -290,6 +339,9 @@ const P2PManager = forwardRef(({
peer.on('error', (err) => {
console.error('❌ Error en peer', targetUser, ':', err.message || err);
if (peer._statsMonitor) {
clearInterval(peer._statsMonitor);
}
if (peersRef.current[targetUser] === peer) {
delete peersRef.current[targetUser];
delete remoteStreamsRef.current[targetUser];