Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -172,12 +172,14 @@ def room(token):
|
|
| 172 |
|
| 173 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"></script>
|
| 174 |
<script>
|
| 175 |
-
// Используем относительный путь для подключения сокетов
|
| 176 |
const socket = io();
|
| 177 |
const token = '{{ token }}';
|
| 178 |
const username = '{{ session['username'] }}';
|
| 179 |
let localStream;
|
| 180 |
const peers = {};
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
// Получение локального видео/аудио потока
|
| 183 |
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
|
@@ -196,10 +198,8 @@ def room(token):
|
|
| 196 |
|
| 197 |
const video = document.createElement('video');
|
| 198 |
video.srcObject = stream;
|
| 199 |
-
// Важно для мобильных устройств:
|
| 200 |
video.setAttribute('playsinline', '');
|
| 201 |
video.setAttribute('autoplay', '');
|
| 202 |
-
// По умолчанию autoplay может блокироваться, поэтому вызываем play() явно
|
| 203 |
video.addEventListener('loadedmetadata', () => {
|
| 204 |
video.play().catch(e => console.error('Autoplay error:', e));
|
| 205 |
});
|
|
@@ -209,24 +209,28 @@ def room(token):
|
|
| 209 |
document.getElementById('video-grid').appendChild(video);
|
| 210 |
}
|
| 211 |
|
| 212 |
-
// Создание соединения с другим пользователем
|
| 213 |
-
function connectToUser(user) {
|
| 214 |
-
if (user === username || peers[user]) return;
|
| 215 |
-
console.log('Инициирую соединение с', user);
|
| 216 |
-
const peer = new RTCPeerConnection({
|
| 217 |
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
| 218 |
-
});
|
| 219 |
-
peers[user] = peer;
|
| 220 |
|
| 221 |
-
localStream.getTracks().forEach(track => peer.addTrack(track, localStream));
|
| 222 |
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
console.log('Получен поток от', user);
|
| 225 |
addVideoStream(event.streams[0], user);
|
| 226 |
};
|
| 227 |
|
| 228 |
-
|
| 229 |
if (event.candidate) {
|
|
|
|
| 230 |
socket.emit('signal', {
|
| 231 |
token: token,
|
| 232 |
from: username,
|
|
@@ -235,82 +239,94 @@ def room(token):
|
|
| 235 |
});
|
| 236 |
}
|
| 237 |
};
|
| 238 |
-
|
| 239 |
-
peer.onnegotiationneeded = () => {
|
| 240 |
-
peer.createOffer()
|
| 241 |
-
.then(offer => peer.setLocalDescription(offer))
|
| 242 |
-
.then(() => {
|
| 243 |
-
socket.emit('signal', {
|
| 244 |
-
token: token,
|
| 245 |
-
from: username,
|
| 246 |
-
to: user,
|
| 247 |
-
signal: peer.localDescription
|
| 248 |
-
});
|
| 249 |
-
})
|
| 250 |
-
.catch(err => console.error('Ошибка создания предложения:', err));
|
| 251 |
-
};
|
| 252 |
}
|
| 253 |
|
| 254 |
-
|
| 255 |
socket.on('signal', data => {
|
| 256 |
-
// Если сигнал пришёл от нас же, игнорируем
|
| 257 |
if (data.from === username) return;
|
| 258 |
console.log('Получен сигнал от', data.from, ':', data.signal.type);
|
| 259 |
|
| 260 |
-
let
|
| 261 |
-
if (!
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
socket.emit('signal', {
|
| 272 |
token: token,
|
| 273 |
from: username,
|
| 274 |
to: data.from,
|
| 275 |
-
signal:
|
| 276 |
});
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
.then(answer => peer.setLocalDescription(answer))
|
| 285 |
-
.then(() => socket.emit('signal', {
|
| 286 |
-
token: token,
|
| 287 |
-
from: username,
|
| 288 |
-
to: data.from,
|
| 289 |
-
signal: peer.localDescription
|
| 290 |
-
}))
|
| 291 |
.catch(err => console.error('Ошибка обработки предложения:', err));
|
|
|
|
| 292 |
} else if (data.signal.type === 'answer') {
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
} else if (data.signal.type === 'candidate') {
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
| 299 |
});
|
| 300 |
-
|
| 301 |
socket.on('user_joined', data => {
|
| 302 |
console.log('Пользователь', data.username, 'присоединился');
|
| 303 |
document.getElementById('users').innerText = 'Пользователи: ' + data.users.join(', ');
|
|
|
|
| 304 |
if (data.username !== username) {
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
}
|
| 307 |
});
|
| 308 |
|
|
|
|
| 309 |
socket.on('user_left', data => {
|
| 310 |
console.log('Пользователь', data.username, 'покинул комнату');
|
| 311 |
document.getElementById('users').innerText = 'Пользователи: ' + data.users.join(', ');
|
| 312 |
if (peers[data.username]) {
|
| 313 |
-
peers[data.username].close();
|
| 314 |
delete peers[data.username];
|
| 315 |
const video = document.querySelector(`video[data-user="${data.username}"]`);
|
| 316 |
if (video) video.remove();
|
|
@@ -320,15 +336,16 @@ def room(token):
|
|
| 320 |
socket.on('init_users', data => {
|
| 321 |
console.log('Инициализация пользователей:', data.users);
|
| 322 |
data.users.forEach(user => {
|
| 323 |
-
if (user !== username)
|
|
|
|
|
|
|
| 324 |
});
|
| 325 |
});
|
| 326 |
-
|
| 327 |
function leaveRoom() {
|
| 328 |
socket.emit('leave', { token: token, username: username });
|
| 329 |
localStream.getTracks().forEach(track => track.stop());
|
| 330 |
for (let user in peers) {
|
| 331 |
-
peers[user].close();
|
| 332 |
}
|
| 333 |
window.location.href = '/dashboard';
|
| 334 |
}
|
|
|
|
| 172 |
|
| 173 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"></script>
|
| 174 |
<script>
|
|
|
|
| 175 |
const socket = io();
|
| 176 |
const token = '{{ token }}';
|
| 177 |
const username = '{{ session['username'] }}';
|
| 178 |
let localStream;
|
| 179 |
const peers = {};
|
| 180 |
+
const iceConfig = {
|
| 181 |
+
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
| 182 |
+
};
|
| 183 |
|
| 184 |
// Получение локального видео/аудио потока
|
| 185 |
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
|
|
|
| 198 |
|
| 199 |
const video = document.createElement('video');
|
| 200 |
video.srcObject = stream;
|
|
|
|
| 201 |
video.setAttribute('playsinline', '');
|
| 202 |
video.setAttribute('autoplay', '');
|
|
|
|
| 203 |
video.addEventListener('loadedmetadata', () => {
|
| 204 |
video.play().catch(e => console.error('Autoplay error:', e));
|
| 205 |
});
|
|
|
|
| 209 |
document.getElementById('video-grid').appendChild(video);
|
| 210 |
}
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
|
|
|
| 213 |
|
| 214 |
+
// Создание соединения с другим пользователем (используется и при инициации, и при ответе)
|
| 215 |
+
function createPeerConnection(user) {
|
| 216 |
+
if (peers[user]) {
|
| 217 |
+
return peers[user].peerConnection; // Return existing connection
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
console.log('Создание RTCPeerConnection для', user);
|
| 221 |
+
const peerConnection = new RTCPeerConnection(iceConfig);
|
| 222 |
+
peers[user] = { peerConnection: peerConnection, iceCandidates: [] };
|
| 223 |
+
|
| 224 |
+
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
|
| 225 |
+
|
| 226 |
+
peerConnection.ontrack = event => {
|
| 227 |
console.log('Получен поток от', user);
|
| 228 |
addVideoStream(event.streams[0], user);
|
| 229 |
};
|
| 230 |
|
| 231 |
+
peerConnection.onicecandidate = event => {
|
| 232 |
if (event.candidate) {
|
| 233 |
+
console.log('Отправка ICE-кандидата для', user);
|
| 234 |
socket.emit('signal', {
|
| 235 |
token: token,
|
| 236 |
from: username,
|
|
|
|
| 239 |
});
|
| 240 |
}
|
| 241 |
};
|
| 242 |
+
return peerConnection;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
|
| 245 |
+
// Обработка входящих сигналов
|
| 246 |
socket.on('signal', data => {
|
|
|
|
| 247 |
if (data.from === username) return;
|
| 248 |
console.log('Получен сигнал от', data.from, ':', data.signal.type);
|
| 249 |
|
| 250 |
+
let peerEntry = peers[data.from];
|
| 251 |
+
if (!peerEntry) {
|
| 252 |
+
createPeerConnection(data.from);
|
| 253 |
+
peerEntry = peers[data.from];
|
| 254 |
+
}
|
| 255 |
+
let peerConnection = peerEntry.peerConnection;
|
| 256 |
+
|
| 257 |
+
if (data.signal.type === 'offer') {
|
| 258 |
+
console.log('Обработка предложения от', data.from);
|
| 259 |
+
peerConnection.setRemoteDescription(new RTCSessionDescription(data.signal))
|
| 260 |
+
.then(() => peerConnection.createAnswer())
|
| 261 |
+
.then(answer => peerConnection.setLocalDescription(answer))
|
| 262 |
+
.then(() => {
|
| 263 |
socket.emit('signal', {
|
| 264 |
token: token,
|
| 265 |
from: username,
|
| 266 |
to: data.from,
|
| 267 |
+
signal: peerConnection.localDescription
|
| 268 |
});
|
| 269 |
+
//Добавление ICE-кандидатов из буффера
|
| 270 |
+
while (peerEntry.iceCandidates.length > 0) {
|
| 271 |
+
const candidate = peerEntry.iceCandidates.shift();
|
| 272 |
+
peerConnection.addIceCandidate(new RTCIceCandidate(candidate))
|
| 273 |
+
.catch(err => console.error("Error adding ice candidate from buffer", err));
|
| 274 |
+
}
|
| 275 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
.catch(err => console.error('Ошибка обработки предложения:', err));
|
| 277 |
+
|
| 278 |
} else if (data.signal.type === 'answer') {
|
| 279 |
+
console.log('Обработка ответа от', data.from);
|
| 280 |
+
peerConnection.setRemoteDescription(new RTCSessionDescription(data.signal))
|
| 281 |
+
.then(() => {
|
| 282 |
+
//Добавление ICE-кандидатов из буффера
|
| 283 |
+
while (peerEntry.iceCandidates.length > 0) {
|
| 284 |
+
const candidate = peerEntry.iceCandidates.shift();
|
| 285 |
+
peerConnection.addIceCandidate(new RTCIceCandidate(candidate))
|
| 286 |
+
.catch(err => console.error("Error adding ice candidate from buffer", err));
|
| 287 |
+
}
|
| 288 |
+
})
|
| 289 |
+
.catch(err => console.error('Ошибка установки ответа:', err));
|
| 290 |
+
|
| 291 |
} else if (data.signal.type === 'candidate') {
|
| 292 |
+
console.log('Обработка ICE-кандидата от', data.from);
|
| 293 |
+
if (peerConnection.remoteDescription) {
|
| 294 |
+
peerConnection.addIceCandidate(new RTCIceCandidate(data.signal.candidate))
|
| 295 |
+
.catch(err => console.error('Ошибка добавления ICE-кандидата:', err));
|
| 296 |
+
} else {
|
| 297 |
+
// Если remote description еще не установлен, добавляем в буфер
|
| 298 |
+
peerEntry.iceCandidates.push(data.signal.candidate);
|
| 299 |
+
console.log("Ice candidate buffered for", data.from)
|
| 300 |
+
}
|
| 301 |
}
|
| 302 |
});
|
|
|
|
| 303 |
socket.on('user_joined', data => {
|
| 304 |
console.log('Пользователь', data.username, 'присоединился');
|
| 305 |
document.getElementById('users').innerText = 'Пользователи: ' + data.users.join(', ');
|
| 306 |
+
|
| 307 |
if (data.username !== username) {
|
| 308 |
+
const peerConnection = createPeerConnection(data.username);
|
| 309 |
+
// Создание предложения (offer), только если мы инициатор
|
| 310 |
+
peerConnection.createOffer()
|
| 311 |
+
.then(offer => peerConnection.setLocalDescription(offer))
|
| 312 |
+
.then(() => {
|
| 313 |
+
socket.emit('signal', {
|
| 314 |
+
token: token,
|
| 315 |
+
from: username,
|
| 316 |
+
to: data.username,
|
| 317 |
+
signal: peerConnection.localDescription
|
| 318 |
+
});
|
| 319 |
+
})
|
| 320 |
+
.catch(err => console.error('Ошибка создания п��едложения:', err));
|
| 321 |
}
|
| 322 |
});
|
| 323 |
|
| 324 |
+
|
| 325 |
socket.on('user_left', data => {
|
| 326 |
console.log('Пользователь', data.username, 'покинул комнату');
|
| 327 |
document.getElementById('users').innerText = 'Пользователи: ' + data.users.join(', ');
|
| 328 |
if (peers[data.username]) {
|
| 329 |
+
peers[data.username].peerConnection.close();
|
| 330 |
delete peers[data.username];
|
| 331 |
const video = document.querySelector(`video[data-user="${data.username}"]`);
|
| 332 |
if (video) video.remove();
|
|
|
|
| 336 |
socket.on('init_users', data => {
|
| 337 |
console.log('Инициализация пользователей:', data.users);
|
| 338 |
data.users.forEach(user => {
|
| 339 |
+
if (user !== username) {
|
| 340 |
+
createPeerConnection(user); // Создаем RTCPeerConnection для всех, *кроме себя*
|
| 341 |
+
}
|
| 342 |
});
|
| 343 |
});
|
|
|
|
| 344 |
function leaveRoom() {
|
| 345 |
socket.emit('leave', { token: token, username: username });
|
| 346 |
localStream.getTracks().forEach(track => track.stop());
|
| 347 |
for (let user in peers) {
|
| 348 |
+
peers[user].peerConnection.close();
|
| 349 |
}
|
| 350 |
window.location.href = '/dashboard';
|
| 351 |
}
|