From ae689d6c6cc302768a9e68e53f08f00120337d9f Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sun, 3 May 2026 16:42:21 -0400 Subject: [PATCH] updated --- server.py | 21 +++++++++++-- static/js/client.js | 74 ++++++++++++++++++++++++++++++++++++++++++++- static/js/media.js | 21 +++++++++++++ static/js/ui.js | 23 ++++++++++++-- static/js/webrtc.js | 14 ++++++++- static/style.css | 25 +++++++++++++++ 6 files changed, 170 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index 967b644..2d90d5c 100644 --- a/server.py +++ b/server.py @@ -45,6 +45,7 @@ DIAL_CODES = { '*000#': 'reset_all', # turns off every mode/effect for everyone '*73#': 'record_open', # opens the dialer's record popup (10s max) '*74#': 'play_recording', # broadcasts the dialer's last recording to everyone + '*87#': 'breakout_toggle', # toggles dialer in/out of the private breakout room '*#06#': 'show_codes', # privately reveals all dial codes to the dialer } @@ -59,6 +60,7 @@ DIAL_CODE_DESCRIPTIONS = [ ('*000#', 'Reset everything to normal (everyone)'), ('*73#', 'Record up to 10s of the chat (everyone you hear)'), ('*74#', 'Play your recording for everyone in the chat'), + ('*87#', 'Join/leave the breakout room (private side convo)'), ('*#06#', 'Show this list (just you)'), ] @@ -237,7 +239,7 @@ async def websocket_handler(request: web.Request) -> web.WebSocketResponse: await ws.prepare(request) client_id = str(uuid.uuid4())[:8] - clients[client_id] = {'ws': ws, 'username': None, 'cam_on': False, 'mic_on': True, 'screen_on': False, 'rainbow_nick': False, 'ghost': False, 'fed': False} + clients[client_id] = {'ws': ws, 'username': None, 'cam_on': False, 'mic_on': True, 'screen_on': False, 'rainbow_nick': False, 'ghost': False, 'fed': False, 'breakout': False} logging.info(f'[{client_id}] Connected') @@ -300,7 +302,7 @@ async def handle_message(client_id: str, data: dict): reconnect_tokens[reconnect_token] = {'username': username, 'expires': time.time() + 3600} users = [ - {'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False), 'ghost': c.get('ghost', False), 'fed': c.get('fed', False)} + {'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False), 'ghost': c.get('ghost', False), 'fed': c.get('fed', False), 'breakout': c.get('breakout', False)} for cid, c in clients.items() if c['username'] and cid != client_id ] @@ -366,7 +368,7 @@ async def handle_message(client_id: str, data: dict): reconnect_tokens[new_token] = {'username': username, 'expires': time.time() + 3600} users = [ - {'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False), 'ghost': c.get('ghost', False), 'fed': c.get('fed', False)} + {'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False), 'ghost': c.get('ghost', False), 'fed': c.get('fed', False), 'breakout': c.get('breakout', False)} for cid, c in clients.items() if c['username'] and cid != client_id ] @@ -508,8 +510,21 @@ async def handle_message(client_id: str, data: dict): for c in clients.values(): c['rainbow_nick'] = False c['ghost'] = False + c['breakout'] = False logging.info(f'[{client_id}] Reset all modes') await broadcast_all({'type': 'reset_all'}) + elif action == 'breakout_toggle': + # Per-user toggle. Audio gating is handled client-side: each client mutes + # the sender track + receiver audio for any peer whose breakout flag + # doesn't match their own. Server just tracks state and fans out. + current = clients[client_id].get('breakout', False) + clients[client_id]['breakout'] = not current + logging.info(f'[{client_id}] Breakout -> {not current}') + await broadcast_all({ + 'type' : 'breakout_status', + 'id' : client_id, + 'breakout' : not current + }) elif action == 'ghost_toggle': current = clients[client_id].get('ghost', False) clients[client_id]['ghost'] = not current diff --git a/static/js/client.js b/static/js/client.js index d13616c..09f7d49 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -362,7 +362,7 @@ function handleSignal(data) { // Preserve local state on reconnect, or initialize if (!state.users['local']) { - state.users['local'] = { username: state.username, camOn: state.camEnabled, micOn: state.micEnabled, screenOn: state.screenEnabled, rainbowNick: false, ghost: false, fed: false, speaking: false }; + state.users['local'] = { username: state.username, camOn: state.camEnabled, micOn: state.micEnabled, screenOn: state.screenEnabled, rainbowNick: false, ghost: false, fed: false, breakout: false, speaking: false }; } data.users.forEach(user => { @@ -374,6 +374,7 @@ function handleSignal(data) { rainbowNick: !!user.rainbow_nick, ghost: !!user.ghost, fed: !!user.fed, + breakout: !!user.breakout, speaking: false }; createPeerConnection(user.id, user.username, true); @@ -410,6 +411,7 @@ function handleSignal(data) { camOn: data.cam_on || false, micOn: data.mic_on !== false, screenOn: data.screen_on || false, + breakout: false, speaking: false }; console.log('[Signal] user_joined:', data.id, 'micOn:', data.mic_on, 'state:', state.users[data.id]); @@ -531,7 +533,9 @@ function handleSignal(data) { if (!u) return; u.rainbowNick = false; u.ghost = false; + u.breakout = false; }); + applyBreakoutGatingAll(); updateUsersList(); break; @@ -566,6 +570,10 @@ function handleSignal(data) { updateUsersList(); break; + case 'breakout_status': + handleBreakoutStatus(data.id, !!data.breakout); + break; + case 'nick_status': // Per-user rainbow nick toggle. Server tells us when ANY user (including // us) flips theirs - we just mirror it into local state and re-render. @@ -580,6 +588,70 @@ function handleSignal(data) { } } +// ========== BREAKOUT ROOM (*87#) ========== +// +// Per-user flag gates audio in both directions: if your breakout matches a peer's, +// you can hear each other. If not, both sides mute the relevant sender track and +//