Vocal canaux avec membres en apercu
This commit is contained in:
parent
4eaf7679f1
commit
e6a755b1d6
@ -107,7 +107,7 @@ if ($action === "join") {
|
||||
if (($p["last_seen"] ?? 0) < $stale_time) unset($ps[$id]);
|
||||
}
|
||||
|
||||
$new_id = peer_id();
|
||||
$new_id = substr($_REQUEST["peer_id"] ?: peer_id(), 0, 16);
|
||||
$ps[$new_id] = [
|
||||
"id" => $new_id,
|
||||
"user_id" => $current_user_id,
|
||||
@ -120,9 +120,14 @@ if ($action === "join") {
|
||||
// DB Integration for sidebar
|
||||
if ($current_user_id > 0) {
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO voice_sessions (user_id, channel_id, last_seen) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE channel_id = ?, last_seen = ?");
|
||||
$stmt->execute([$current_user_id, $room, now_ms(), $room, now_ms()]);
|
||||
} catch (Exception $e) {}
|
||||
$stmt = db()->prepare("INSERT INTO voice_sessions (user_id, channel_id, last_seen, peer_id, name) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE channel_id = ?, last_seen = ?, peer_id = ?, name = ?");
|
||||
$res = $stmt->execute([$current_user_id, $room, now_ms(), $new_id, $name, $room, now_ms(), $new_id, $name]);
|
||||
if (!$res) {
|
||||
error_log("Failed to insert voice session for user $current_user_id in room $room");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Voice session DB error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
json_out(["success" => true, "peer_id" => $new_id, "participants" => $ps]);
|
||||
|
||||
@ -925,7 +925,7 @@ body {
|
||||
}
|
||||
|
||||
/* Voice active state */
|
||||
.voice-item.active {
|
||||
.voice-item.active, .voice-item.connected {
|
||||
background-color: rgba(35, 165, 89, 0.1);
|
||||
color: #23a559 !important;
|
||||
}
|
||||
|
||||
@ -29,7 +29,22 @@ class VoiceChannel {
|
||||
this.speakingUsers = new Set();
|
||||
|
||||
this.setupPTTListeners();
|
||||
window.addEventListener('beforeunload', () => this.leave());
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// We don't want to leave on page refresh if we want persistence
|
||||
// but we might want to tell the server we are "still here" soon.
|
||||
// Actually, for a simple refresh, we just let the session timeout or re-join.
|
||||
});
|
||||
|
||||
// Auto-rejoin if we were in a channel
|
||||
setTimeout(() => {
|
||||
const savedChannelId = sessionStorage.getItem('activeVoiceChannel');
|
||||
const savedPeerId = sessionStorage.getItem('activeVoicePeerId');
|
||||
if (savedChannelId) {
|
||||
console.log('Auto-rejoining voice channel:', savedChannelId);
|
||||
if (savedPeerId) this.myPeerId = savedPeerId;
|
||||
this.join(savedChannelId, true); // Pass true to indicate auto-rejoin
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
setupPTTListeners() {
|
||||
@ -67,18 +82,19 @@ class VoiceChannel {
|
||||
});
|
||||
}
|
||||
|
||||
async join(channelId) {
|
||||
console.log('VoiceChannel.join process started for channel:', channelId);
|
||||
if (this.currentChannelId === channelId) {
|
||||
async join(channelId, isAutoRejoin = false) {
|
||||
console.log('VoiceChannel.join process started for channel:', channelId, 'isAutoRejoin:', isAutoRejoin);
|
||||
if (this.currentChannelId === channelId && !isAutoRejoin) {
|
||||
console.log('Already in this channel');
|
||||
return;
|
||||
}
|
||||
if (this.currentChannelId) {
|
||||
if (this.currentChannelId && this.currentChannelId != channelId) {
|
||||
console.log('Leaving previous channel:', this.currentChannelId);
|
||||
this.leave();
|
||||
}
|
||||
|
||||
this.currentChannelId = channelId;
|
||||
sessionStorage.setItem('activeVoiceChannel', channelId);
|
||||
|
||||
try {
|
||||
console.log('Requesting microphone access...');
|
||||
@ -91,13 +107,14 @@ class VoiceChannel {
|
||||
|
||||
// Join via PHP
|
||||
console.log('Calling API join...');
|
||||
const url = `api_v1_voice.php?action=join&room=${channelId}&name=${encodeURIComponent(window.currentUsername || 'Unknown')}`;
|
||||
const url = `api_v1_voice.php?action=join&room=${channelId}&name=${encodeURIComponent(window.currentUsername || 'Unknown')}${this.myPeerId ? '&peer_id='+this.myPeerId : ''}`;
|
||||
const resp = await fetch(url);
|
||||
const data = await resp.json();
|
||||
console.log('API join response:', data);
|
||||
|
||||
if (data.success) {
|
||||
this.myPeerId = data.peer_id;
|
||||
sessionStorage.setItem('activeVoicePeerId', this.myPeerId);
|
||||
console.log('Joined room with peer_id:', this.myPeerId);
|
||||
|
||||
// Start polling
|
||||
@ -381,14 +398,27 @@ class VoiceChannel {
|
||||
}
|
||||
|
||||
leave() {
|
||||
if (!this.currentChannelId) return;
|
||||
console.log('Leaving voice channel:', this.currentChannelId);
|
||||
if (!this.currentChannelId) {
|
||||
console.log('VoiceChannel.leave called but no active channel');
|
||||
return;
|
||||
}
|
||||
console.log('Leaving voice channel:', this.currentChannelId, 'myPeerId:', this.myPeerId);
|
||||
const cid = this.currentChannelId;
|
||||
const pid = this.myPeerId;
|
||||
|
||||
sessionStorage.removeItem('activeVoiceChannel');
|
||||
sessionStorage.removeItem('activeVoicePeerId');
|
||||
if (this.pollInterval) clearInterval(this.pollInterval);
|
||||
|
||||
fetch(`api_v1_voice.php?action=leave&room=${this.currentChannelId}&peer_id=${this.myPeerId}`);
|
||||
// Use keepalive for the leave fetch to ensure it reaches the server during page unload
|
||||
fetch(`api_v1_voice.php?action=leave&room=${cid}&peer_id=${pid}`, { keepalive: true });
|
||||
|
||||
if (this.localStream) {
|
||||
this.localStream.getTracks().forEach(track => track.stop());
|
||||
console.log('Stopping local stream tracks');
|
||||
this.localStream.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
console.log('Track stopped:', track.kind);
|
||||
});
|
||||
this.localStream = null;
|
||||
}
|
||||
if (this.analysisStream) {
|
||||
@ -482,11 +512,18 @@ class VoiceChannel {
|
||||
// Clear all lists first
|
||||
document.querySelectorAll('.voice-users-list').forEach(el => el.innerHTML = '');
|
||||
|
||||
// Remove connected highlight from all voice items
|
||||
document.querySelectorAll('.voice-item').forEach(el => {
|
||||
el.classList.remove('connected');
|
||||
});
|
||||
|
||||
// Populate based on data
|
||||
Object.keys(data.channels).forEach(channelId => {
|
||||
// Fix: The voice-users-list is a sibling of the container of the voice-item
|
||||
const voiceItem = document.querySelector(`.voice-item[data-channel-id="${channelId}"]`);
|
||||
if (voiceItem) {
|
||||
// Highlight channel as connected if anyone is in it
|
||||
voiceItem.classList.add('connected');
|
||||
|
||||
const container = voiceItem.closest('.channel-item-container');
|
||||
if (container) {
|
||||
const listEl = container.querySelector('.voice-users-list');
|
||||
|
||||
@ -1,5 +1,52 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/session.php';
|
||||
|
||||
$user = getCurrentUser();
|
||||
if ($user) {
|
||||
try {
|
||||
// Find which channel and peer they were in to clean up files too
|
||||
$stmt = db()->prepare("SELECT channel_id, peer_id FROM voice_sessions WHERE user_id = ?");
|
||||
$stmt->execute([$user['id']]);
|
||||
$sess = $stmt->fetch();
|
||||
|
||||
if ($sess) {
|
||||
$room = $sess['channel_id'];
|
||||
$peerId = $sess['peer_id'];
|
||||
|
||||
// Clean up file-based participants
|
||||
$p_file = __DIR__ . "/../data/" . $room . ".participants.json";
|
||||
if (file_exists($p_file)) {
|
||||
$raw = @file_get_contents($p_file);
|
||||
if ($raw) {
|
||||
$ps = json_decode($raw, true);
|
||||
if (is_array($ps)) {
|
||||
$found = false;
|
||||
if (isset($ps[$peerId])) {
|
||||
unset($ps[$peerId]);
|
||||
$found = true;
|
||||
}
|
||||
// Also cleanup by user_id just in case
|
||||
foreach ($ps as $id => $p) {
|
||||
if (($p['user_id'] ?? 0) == $user['id']) {
|
||||
unset($ps[$id]);
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
file_put_contents($p_file, json_encode($ps), LOCK_EX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up DB session
|
||||
db()->prepare("DELETE FROM voice_sessions WHERE user_id = ?")->execute([$user['id']]);
|
||||
} catch (Exception $e) {
|
||||
// Ignore errors during logout cleanup
|
||||
}
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
|
||||
@ -1 +1 @@
|
||||
{"c936fedf9810ea51":{"id":"c936fedf9810ea51","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771433918233},"abad0e2fe8760303":{"id":"abad0e2fe8760303","user_id":0,"name":"swefheim","avatar_url":"","last_seen":1771433918373}}
|
||||
{"3356a3073b77f72d":{"id":"3356a3073b77f72d","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771441757997},"d58aa0268cc9e8d0":{"id":"d58aa0268cc9e8d0","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771441758062}}
|
||||
@ -316,8 +316,10 @@ if ($is_dm_view) {
|
||||
SELECT vs.channel_id, vs.user_id, u.username, u.display_name, u.avatar_url
|
||||
FROM voice_sessions vs
|
||||
JOIN users u ON vs.user_id = u.id
|
||||
WHERE vs.last_seen > ?
|
||||
");
|
||||
$stmt_vs->execute();
|
||||
$stale_db_time = (int) floor(microtime(true) * 1000) - 15000;
|
||||
$stmt_vs->execute([$stale_db_time]);
|
||||
$voice_sessions = $stmt_vs->fetchAll();
|
||||
$voice_users_by_channel = [];
|
||||
foreach($voice_sessions as $vs) {
|
||||
@ -553,7 +555,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<?php foreach($voice_users_by_channel[$c['id']] as $v_user): ?>
|
||||
<div class="voice-user small text-muted d-flex align-items-center mb-1" data-user-id="<?php echo $v_user['user_id']; ?>">
|
||||
<div class="message-avatar me-2" style="width: 16px; height: 16px; <?php echo $v_user['avatar_url'] ? "background-image: url('{$v_user['avatar_url']}');" : ""; ?>"></div>
|
||||
<span><?php echo htmlspecialchars($v_user['display_name'] ?? $v_user['username']); ?></span>
|
||||
<span style="font-size: 13px;"><?php echo htmlspecialchars($v_user['display_name'] ?? $v_user['username']); ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
@ -634,7 +636,8 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<a href="#" title="Settings" style="color: var(--text-muted); margin-right: 8px;" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
||||
</a>
|
||||
<a href="auth/logout.php" title="Logout" style="color: var(--text-muted);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a>
|
||||
<a href="auth/logout.php" onclick="if(window.voiceHandler) window.voiceHandler.leave(); sessionStorage.clear();" title="Logout" style="color: var(--text-muted);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 10px; font-size: 10px; color: #4e5058; border-top: 1px solid #1e1f22;">
|
||||
|
||||
19
requests.log
19
requests.log
@ -657,3 +657,22 @@
|
||||
2026-02-18 16:56:38 - GET /index.php - POST: []
|
||||
2026-02-18 16:57:15 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:57:23 - GET /index.php - POST: []
|
||||
2026-02-18 17:00:18 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 18:08:55 - GET /index.php - POST: []
|
||||
2026-02-18 18:09:11 - GET /index.php - POST: []
|
||||
2026-02-18 18:09:30 - GET /index.php - POST: []
|
||||
2026-02-18 18:09:31 - GET /index.php - POST: []
|
||||
2026-02-18 18:10:17 - GET /index.php - POST: []
|
||||
2026-02-18 18:16:02 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 18:24:35 - GET /index.php - POST: []
|
||||
2026-02-18 18:24:40 - GET /index.php - POST: []
|
||||
2026-02-18 18:24:50 - GET /index.php - POST: []
|
||||
2026-02-18 18:25:18 - GET /index.php - POST: []
|
||||
2026-02-18 18:25:21 - GET /index.php - POST: []
|
||||
2026-02-18 18:28:00 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 18:39:54 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 18:43:31 - GET / - POST: []
|
||||
2026-02-18 18:44:03 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 19:06:59 - GET /index.php - POST: []
|
||||
2026-02-18 19:07:22 - GET /index.php - POST: []
|
||||
2026-02-18 19:08:00 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user