проверь когда  я загружаю файл для стрима ,видио не стриметься и зритель его не видит  ,исправь и напиши целиком не меня дизайн который уже есть . 
вот код index.html 
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Платформа трансляций</title>
    <link rel="stylesheet" href="css/styles.css">
    <!-- Подключение Google Fonts для лучшей типографики -->
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
    <!-- Подключение иконок Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
    <div class="container" id="mainContainer">
        <!-- Навигационная панель -->
        <nav class="navbar">
            <div class="navbar-brand">
                <i class="fas fa-stream"></i> Платформа Трансляций
            </div>
            <div class="navbar-buttons">
                <button id="startStreamerBtnNav" class="btn">Начать трансляцию</button>
                <button id="startViewerBtnNav" class="btn">Смотреть трансляции</button>
            </div>
        </nav>

        <!-- Главный экран -->
        <div id="welcomeScreen" class="screen active">
            <div class="welcome-content">
                <h1>Добро пожаловать на платформу трансляций</h1>
                <p>Начните свою трансляцию или присоединяйтесь к зрителям</p>
                <div class="buttons">
                    <button id="startStreamerBtnMain" class="btn primary">Начать трансляцию</button>
                    <button id="startViewerBtnMain" class="btn secondary">Смотреть трансляции</button>
                </div>
            </div>
        </div>

        <!-- Экран стримера -->
        <div id="streamerScreen" class="screen">
            <div class="streamer-header">
                <h2>Начать трансляцию</h2>
                <button id="backToMainFromStreamer" class="btn-icon"><i class="fas fa-arrow-left"></i></button>
            </div>
            <div class="streamer-content">
                <!-- Видео для локального отображения -->
                <video id="localVideo" autoplay muted playsinline></video>

                <!-- Выбор источника трансляции -->
                <div class="source-selection">
                    <label class="radio-label">
                        <input type="radio" name="source" value="camera" checked>
                        <i class="fas fa-video"></i> Веб-камера
                    </label>
                    <label class="radio-label">
                        <input type="radio" name="source" value="file">
                        <i class="fas fa-file-video"></i> Видео-файл
                    </label>
                </div>

                <!-- Загрузка видео-файла -->
                <div id="fileInputContainer" class="file-input-container">
                    <input type="file" id="videoFileInput" accept="video/*">
                </div>

                <!-- Выбор категории трансляции -->
                <div class="category-selection">
                    <label for="category">Категория:</label>
                    <select id="category">
                        <option value="gaming">Игры</option>
                        <option value="music">Музыка</option>
                        <option value="education">Образование</option>
                        <option value="other">Другое</option>
                    </select>
                </div>

                <!-- Кнопки управления трансляцией -->
                <div class="buttons">
                    <button id="startStreamBtn" class="btn primary">Начать стрим</button>
                    <button id="endStreamBtn" class="btn danger" style="display:none;">Закончить стрим</button>
                </div>

                <!-- Информация о трансляции -->
                <div id="streamInfo" class="stream-info">
                    <p>Трансляция активна! Ссылка для зрителей:</p>
                    <input type="text" id="streamLink" readonly>
                    <!-- Прогресс-бар -->
                    <div id="progressContainer">
                        <progress id="videoProgress" value="0" max="100"></progress>
                        <span id="progressLabel">0%</span>
                    </div>
                    <!-- Чат -->
                    <div class="chatContainer">
                        <div class="chatMessages"></div>
                        <form class="chatForm">
                            <input type="text" class="chatInput" placeholder="Введите сообщение...">
                            <button type="submit" class="btn send-btn"><i class="fas fa-paper-plane"></i></button>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <!-- Экран зрителя -->
        <div id="viewerScreen" class="screen">
            <div class="viewer-header">
                <h2>Активные трансляции</h2>
                <button id="backToMainFromViewer" class="btn-icon"><i class="fas fa-arrow-left"></i></button>
            </div>
            <!-- Фильтр по категориям -->
            <div class="filter-section">
                <label for="filterCategory">Фильтр по категории:</label>
                <select id="filterCategory">
                    <option value="all">Все</option>
                    <option value="gaming">Игры</option>
                    <option value="music">Музыка</option>
                    <option value="education">Образование</option>
                    <option value="other">Другое</option>
                </select>
            </div>
            <ul id="streamsList" class="streams-list">
                <!-- Список активных стримов будет динамически заполнен -->
            </ul>
        </div>

        <!-- Экран просмотра трансляции -->
        <div id="streamScreen" class="screen">
            <div class="stream-header">
                <h2>Просмотр трансляции</h2>
                <button id="backToViewer" class="btn-icon"><i class="fas fa-arrow-left"></i></button>
            </div>
            <div id="streamContainer" class="stream-container">
                <video id="remoteVideo" autoplay playsinline controls></video>
                <!-- Чат -->
                <div class="chatContainer">
                    <div class="chatMessages"></div>
                    <form class="chatForm">
                        <input type="text" class="chatInput" placeholder="Введите сообщение...">
                        <button type="submit" class="btn send-btn"><i class="fas fa-paper-plane"></i></button>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <!-- Подключение Socket.IO -->
    <script src="/socket.io/socket.io.js"></script>
    <!-- Подключение основного скрипта -->
    <script src="js/main.js"></script>
