Skip to content

Instantly share code, notes, and snippets.

@Hri7566
Last active March 22, 2021 02:49
Show Gist options
  • Select an option

  • Save Hri7566/bd72610fdfbb5aae4d1acd441ec52d26 to your computer and use it in GitHub Desktop.

Select an option

Save Hri7566/bd72610fdfbb5aae4d1acd441ec52d26 to your computer and use it in GitHub Desktop.
MPP TypeScript Client for Deno
import { WebSocketClient, StandardWebSocketClient } from "https://deno.land/x/[email protected]/mod.ts";
import { EventEmitter } from "https://deno.land/[email protected]/node/events.ts";
export default class Client extends EventEmitter {
uri: string;
ws: WebSocketClient | any;
serverTimeOffset: number;
user: any;
participantId: any;
channel: any;
ppl: any;
connectionTime: any;
connectionAttempts: any;
desiredChannelId: any;
desiredChannelSettings: any;
pingInterval: any;
canConnect: boolean;
noteBuffer: any;
noteBufferTime: any;
noteFlushInterval: any;
['๐Ÿˆ']: any;
offlineParticipant: any;
offlineChannelSettings: any;
constructor (uri: string) {
super();
this.uri = uri;
this.ws = undefined;
this.serverTimeOffset = 0;
this.user = undefined;
this.participantId = undefined;
this.channel = undefined;
this.ppl = {};
this.connectionTime = undefined;
this.connectionAttempts = 0;
this.desiredChannelId = undefined;
this.desiredChannelSettings = undefined;
this.pingInterval = undefined;
this.canConnect = false;
this.noteBuffer = [];
this.noteBufferTime = 0;
this.noteFlushInterval = undefined;
this['๐Ÿˆ'] = 0;
this.offlineParticipant = {
_id: "",
name: "",
color: "#777"
};
this.offlineChannelSettings = {
color:"#ecfaed"
};
this.bindEventListeners();
this.emit("status", "(Offline mode)");
}
bindEventListeners() {
var self = this;
this.on("hi", function(msg) {
self.user = msg.u;
self.receiveServerTime(msg.t, msg.e || undefined);
if(self.desiredChannelId) {
self.setChannel();
}
});
this.on("t", function(msg) {
self.receiveServerTime(msg.t, msg.e || undefined);
});
this.on("ch", function(msg) {
self.desiredChannelId = msg.ch._id;
self.desiredChannelSettings = msg.ch.settings;
self.channel = msg.ch;
if(msg.p) self.participantId = msg.p;
self.setParticipants(msg.ppl);
});
this.on("p", function(msg) {
self.participantUpdate(msg);
self.emit("participant update", self.findParticipantById(msg.id));
});
this.on("m", function(msg) {
if(self.ppl.hasOwnProperty(msg.id)) {
self.participantUpdate(msg);
}
});
this.on("bye", function(msg) {
self.removeParticipant(msg.p);
});
}
receiveServerTime(time: any, echo: any) {
var self = this;
var now = Date.now();
var target = time - now;
//console.log("Target serverTimeOffset: " + target);
var duration = 1000;
var step = 0;
var steps = 50;
var step_ms = duration / steps;
var difference = target - this.serverTimeOffset;
var inc = difference / steps;
var iv: any;
iv = setInterval(function() {
self.serverTimeOffset += inc;
if(++step >= steps) {
clearInterval(iv);
//console.log("serverTimeOffset reached: " + self.serverTimeOffset);
self.serverTimeOffset=target;
}
}, step_ms);
// smoothen
//this.serverTimeOffset = time - now; // mostly time zone offset ... also the lags so todo smoothen this
// not smooth:
//if(echo) this.serverTimeOffset += echo - now; // mostly round trip time offset
}
setChannel(id?: string, set?: any) {
this.desiredChannelId = id || this.desiredChannelId || "lobby";
this.desiredChannelSettings = set || this.desiredChannelSettings || undefined;
this.sendArray([{m: "ch", _id: this.desiredChannelId, set: this.desiredChannelSettings}]);
}
sendArray(arr: any) {
this.send(JSON.stringify(arr));
}
send(raw: string) {
if(this.isConnected()) this.ws.webSocket.send(raw);
}
isConnected() {
return this.isSupported() && this.ws && this.ws.webSocket.readyState === 1;
}
isConnecting() {
return this.isSupported() && this.ws && this.ws.webSocket.readyState === 0;
}
isSupported() {
return typeof WebSocket === "function";
}
start() {
this.canConnect = true;
this.connect();
}
stop() {
this.canConnect = false;
this.ws.webSocket.close();
}
connect() {
if(!this.canConnect || !this.isSupported() || this.isConnected() || this.isConnecting()) return;
this.emit("status", "Connecting...");
this.ws = new StandardWebSocketClient(this.uri);
var self = this;
this.ws.webSocket.addEventListener("close", function(evt: any) {
self.user = undefined;
self.participantId = undefined;
self.channel = undefined;
self.setParticipants([]);
clearInterval(self.pingInterval);
clearInterval(self.noteFlushInterval);
self.emit("disconnect", evt);
self.emit("status", "Offline mode");
// reconnect!
if(self.connectionTime) {
self.connectionTime = undefined;
self.connectionAttempts = 0;
} else {
++self.connectionAttempts;
}
var ms_lut = [50, 2950, 7000, 10000];
var idx = self.connectionAttempts;
if(idx >= ms_lut.length) idx = ms_lut.length - 1;
var ms = ms_lut[idx];
setTimeout(self.connect.bind(self), ms);
});
this.ws.webSocket.addEventListener("error", function(err: any) {
self.emit("wserror", err);
self.ws.webSocket.close(); // self.ws.emit("close");
});
this.ws.webSocket.addEventListener("open", function(evt: any) {
self.connectionTime = Date.now();
self.sendArray([{"m": "hi", "๐Ÿˆ": self['๐Ÿˆ']++ || undefined }]);
self.pingInterval = setInterval(function() {
self.sendArray([{m: "t", e: Date.now()}]);
}, 20000);
//self.sendArray([{m: "t", e: Date.now()}]);
self.noteBuffer = [];
self.noteBufferTime = 0;
self.noteFlushInterval = setInterval(function() {
if(self.noteBufferTime && self.noteBuffer.length > 0) {
self.sendArray([{m: "n", t: self.noteBufferTime + self.serverTimeOffset, n: self.noteBuffer}]);
self.noteBufferTime = 0;
self.noteBuffer = [];
}
}, 200);
self.emit("connect");
self.emit("status", "Joining channel...");
});
this.ws.webSocket.addEventListener("message", function(evt: any) {
var transmission = JSON.parse(evt.data);
for(var i = 0; i < transmission.length; i++) {
var msg = transmission[i];
self.emit(msg.m, msg);
}
});
}
setParticipants(ppl: any) {
for(var id in this.ppl) {
if(!this.ppl.hasOwnProperty(id)) continue;
var found = false;
for(var j = 0; j < ppl.length; j++) {
if(ppl[j].id === id) {
found = true;
break;
}
}
if(!found) {
this.removeParticipant(id);
}
}
// update all
for(var i = 0; i < ppl.length; i++) {
this.participantUpdate(ppl[i]);
}
}
participantUpdate(update: any) {
var part = this.ppl[update.id] || null;
if(part === null) {
part = update;
this.ppl[part.id] = part;
this.emit("participant added", part);
this.emit("count", this.countParticipants());
} else {
if(update.x) part.x = update.x;
if(update.y) part.y = update.y;
if(update.color) part.color = update.color;
if(update.name) part.name = update.name;
}
}
findParticipantById(id: string) {
return this.ppl[id] || this.offlineParticipant;
}
removeParticipant(id: string) {
if(this.ppl.hasOwnProperty(id)) {
var part = this.ppl[id];
delete this.ppl[id];
this.emit("participant removed", part);
this.emit("count", this.countParticipants());
}
}
countParticipants() {
var count = 0;
for(var i in this.ppl) {
if(this.ppl.hasOwnProperty(i)) ++count;
}
return count;
}
getChannelSetting(key: any) {
if(!this.isConnected() || !this.channel || !this.channel.settings) {
return this.offlineChannelSettings[key];
}
return this.channel.settings[key];
}
setChannelSettings(settings: any) {
if(!this.isConnected() || !this.channel || !this.channel.settings) {
return;
}
if(this.desiredChannelSettings){
for(var key in settings) {
this.desiredChannelSettings[key] = settings[key];
}
this.sendArray([{m: "chset", set: this.desiredChannelSettings}]);
}
}
getOwnParticipant() {
return this.findParticipantById(this.participantId);
}
isOwner() {
return this.channel && this.channel.crown && this.channel.crown.participantId === this.participantId;
}
preventsPlaying() {
return this.isConnected() && !this.isOwner() && this.getChannelSetting("crownsolo") === true;
}
startNote(note: any, v: number) {
if(this.isConnected()) {
var vel: any = typeof v === "undefined" ? undefined : +v.toFixed(3);
if(!this.noteBufferTime) {
this.noteBufferTime = Date.now();
this.noteBuffer.push({n: note, v: vel});
} else {
this.noteBuffer.push({d: Date.now() - this.noteBufferTime, n: note, v: vel});
}
}
}
stopNote(note: any) {
if(this.isConnected()) {
if(!this.noteBufferTime) {
this.noteBufferTime = Date.now();
this.noteBuffer.push({n: note, s: 1});
} else {
this.noteBuffer.push({d: Date.now() - this.noteBufferTime, n: note, s: 1});
}
}
}
}
export { Client };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment