Skip to content
HyroVitalyProtago edited this page Apr 1, 2022 · 8 revisions

How it should work

  • Peers are discovered through a signalling server and connected through WebRTC.
  • A contract is established between them as a list of slots. This way, only listened messages are sent between them.
  • The contract evolves during the connection when listeners are added/removed through on/off calls.
  • Messages are initially serialized in JSON, but I recommend using flatbuffers or at least flexbuffers.
  • A default signalling server metahack-meet.glitch.me enables everyone to meet as a meta-multiplayer layer, but you can just remix it to create a meeting space for your app. For now, rooms (isolate groups inside a meeting space) aren't supported.

Web

Setup

By default, there is nothing to do, but you can configure the library by calling the init function before anything else from the lib

// signalling server address
mh.init('metahack-meet.glitch.me');


// TODO full config
mh.init({
  server: 'metahack-meet.glitch.me',
  network: 'webrtc', // websocket, ...
  serializer: 'json', // flatbuffers, ...
  options: {
    'iceServers': [
      {"urls":"stun:stun.l.google.com:19302"},
      {"urls":"stun:stun1.l.google.com:19302"},
      {"urls":"stun:stun2.l.google.com:19302"},
      {"urls":"stun:stun3.l.google.com:19302"},
    ]
  }
});

Signals/Slots

// default way to use the lib is by sending json signals
// object.evt define the signal name connected to a slot with the same name
// userId is optional, broadcast when not used
// mh.send(object, userId?:int);
mh.jsend('avatar', { position: {x:0, y:0, z:0} }); // elegant way
mh.jsend({ evt:'avatar', position: {x:0, y:0, z:0} }); // raw way

// you can also send raw values
mh.send('image', rawData, userId);

// how to receive data
// define slot to receive signal
mh.on('avatar', (data, userId) => {
  // data.position ...
});

Unity

[Serializable]
class Avatar {
  public Vector3 position;
}

Avatar avatar = new Avatar();
MH.JSend('avatar', avatar); // MH equivalent of MetaHack.Singleton

MH.On('avatar', (json, userId) => {
  Avatar avatar = JsonConvert.DeserializeObject<Avatar>(json);
  // avatar.position
});

public class MetaCube : MetaBehaviour {
  protected override void OnReady(int userId) {
    Debug.Log($"OnReady {userId}");
    JSend("{\"evt\":\"test\", \"str\":\"hello\"}", userId);
  }

  protected override void OnQuit(int userId) {
    Debug.Log($"OnQuit {userId}");
    gameObject.GetComponent<Renderer>().material.color = Color.white;
  }

  [On("test")]
  void OnTest(JObject obj, int userId) {
    Debug.Log($"receive hello from {userId}");
    gameObject.GetComponent<Renderer>().material.color = Color.red;
  }
}

WebXR export

TODO

Signalling server

TODO: Federated signalling servers

Here is the default node signalling server hosted on phone-tracker.glitch.me. You can create your own just by remixing the glitch app (equivalent to a fork in git).

const http = require("http");
const express = require("express");
const ws = require('ws')
//const cors = require('cors');

const port = process.env.PORT || 8080;
const app = express();

// force http because of websocket for the leap motion, but need to try some little things...
function forceHttp(req, res, next) {
  if (req.get('X-Forwarded-Proto').indexOf("https")!=-1) {
    res.redirect('http://' + req.hostname + req.url);
  } else {
    return next()
  }
}
app.all('*', forceHttp);
//app.use(cors({origin: '*'}));
app.use(express.static('public'));

const webServer = http.createServer(app).listen(port);

let ID = 0;
const clients = [];

// WebRTC signalling server
// mesh configuration (*<->*)
const wrtc = new ws.Server({server: webServer, path: "/wrtc"});
wrtc.on('connection', ws => {
  
  const userID = ID++;
  clients[userID] = ws;
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(clients));
  
  //ws.send(JSON.stringify({userID:userID}));
  
  // by default, send a message to all already existing users
  clients.forEach((c,id) => { // broadcast message over clients
    if (id !== userID) c.send(JSON.stringify({ newConnection:userID }));
  });
  
  ws.on('message', message => {
    const msg = JSON.parse(message);
    msg.from = userID;
    if (msg.to && clients[msg.to])
      clients[msg.to].send(JSON.stringify(msg));
  })
  
  ws.on('close', function () {
    delete clients[userID];
    console.log('deleted: ' + userID);
  })
})

// TODO add a TURN server to support WebRTC over more distant networks
// https://github.com/Atlantis-Software/node-turn
// note: https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
// note on security: https://tools.ietf.org/html/draft-uberti-rtcweb-turn-rest-00
Clone this wiki locally