</body>
</html>
вот код main.js
// public/js/main.js

const socket = io();

// DOM Elements
const screens = document.querySelectorAll('.screen');
const showScreen = (screenId) => {
    screens.forEach(screen => {
        if (screen.id === screenId) {
            screen.classList.add('active');
        } else {
            screen.classList.remove('active');
        }
    });
};

// Навигационные кнопки
const startStreamerBtnMain = document.getElementById('startStreamerBtnMain');
const startViewerBtnMain = document.getElementById('startViewerBtnMain');

const startStreamerBtnNav = document.getElementById('startStreamerBtnNav');
const startViewerBtnNav = document.getElementById('startViewerBtnNav');

// Кнопки возврата
const backToMainFromStreamer = document.getElementById('backToMainFromStreamer');
const backToMainFromViewer = document.getElementById('backToMainFromViewer');
const backToViewer = document.getElementById('backToViewer');

// Стример элементы
const startStreamBtn = document.getElementById('startStreamBtn');
const endStreamBtn = document.getElementById('endStreamBtn');
const streamInfo = document.getElementById('streamInfo');
const streamLinkInput = document.getElementById('streamLink');
const localVideo = document.getElementById('localVideo');
const uploadedVideo = document.getElementById('uploadedVideo');
const sourceRadios = document.getElementsByName('source');
const fileInputContainer = document.getElementById('fileInputContainer');
const videoFileInput = document.getElementById('videoFileInput');
const videoProgress = document.getElementById('videoProgress');
const progressLabel = document.getElementById('progressLabel');
const categorySelect = document.getElementById('category');

// Чат элементы
const chatContainers = document.querySelectorAll('.chatContainer');
const chatMessagesElements = document.querySelectorAll('.chatMessages');
const chatForms = document.querySelectorAll('.chatForm');
const chatInputs = document.querySelectorAll('.chatInput');

// Зритель элементы
const streamsList = document.getElementById('streamsList');
const remoteVideo = document.getElementById('remoteVideo');
const filterCategory = document.getElementById('filterCategory');

// Навигация по экранам
startStreamerBtnMain.addEventListener('click', () => showScreen('streamerScreen'));
startViewerBtnMain.addEventListener('click', () => {
    showScreen('viewerScreen');
    fetchStreams();
});

// Также добавляем навигационные кнопки в навигационной панели
startStreamerBtnNav.addEventListener('click', () => showScreen('streamerScreen'));
startViewerBtnNav.addEventListener('click', () => {
    showScreen('viewerScreen');
    fetchStreams();
});

// Кнопки возврата
backToMainFromStreamer.addEventListener('click', () => showScreen('welcomeScreen'));
backToMainFromViewer.addEventListener('click', () => showScreen('welcomeScreen'));
backToViewer.addEventListener('click', () => showScreen('viewerScreen'));

// Запрос имени пользователя при подключении
let username = prompt("Введите ваше имя:");
if (!username) {
    username = 'Гость';
}
socket.emit('init-user', username);

// Общие переменные
let streamId = null;
let isStreamer = false;

// Чат функциональность
let currentStreamId = null;

// WebRTC Variables
let localStream = null;
let videoStream = null; // Используется для передачи потоков
const peers = {};
let peerConnection = null;

// Current Source: 'camera' или 'file'
let currentSource = 'camera';

// Обработка выбора источника трансляции
if (sourceRadios) {
    Array.from(sourceRadios).forEach(radio => {
        radio.addEventListener('change', () => {
            currentSource = radio.value;
            if (currentSource === 'file') {
                fileInputContainer.classList.add('visible');
            } else {
                fileInputContainer.classList.remove('visible');
                // Останавливаем видео-файл при переключении обратно на камеру
                if (uploadedVideo.src) {
                    uploadedVideo.pause();
                    uploadedVideo.src = '';
                    videoStream = null;
                    uploadedVideo.style.display = 'none';
                }
            }
        });
    });
}

// Обработка загрузки видео-файла
if (videoFileInput) {
    videoFileInput.addEventListener('change', () => {
        const file = videoFileInput.files[0];
        if (file) {
            const fileURL = URL.createObjectURL(file);
            uploadedVideo.src = fileURL;
            uploadedVideo.style.display = 'block';
            uploadedVideo.load();
            uploadedVideo.play();
            uploadedVideo.onplaying = () => {
                // Захватываем поток после начала воспроизведения
                videoStream = uploadedVideo.captureStream();
                console.log('Видео-файл захвачен:', videoStream);
                // Если стрим уже начат, добавляем видео-файл к существующим соединениям
                if (streamInfo.classList.contains('active')) {
                    addVideoStreamToPeers();
                }
            };

            // Обновление прогресс-бара
            uploadedVideo.ontimeupdate = () => {
                if (uploadedVideo.duration) { // Проверка на существование duration
                    const percentage = (uploadedVideo.currentTime / uploadedVideo.duration) * 100;
                    videoProgress.value = percentage;
                    progressLabel.textContent = `${Math.floor(percentage)}%`;
                }
            };

            uploadedVideo.onended = () => {
                // Автоматически останавливаем трансляцию при окончании видео-файла
                endStream();
            };
        }
    });
}

// Экранирование вводимых сообщений для предотвращения XSS
function escapeHtml(text) {
    const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;',
    };
    return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}

// Чат отправка сообщений для каждой формы чата
chatForms.forEach((form, index) => {
    form.addEventListener('submit', (e) => {
        e.preventDefault();
        const chatInput = chatInputs[index];
        const messageText = chatInput.value.trim();
        if (messageText) {
            const message = {
                sender: username,
                text: messageText
            };
            socket.emit('chat-message', { streamId: currentStreamId, message });
            chatInput.value = '';
            // Не вызываем addChatMessage() здесь, поскольку сообщения приходят через сокеты
        }
    });
});

// Отображение сообщения в чате
function addChatMessage(chatMessages, message) {
    const messageElem = document.createElement('div');
    messageElem.className = 'chat-message';
    messageElem.innerHTML = `<strong>${escapeHtml(message.sender)}:</strong> ${escapeHtml(message.text)}`;
    chatMessages.appendChild(messageElem);
    chatMessages.scrollTop = chatMessages.scrollHeight;
}

// Прием сообщений чата
socket.on('chat-message', (message) => {
    chatMessagesElements.forEach(chatMessages => {
        addChatMessage(chatMessages, message);
    });
});

// Прием истории чата
socket.on('chat-history', (messages) => {
    chatMessagesElements.forEach(chatMessages => {
        chatMessages.innerHTML = '';
        messages.forEach(message => {
            addChatMessage(chatMessages, message);
        });
    });
});

