добавь регистрацию и авторизацию litesql и напиши весь код целиком 
// 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) => {
    // Если пользователь уже существует, обновляем его никнейм
    if (users[socket.id]) {
        users[socket.id].username = sanitizeHtml(username);
    } else {
        users[socket.id] = { username: sanitizeHtml(username), role: 'viewer' }; // Роль по умолчанию – зритель
    }
    console.log(`Пользователь ${socket.id} инициализирован с именем ${users[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); // уведомить стримера о новом зрителе

        // После присоединения к стриму, если пользователь не инициализирован, генерируем ему ник
        if (!users[socket.id] || !users[socket.id].username) {
            const sanitizedUsername = sanitizeHtml(generateRandomUsername());
            users[socket.id] = { username: sanitizedUsername, role: 'viewer' };
            console.log(`Зритель ${socket.id} получил сгенерированный ник: ${sanitizedUsername}`);
            // Отправляем никнейм зрителю
            socket.emit('system-message', `Ваш никнейм: ${sanitizedUsername}`);
        }
    } 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;
}
}

// Функция для генерации случайного ника (для сервера)
function generateRandomUsername() {
const randomNumber = Math.floor(1000 + Math.random() * 9000); // Генерируем 4-значное число
return guster${randomNumber};
}

// Запуск сервера
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(Сервер запущен на порту ${PORT});
});
// 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');
const viewerScreen = document.getElementById('viewerScreen'); // Добавлено для уведомлений

// Навигация по экранам
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.style.display = 'block';
localVideo.style.display = 'none';
uploadedVideo.style.display = 'none';
} else {
fileInputContainer.style.display = 'none';
uploadedVideo.style.display = 'none';
localVideo.style.display = 'block';
// Останавливаем видео-файл при переключении обратно на камеру
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 (isStreamer) {
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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
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
                    addVideoStreamToPeers();
                } 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);
addVideoStreamToPeers();
} 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 signaling when streamer offers to viewer
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();
});

Платформа Трансляций
Начать трансляцию Смотреть трансляции

    <!-- Главный экран -->
    <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>
            <!-- Видео для отображения загруженного файла -->
            <video id="uploadedVideo" autoplay muted playsinline style="display: none;"></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" style="display: none;">
                <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>