Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {v4: uuidv4} = require('uuid');
const express = require('express');
const {ProximityChatService} = require('./proximity-chat-service');
const {MediasoupService} = require('./mediasoup-service');
const {ROOM_CONFIG} = require('./public/room/config');

const uws = require('uWebSockets.js');

Expand Down Expand Up @@ -45,20 +46,28 @@ httpServices.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});

const pcService = new ProximityChatService(socketServer);
const msService = new MediasoupService(socketServer);

const services = new Map(Object.keys(ROOM_CONFIG).map((room) => {
return [room, {
pcService: new ProximityChatService(socketServer, room),
msService: new MediasoupService(socketServer, room),
}];
}));

socketServer.ws('/*', {
open: async (ws) => {
ws.id = uuidv4();
ws.room = Object.keys(ROOM_CONFIG)[0];
},
message: async (...args) => {
await pcService.message(...args);
await msService.message(...args);
const ws = args[0];
await services.get(ws.room).pcService.message(...args);
await services.get(ws.room).msService.message(...args);
},
close: async (...args) => {
await pcService.close(...args);
await msService.close(...args);
const ws = args[0];
await services.get(ws.room).pcService.close(...args);
await services.get(ws.room).msService.close(...args);
},
});

Expand Down
55 changes: 32 additions & 23 deletions proximity-chat-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {ROOM_CONFIG} = require('./public/room/config');