// Стример функциональность

// Start Stream
if (startStreamBtn) {
    startStreamBtn.addEventListener('click', async () => {
        const category = categorySelect.value;
        streamId = await createStream({ category });
        if (streamId) {
            streamInfo.classList.add('active');
            startStreamBtn.style.display = 'none';
            endStreamBtn.style.display = 'inline-block';
            streamLinkInput.value = `${window.location.origin}?streamId=${streamId}`;
            currentStreamId = streamId;
            isStreamer = true;

            if (currentSource === 'camera') {
                // Захват видео с веб-камеры
                startLocalVideo();
            } else if (currentSource === 'file') {
                // Воспроизведение видео-файла и захват потока
                if (uploadedVideo.src) {
                    if (videoStream) {
                        // Ничего не делаем, поток уже захвачен в обработчике onplaying
                    } else {
                        alert('Ожидание захвата видео-файла...');
                    }
                } else {
                    alert('Пожалуйста, выберите видео-файл для трансляции.');
                }
            }
        }
    });
}

// End Stream
if (endStreamBtn) {
    endStreamBtn.addEventListener('click', () => {
        endStream();
    });
}

// Create Stream on Server
function createStream({ category }) {
    return new Promise((resolve) => {
        socket.emit('create-stream', { category }, (generatedStreamId) => {
            resolve(generatedStreamId);
        });
    });
}

// Start Local Video
async function startLocalVideo() {
    try {
        localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
        localVideo.srcObject = localStream;
        videoStream = localStream; // Используется для передачи в Peer Connections
        console.log('Локальный поток захвачен:', videoStream);
    } catch (err) {
        console.error('Ошибка доступа к камере/микрофону:', err);
        alert('Не удалось получить доступ к камере/микрофону.');
    }
}

// Добавление видео-файла к существующим Peer Connections
function addVideoStreamToPeers() {
    for (const viewerId in peers) {
        const peer = peers[viewerId];
        if (videoStream) {
            videoStream.getTracks().forEach(track => peer.pc.addTrack(track, videoStream));
            // Инициализируем повторную сигнализацию
            renegotiate(peer.pc, viewerId);
        } else {
            console.warn('Нет видео-потока для добавления к Peer Connection:', viewerId);
        }
    }
}

// Повторная сигнализация
async function renegotiate(pc, viewerSocketId) {
    try {
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        socket.emit('signal', { to: viewerSocketId, from: socket.id, description: pc.localDescription });
    } catch (err) {
        console.error('Ошибка повторной сигнализации:', err);
    }
}

// Handle Viewer Joined
socket.on('viewer-joined', (viewerSocketId) => {
    console.log('Новый зритель присоединился:', viewerSocketId);
    createPeerConnection(viewerSocketId, true);
});

// Handle Signaling Data
socket.on('signal', async (data) => {
    const { from, candidate, description } = data;
    if (isStreamer) {
        // Стример получает сигнал от зрителя
        if (!peers[from]) {
            createPeerConnection(from, false);
        }
        const peer = peers[from];

        if (description) {
            if (description.type === 'answer') {
                await peer.pc.setRemoteDescription(new RTCSessionDescription(description));
            }
        } else if (candidate) {
            try {
                await peer.pc.addIceCandidate(new RTCIceCandidate(candidate));
            } catch (err) {
                console.error('Ошибка добавления ICE-кандидата:', err);
            }
        }
    } else {
        // Зритель получает сигнал от стримера
        if (!peerConnection) {
            createViewerPeerConnection(from);
        }

        if (description) {
            if (description.type === 'offer') {
                await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
                const answer = await peerConnection.createAnswer();
                await peerConnection.setLocalDescription(answer);
                socket.emit('signal', { to: from, from: socket.id, description: peerConnection.localDescription });
            }
        } else if (candidate) {
            try {
                await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
            } catch (err) {
                console.error('Ошибка добавления ICE-кандидата:', err);
            }
        }
    }
});

// Создание Peer Connection для стримера
function createPeerConnection(viewerSocketId, isInitiator) {
    const peer = {};
    peer.pc = new RTCPeerConnection({
        iceServers: [
            { urls: 'stun:stun.l.google.com:19302' },
            // Добавьте TURN-серверы здесь при необходимости
        ]
    });

    peers[viewerSocketId] = peer;

    // Добавление треков из videoStream в Peer Connection
    if (videoStream) {
        videoStream.getTracks().forEach(track => peer.pc.addTrack(track, videoStream));
    } else {
        console.warn('Нет видео-потока для передачи зрителю:', viewerSocketId);
    }

    // Обработка ICE-кандидатов
    peer.pc.onicecandidate = (event) => {
        if (event.candidate) {
            socket.emit('signal', { to: viewerSocketId, from: socket.id, candidate: event.candidate });
        }
    };

    // Обработка событий соединения
    peer.pc.onconnectionstatechange = () => {
        console.log(`Состояние соединения с ${viewerSocketId}:`, peer.pc.connectionState);
        if (peer.pc.connectionState === 'disconnected' || peer.pc.connectionState === 'failed' || peer.pc.connectionState === 'closed') {
            peer.pc.close();
            delete peers[viewerSocketId];
            console.log(`Соединение с ${viewerSocketId} закрыто.`);
        }
    };

    // Создание предложения, если инициатор
    if (isInitiator) {
        createOffer(peer.pc, viewerSocketId);
    }
}

// Функция для создания и отправки предложения
async function createOffer(pc, viewerSocketId) {
    try {
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        socket.emit('signal', { to: viewerSocketId, from: socket.id, description: pc.localDescription });
    } catch (err) {
        console.error('Ошибка создания предложения:', err);
    }
}

// Закрытие всех Peer Connections и остановка трансляции
function endStream() {
    // Закрытие всех peer connections
    for (const viewerId in peers) {
        peers[viewerId].pc.close();
        delete peers[viewerId];
    }

    // Остановить все локальные треки
    if (localStream) {
        localStream.getTracks().forEach(track => track.stop());
        localStream = null;
        localVideo.srcObject = null;
    }

    // Останавливаем видео-файл
    if (uploadedVideo.src) {
        uploadedVideo.pause();
        uploadedVideo.src = '';
        videoStream = null;
        uploadedVideo.style.display = 'none';
    }

    // Уведомление сервера о завершении трансляции
    socket.emit('end-stream', streamId);

    // Сброс UI
    streamInfo.classList.remove('active');
    startStreamBtn.style.display = 'inline-block';
    endStreamBtn.style.display = 'none';
    streamLinkInput.value = '';
    videoProgress.value = 0;
    progressLabel.textContent = '0%';
    chatMessagesElements.forEach(chatMessages => {
        chatMessages.innerHTML = '';
    });

    isStreamer = false;
    currentStreamId = null;
}

// Зритель функциональность

function fetchStreams() {
    fetch(`/streams`)
        .then(response => response.json())
        .then(data => {
            renderStreams(data);
        })
        .catch(err => {
            console.error('Ошибка при получении списка трансляций:', err);
        });
}

// Render streams list with category filter
function renderStreams(streams) {
    streamsList.innerHTML = '';
    const selectedCategory = filterCategory.value;
    const filteredStreams = selectedCategory === 'all' ? streams : streams.filter(stream => stream.category === selectedCategory);

    if (filteredStreams.length === 0) {
        streamsList.innerHTML = '<p>Нет активных трансляций в данный момент.</p>';
        return;
    }
    filteredStreams.forEach(stream => {
        const listItem = document.createElement('li');
        listItem.className = 'stream-item';

        const button = document.createElement('button');
        button.className = 'btn secondary';
        button.innerHTML = `
            <h3>Трансляция #${stream.id.substring(0, 8)}</h3>
            <p>Категория: ${stream.category}</p>
        `;
        button.addEventListener('click', () => {
            joinStream(stream.id);
        });

        listItem.appendChild(button);
        streamsList.appendChild(listItem);
    });
}

// Обновление списка трансляций в реальном времени
socket.on('update-streams', (activeStreams) => {
    if (viewerScreen.classList.contains('active')) {
        renderStreams(activeStreams);
    }
});

// Обработка фильтрации по категориям
filterCategory.addEventListener('change', () => {
    fetchStreams();
});

// Присоединение к трансляции
function joinStream(streamIdParam) {
    currentStreamId = streamIdParam;
    isStreamer = false;

    showScreen('streamScreen');

    socket.emit('join-stream', streamIdParam, (response) => {
        if (response.success) {
            console.log('Присоединился к трансляции:', streamIdParam);
        } else {
            alert(response.message);
            showScreen('welcomeScreen');
        }
    });
}

// Создание Peer Connection для зрителя
function createViewerPeerConnection(streamerId) {
    peerConnection = new RTCPeerConnection({
        iceServers: [
            { urls: 'stun:stun.l.google.com:19302' },
            // Добавьте TURN-серверы здесь при необходимости
        ]
    });

    // Обработка ICE-кандидатов
    peerConnection.onicecandidate = (event) => {
        if (event.candidate) {
            socket.emit('signal', { to: streamerId, from: socket.id, candidate: event.candidate });
        }
    };

    // Получение удалённого потока
    peerConnection.ontrack = (event) => {
        if (remoteVideo.srcObject !== event.streams[0]) {
            remoteVideo.srcObject = event.streams[0];
            console.log('Получен удалённый поток:', event.streams[0]);
        }
    };

    // Обработка состояний соединения
    peerConnection.onconnectionstatechange = () => {
        console.log(`Состояние соединения:`, peerConnection.connectionState);
        if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed' || peerConnection.connectionState === 'closed') {
            remoteVideo.srcObject = null;
            peerConnection.close();
            peerConnection = null;
            alert('Соединение было прервано.');
            showScreen('viewerScreen');
            fetchStreams();
        }
    };
}

// Handle stream ended
socket.on('stream-ended', () => {
    alert('Трансляция завершена.');
    if (streamScreen.classList.contains('active')) {
        showScreen('viewerScreen');
        remoteVideo.srcObject = null;
    }
});

// Обработка ссылок на прямой переход к трансляции
window.addEventListener('load', () => {
    const urlParams = new URLSearchParams(window.location.search);
    const streamIdParam = urlParams.get('streamId');
    if (streamIdParam) {
        // Автоматически присоединяемся к трансляции
        showScreen('streamScreen');
        joinStream(streamIdParam);
    }
});

// Уведомления

// Запрос разрешения на уведомления
if (Notification.permission !== 'granted') {
    Notification.requestPermission();
}

// Обработка новых стримов для уведомлений
socket.on('new-stream', (stream) => {
    // Если пользователь не смотрит стримы, отправляем уведомление
    if (!viewerScreen.classList.contains('active') && !streamScreen.classList.contains('active')) {
        new Notification('Новая трансляция!', {
            body: `Категория: ${stream.category}`,
            icon: '/path/to/icon.png' // Укажите путь к иконке уведомления
        });
        // Обновляем список стримов
        fetchStreams();
    }
});

// Функция для отправки системных сообщений в чат
socket.on('system-message', (message) => {
    chatMessagesElements.forEach(chatMessages => {
        const messageElem = document.createElement('div');
        messageElem.className = 'system-message';
        messageElem.innerHTML = `<em>${escapeHtml(message)}</em>`;
        chatMessages.appendChild(messageElem);
        chatMessages.scrollTop = chatMessages.scrollHeight;
    });
});

// Обработка бана
socket.on('banned', () => {
    alert('Вы были забанены.');
    showScreen('welcomeScreen');
    window.location.reload();
});