// Calculate a starting position. Spread out players so joining players don't
// all start out overlapping each other.
function calculateStartPosition(nUsers) {
function calculateStartPosition(room, nUsers) {
// Approximate radius of player bubble plus some spacing.
const spreadOutDistance = 150;

Expand All @@ -11,15 +11,16 @@ function calculateStartPosition(nUsers) {
const offsetY = nUsers % 4 >= 2; // 3rd and 4th player are offset down.

return {
x: ROOM_CONFIG.starting_position.x + offsetX * spreadOutDistance,
y: ROOM_CONFIG.starting_position.y + offsetY * spreadOutDistance,
x: ROOM_CONFIG[room].starting_position.x + offsetX * spreadOutDistance,
y: ROOM_CONFIG[room].starting_position.y + offsetY * spreadOutDistance,
};
}

class ProximityChatService {
constructor(usocket) {
// track which users are connected
this.users = {};

// To broadcast, especially in close().
this.usocket = usocket;
}
Expand All @@ -32,27 +33,34 @@ class ProximityChatService {
return;
}

if (components[0] == 'room') {
if (components[1] !== '') {
ws.room = components[1];
}
return;
}

if (components[0] == 'connect') {
const id = ws.id;
const pos = calculateStartPosition(Object.keys(this.users).length);
const name = components[1];
const id = ws.id;
const pos = calculateStartPosition(ws.room, Object.keys(this.users).length);

console.log('user connected', id);
console.log('user connected', id, 'to room', ws.room);

// Tell user his or her id
ws.send(JSON.stringify({'id': id, 'pos': pos}));

// Tell the other users to connect to this user
this.usocket.publish('join', JSON.stringify({join: {id: id, name: name, pos: pos}}));
this.usocket.publish(`${ws.room}-join`, JSON.stringify({join: {id: id, name: name, pos: pos}}));

// Let this client listen to join, leave, and position broadcasts
ws.subscribe('join');
ws.subscribe('leave');
ws.subscribe('add');
ws.subscribe('remove');
ws.subscribe('drink');
ws.subscribe('position');
ws.subscribe('update');
ws.subscribe(`${ws.room}-join`);
ws.subscribe(`${ws.room}-leave`);
ws.subscribe(`${ws.room}-add`);
ws.subscribe(`${ws.room}-remove`);
ws.subscribe(`${ws.room}-drink`);
ws.subscribe(`${ws.room}-position`);
ws.subscribe(`${ws.room}-update`);

// ..and players info
ws.send(JSON.stringify({
Expand Down Expand Up @@ -81,7 +89,7 @@ class ProximityChatService {
objects: {},
};
user.emitPos = (objectId, x, y) => {
this.usocket.publish('position', String([id, objectId, x, y]));
this.usocket.publish(`${ws.room}-position`, String([id, objectId, x, y]));
};

this.users[id] = user;
Expand All @@ -98,7 +106,7 @@ class ProximityChatService {
user.audioEnabled = components[2] === 'true';
user.videoEnabled = components[3] === 'true';
user.broadcast = components[4] === 'true';
this.usocket.publish('update', JSON.stringify({
this.usocket.publish(`${ws.room}-update`, JSON.stringify({
update: {
id: user.id,
name: user.name,
Expand All @@ -116,7 +124,7 @@ class ProximityChatService {
pos: {x: user.pos.x, y: user.pos.y},
};
user.objects[objectId] = object;
this.usocket.publish('add', JSON.stringify({
this.usocket.publish(`${ws.room}-add`, JSON.stringify({
add: {
id: user.id,
objectId: objectId,
Expand All @@ -127,7 +135,7 @@ class ProximityChatService {

if (components[0] == 'remove') {
const objectId = components[1];
this.usocket.publish('remove', JSON.stringify({
this.usocket.publish(`${ws.room}-remove`, JSON.stringify({
remove: {
id: user.id,
objectId: objectId,
Expand All @@ -136,12 +144,13 @@ class ProximityChatService {
delete user.objects[objectId];
}

if (components[0] == 'drink' && 'drinks' in ROOM_CONFIG) {
const dist = Math.hypot(ROOM_CONFIG.drinks.y - user.pos.y, ROOM_CONFIG.drinks.x - user.pos.x);
if (dist <= ROOM_CONFIG.drinks.range) {
const config = ROOM_CONFIG[ws.room];
if (components[0] == 'drink' && 'drinks' in config) {
const dist = Math.hypot(config.drinks.y - user.pos.y, config.drinks.x - user.pos.x);
if (dist <= config.drinks.range) {
const drinkId = components[1];
user.drink = {id: drinkId, time: Date.now()};
this.usocket.publish('drink', JSON.stringify({
this.usocket.publish(`${ws.room}-drink`, JSON.stringify({
drink: {
id: user.id,
drinkId: user.drink.id,
Expand Down Expand Up @@ -179,7 +188,7 @@ class ProximityChatService {
console.log('user disconnected', user.id);

// let other users know to disconnect this client
this.usocket.publish('leave', JSON.stringify({leave: {id: user.id}}));
this.usocket.publish(`${ws.room}-leave`, JSON.stringify({leave: {id: user.id}}));

// remove the user from the users list
delete this.users[user.id];
Expand Down
2 changes: 2 additions & 0 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export class App {
this.socket = new Socket(`wss://${location.hostname}:9001`);
this.socket.onmessage = async (...args) => await this.handleMessage(...args);
this.socket.onopen = (_) => {
this.socket.send(['room', localStorage.getItem('room')]);

this.mediasoupClient = new MediasoupClient(this.socket);
this.mediasoupClient.init().then((_) => {
navigator.mediaDevices.enumerateDevices().then(window.gotDevices);
Expand Down
12 changes: 12 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<script src="/public/vendor/panzoom.min.js"></script>

<script src="/public/room/config.js"></script>
<script>
document.ROOMS = Object.keys(document.ROOM_CONFIG);
document.ROOM_CONFIG = document.ROOM_CONFIG[localStorage.getItem('room') || Object.keys(document.ROOM_CONFIG)[0]];
</script>
</head>
<body>
<div id="viewport">
Expand All @@ -30,6 +34,14 @@
</div>
</div>

<aside class="d-flex flex-column p-3">
<label>Rooms</label>
<ul id="rooms" class="nav nav-pills flex-column mb-auto">
</ul>

<div class="toggle"></div>
</aside>

<nav>
<ul>
<li><button class="settings"><i class="material-icons">settings</i></button></li>
Expand Down
26 changes: 26 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ if (localStorage.getItem('name') == null) {

const $viewport = document.querySelector('#viewport');
const $bg = document.querySelector('#background');
$bg.style.backgroundImage = `url('/public/room/${document.ROOM_CONFIG.background}')`;
const pz = panzoom($bg, {
zoomSpeed: 0.25,
bounds: true,
Expand Down Expand Up @@ -94,6 +95,7 @@ const updateTooltip = (_) => {
pz.on('pan', updateTooltip);
pz.on('zoom', updateTooltip);

// Drinks menu
const isTouchDevice = 'ontouchstart' in document.documentElement;
if ('drinks' in document.ROOM_CONFIG) {
const $menu = document.querySelector('.radial-menu');
Expand Down Expand Up @@ -131,6 +133,30 @@ if ('drinks' in document.ROOM_CONFIG) {
});
}

// Side menu
const $menu = document.querySelector('body > aside');
$menu.querySelector('.toggle').onclick = (e) => {
e.preventDefault();
$menu.classList.toggle('show');
}
for(const room of document.ROOMS) {
const $item = document.createElement('li');
$item.classList.add('nav-item');

const $link = document.createElement('a');
$link.href = '#';
$link.onclick = (e) => {
localStorage.setItem('room', room);
document.location.reload();
};
$link.classList.add('nav-link');
if (room == localStorage.getItem('room')) $link.classList.add('active');
$link.innerHTML = room;

$item.appendChild($link);
document.querySelector('#rooms').appendChild($item);
}


// Settings
let micEnabled = true;
Expand Down
49 changes: 32 additions & 17 deletions public/room/config.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
(function(exports) {
exports.ROOM_CONFIG = {
// The size of the room background image. These must be the dimensions of
// room.png.
width: 3200,
height: 1800,
'Room 1': {
// The size of the room background image. These must be the dimensions of
// room.png.
width: 3200,
height: 1800,
background: 'room.png',

// The position at which new players enter the map. They may be offset by a
// small amount from this location so they don't overlap each other.
starting_position: {
x: 100,
y: 100,
},
// The position at which new players enter the map. They may be offset by a
// small amount from this location so they don't overlap each other.
starting_position: {
x: 100,
y: 100,
},

// Optional key to show a menu at the specified location where users can
// obtain a drink icon.
drinks: {
x: 1785,
y: 280,
range: 400,
duration: 1000 * 60 * 10, // in ms
// Optional key to show a menu at the specified location where users can
// obtain a drink icon.
drinks: {
x: 1785,
y: 280,
range: 400,
duration: 1000 * 60 * 10, // in ms
},
},
'Room 2': {
width: 3200,
height: 1800,
background: 'room.png',
starting_position: {x: 100, y: 100},
drinks: {
x: 1785,
y: 280,
range: 400,
duration: 1000 * 60 * 10,
},
},
};
})(typeof exports === 'undefined'? document: exports);
Loading