// Обработка кика
socket.on('kicked', () => {
    alert('Вы были кикнуты из трансляции.');
    showScreen('welcomeScreen');
    window.location.reload();
});
вот код server.js
// server.js

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const sanitizeHtml = require('sanitize-html');
const { v4: uuidv4 } = require('uuid');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

// Middleware
app.use(express.static('public'));
app.use(express.json());

// Хранение пользователей и стримов (в реальном проекте используйте базу данных)
const users = {}; // { socketId: { username, role } }
const streams = {}; // { streamId: { host: socketId, category } }
const bannedUsers = {}; // { socketId: banExpirationTime }
const socketToStreamId = {}; // { socketId: streamId }

// Получение списка активных стримов
app.get('/streams', (req, res) => {
    const activeStreams = Object.keys(streams).map(id => ({
        id,
        category: streams[id].category
    }));
    res.json(activeStreams);
});

// Функция для генерации уникального ID стрима
function generateUniqueId() {
    return uuidv4();
}

// Настройка Socket.io
io.on('connection', (socket) => {
    console.log(`Пользователь подключен: ${socket.id}`);

    // Инициализация пользователя
    socket.on('init-user', (username) => {
        users[socket.id] = { username, role: 'viewer' }; // Роль по умолчанию – зритель
        console.log(`Пользователь ${socket.id} инициализирован с именем ${username}`);
    });

    // Создание стрима
    socket.on('create-stream', ({ category }, callback) => {
        if (isUserBanned(socket.id)) {
            console.log(`Пользователь ${socket.id} забанен и не может создавать стрим`);
            callback(null);
            return;
        }

        const streamId = generateUniqueId();
        streams[streamId] = { host: socket.id, category };
        socket.join(streamId);
        users[socket.id].role = 'streamer';
        socketToStreamId[socket.id] = streamId;
        console.log(`Стример ${socket.id} создал стрим ${streamId} в категории ${category}`);
        callback(streamId);
        io.emit('update-streams', getActiveStreams());

        // Уведомление о начале стрима
        io.emit('new-stream', { id: streamId, category });
    });

    // Завершение стрима
    socket.on('end-stream', (streamId) => {
        if (streams[streamId] && streams[streamId].host === socket.id) {
            console.log(`Стример ${socket.id} завершает стрим ${streamId}`);
            delete streams[streamId];
            io.emit('update-streams', getActiveStreams());
            io.to(streamId).emit('stream-ended');

            // Очистка socketToStreamId для стримера
            delete socketToStreamId[socket.id];
        }
    });

    // Присоединение к стриму
    socket.on('join-stream', (streamId, callback) => {
        if (streams[streamId]) {
            socket.join(streamId);
            socketToStreamId[socket.id] = streamId;
            console.log(`Зритель ${socket.id} присоединился к стриму ${streamId}`);
            callback({ success: true });
            io.to(streamId).emit('viewer-joined', socket.id); // уведомить стримера о новом зрителе
        } else {
            console.log(`Зритель ${socket.id} попытался присоединиться к несуществующему стриму ${streamId}`);
            callback({ success: false, message: 'Стрим не найден или уже завершен.' });
        }
    });

    // Обработка сигналов WebRTC
    socket.on('signal', (data) => {
        const { to, from, description, candidate } = data;
        const senderRole = users[socket.id]?.role;
        
        if (senderRole === 'streamer') {
            // Стример отправляет сигнал конкретному зрителю
            console.log(`Сигнал от стримера ${socket.id} к зрителю ${to}`);
            io.to(to).emit('signal', { from, description, candidate });
        } else if (senderRole === 'viewer') {
            // Зритель отправляет сигнал стримеру
            const streamId = socketToStreamId[socket.id];
            if (streamId) {
                const streamerSocketId = streams[streamId]?.host;
                if (streamerSocketId) {
                    console.log(`Сигнал от зрителя ${socket.id} к стримеру ${streamerSocketId}`);
                    io.to(streamerSocketId).emit('signal', { from: socket.id, description, candidate });
                } else {
                    console.log(`Не найден стример для стрима ${streamId}`);
                }
            } else {
                console.log(`Зритель ${socket.id} не присоединился к какому-либо стриму`);
            }
        } else {
            console.log(`Сигнал от неизвестной роли: ${socket.id}`);
        }
    });

    // Обработка сообщений чата с защитой от XSS
    socket.on('chat-message', ({ streamId, message }) => {
        if (isUserBanned(socket.id)) {
            console.log(`Забаненный пользователь ${socket.id} пытается отправить сообщение в чат`);
            return;
        }

        // Проверка, что стрим существует и пользователь присоединён к нему
        if (streams[streamId] && (streams[streamId].host === socket.id || io.sockets.adapter.rooms.get(streamId)?.has(socket.id))) {
            // Экранирование HTML для предотвращения XSS
            const sanitizedMessage = {
                sender: sanitizeHtml(message.sender),
                text: sanitizeHtml(message.text)
            };
            console.log(`Сообщение от ${socket.id} в стрим ${streamId}: ${sanitizedMessage.text}`);
            io.to(streamId).emit('chat-message', sanitizedMessage);
        } else {
            console.log(`Пользователь ${socket.id} пытается отправить сообщение в несуществующий или не присоединённый стрим ${streamId}`);
        }
    });

    // Модерация чата: удаление сообщений и бан пользователей
    socket.on('moderate', ({ action, targetSocketId, streamId }) => {
        if (users[socket.id] && users[socket.id].role === 'admin') {
            const targetUser = users[targetSocketId];
            if (!targetUser) {
                console.log(`Не удалось найти пользователя для модерации: ${targetSocketId}`);
                return;
            }
            if (action === 'ban') {
                // Бан пользователя на 1 час
                bannedUsers[targetSocketId] = Date.now() + 60 * 60 * 1000;
                io.to(targetSocketId).emit('banned');
                io.to(streamId).emit('system-message', `${targetUser.username} был забанен модератором.`);
                console.log(`Пользователь ${targetSocketId} был забанен модератором`);
            } else if (action === 'kick') {
                io.to(targetSocketId).emit('kicked');
                io.to(streamId).emit('system-message', `${targetUser.username} был кикнут модератором.`);
                console.log(`Пользователь ${targetSocketId} был кикнут модератором`);
            }
        } else {
            console.log(`Пользователь ${socket.id} попытался выполнить действие модерации без прав`);
        }
    });

    // Отключение пользователя
    socket.on('disconnect', () => {
        console.log(`Пользователь отключен: ${socket.id}`);

        // Если пользователь был стримером, завершить его стрим
        const streamId = socketToStreamId[socket.id];
        if (streamId && streams[streamId]?.host === socket.id) {
            console.log(`Стример ${socket.id} отключился и завершает стрим ${streamId}`);
            delete streams[streamId];
            io.emit('update-streams', getActiveStreams());
            io.to(streamId).emit('stream-ended');

            // Очистка socketToStreamId для стримера
            delete socketToStreamId[socket.id];
        }

        // Если пользователь был зрителем, очистить его связь со стримом
        if (socketToStreamId[socket.id]) {
            console.log(`Зритель ${socket.id} отключился от стрима ${socketToStreamId[socket.id]}`);
            delete socketToStreamId[socket.id];
        }

        delete users[socket.id];
        delete bannedUsers[socket.id];
    });
});

// Функция для получения активных стримов
function getActiveStreams() {
    return Object.keys(streams).map(id => ({
        id,
        category: streams[id].category
    }));
}

// Функция проверки бана пользователя
function isUserBanned(socketId) {
    const banExpiration = bannedUsers[socketId];
    if (banExpiration && Date.now() < banExpiration) {
        return true;
    } else {
        delete bannedUsers[socketId];
        return false;
    }
}

// Запуск сервера
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
    console.log(`Сервер запущен на порту ${PORT}`);
});
вот код style.css