Compare commits
17 Commits
templating
...
dev2
Author | SHA1 | Date |
---|---|---|
Hri7566 | 87352df5b2 | |
Hri7566 | ef70175403 | |
Hri7566 | fb17f616a7 | |
Hri7566 | 253ef651c6 | |
Hri7566 | d76069109b | |
Hri7566 | b65bf920be | |
Hri7566 | bb4454345d | |
Hri7566 | ccc02f653a | |
Hri7566 | 2c50e7ae5d | |
Hri7566 | cb72f5ac7b | |
Hri7566 | 911b907f85 | |
Hri7566 | c7344e1bd9 | |
Hri7566 | c15dee967b | |
Hri7566 | af660e9834 | |
Hri7566 | f5b708aceb | |
Hri7566 | 9181370a82 | |
Hri7566 | 9b28ae985c |
|
@ -1,3 +1,4 @@
|
||||||
[submodule "sounds"]
|
[submodule "sounds"]
|
||||||
path = sounds
|
path = sounds
|
||||||
url = git@git.hri7566.info:Hri7566/piano-sounds-dev
|
url = git@github.com:Hri7566/piano-sounds
|
||||||
|
branch = master
|
||||||
|
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 4.6 KiB |
758
Client.js
|
@ -1,335 +1,429 @@
|
||||||
|
WebSocket.prototype.send = new Proxy(WebSocket.prototype.send, {
|
||||||
|
apply: (target, thisArg, args) => {
|
||||||
|
if (localStorage.token && !args[0].startsWith(`[{"m":"hi"`))
|
||||||
|
args[0] = args[0].replace(localStorage.token, "[REDACTED]");
|
||||||
|
return target.apply(thisArg, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if(typeof module !== "undefined") {
|
class Client extends EventEmitter {
|
||||||
module.exports = Client;
|
constructor(uri) {
|
||||||
WebSocket = require("ws");
|
super();
|
||||||
EventEmitter = require("events").EventEmitter;
|
|
||||||
} else {
|
this.uri = uri;
|
||||||
this.Client = Client;
|
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.permissions = {};
|
||||||
|
this["🐈"] = 0;
|
||||||
|
this.loginInfo = undefined;
|
||||||
|
|
||||||
|
this.bindEventListeners();
|
||||||
|
|
||||||
|
this.emit("status", "(Offline mode)");
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return typeof WebSocket === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected() {
|
||||||
|
return (
|
||||||
|
this.isSupported() &&
|
||||||
|
this.ws &&
|
||||||
|
this.ws.readyState === WebSocket.OPEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnecting() {
|
||||||
|
return (
|
||||||
|
this.isSupported() &&
|
||||||
|
this.ws &&
|
||||||
|
this.ws.readyState === WebSocket.CONNECTING
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(enableTokens = true, enableChallenge = true) {
|
||||||
|
this.canConnect = true;
|
||||||
|
if (!this.connectionTime) {
|
||||||
|
this.connect(enableTokens, enableChallenge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.canConnect = false;
|
||||||
|
this.ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(enableTokens, enableChallenge) {
|
||||||
|
if (
|
||||||
|
!this.canConnect ||
|
||||||
|
!this.isSupported() ||
|
||||||
|
this.isConnected() ||
|
||||||
|
this.isConnecting()
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
this.emit("status", "Connecting...");
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
// nodejsicle
|
||||||
|
this.ws = new WebSocket(this.uri, {
|
||||||
|
origin: "https://www.multiplayerpiano.com"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// browseroni
|
||||||
|
this.ws = new WebSocket(this.uri);
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
this.ws.addEventListener("close", function (evt) {
|
||||||
|
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, 2500, 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.addEventListener("error", function (err) {
|
||||||
|
self.emit("wserror", err);
|
||||||
|
self.ws.close(); // self.ws.emit("close");
|
||||||
|
});
|
||||||
|
this.ws.addEventListener("open", function (evt) {
|
||||||
|
self.pingInterval = setInterval(function () {
|
||||||
|
self.sendPing();
|
||||||
|
}, 20000);
|
||||||
|
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...");
|
||||||
|
|
||||||
|
if (!enableChallenge) {
|
||||||
|
const token = localStorage.token;
|
||||||
|
var hiMsg = { m: "hi", token };
|
||||||
|
self.sendArray([hiMsg]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.ws.addEventListener("message", async function (evt) {
|
||||||
|
var transmission = JSON.parse(evt.data);
|
||||||
|
for (var i = 0; i < transmission.length; i++) {
|
||||||
|
var msg = transmission[i];
|
||||||
|
self.emit(msg.m, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEventListeners() {
|
||||||
|
var self = this;
|
||||||
|
this.on("hi", function (msg) {
|
||||||
|
self.connectionTime = Date.now();
|
||||||
|
self.user = msg.u;
|
||||||
|
self.receiveServerTime(msg.t, msg.e || undefined);
|
||||||
|
if (self.desiredChannelId) {
|
||||||
|
self.setChannel();
|
||||||
|
}
|
||||||
|
if (msg.token) localStorage.token = msg.token;
|
||||||
|
if (msg.permissions) {
|
||||||
|
self.permissions = msg.permissions;
|
||||||
|
} else {
|
||||||
|
self.permissions = {};
|
||||||
|
}
|
||||||
|
if (msg.accountInfo) {
|
||||||
|
self.accountInfo = msg.accountInfo;
|
||||||
|
} else {
|
||||||
|
self.accountInfo = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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.participantMoveMouse(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.on("bye", function (msg) {
|
||||||
|
self.removeParticipant(msg.p);
|
||||||
|
});
|
||||||
|
this.on("b", function (msg) {
|
||||||
|
var hiMsg = { m: "hi" };
|
||||||
|
hiMsg["🐈"] = self["🐈"]++ || undefined;
|
||||||
|
if (this.loginInfo) hiMsg.login = this.loginInfo;
|
||||||
|
this.loginInfo = undefined;
|
||||||
|
console.log(msg);
|
||||||
|
if (msg.code) {
|
||||||
|
try {
|
||||||
|
if (msg.code.startsWith("~")) {
|
||||||
|
hiMsg.code = Function(msg.code.substring(1))();
|
||||||
|
} else {
|
||||||
|
hiMsg.code = Function(msg.code)();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
hiMsg.code = "broken";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (localStorage.token) {
|
||||||
|
hiMsg.token = localStorage.token;
|
||||||
|
}
|
||||||
|
self.sendArray([hiMsg]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(raw) {
|
||||||
|
if (this.isConnected()) this.ws.send(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendArray(arr) {
|
||||||
|
this.send(JSON.stringify(arr));
|
||||||
|
}
|
||||||
|
|
||||||
|
setChannel(id, set) {
|
||||||
|
this.desiredChannelId = id || this.desiredChannelId || "lobby";
|
||||||
|
this.desiredChannelSettings =
|
||||||
|
set || this.desiredChannelSettings || undefined;
|
||||||
|
this.sendArray([
|
||||||
|
{
|
||||||
|
m: "ch",
|
||||||
|
_id: this.desiredChannelId,
|
||||||
|
set: this.desiredChannelSettings
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
offlineChannelSettings = {
|
||||||
|
color: "#ecfaed"
|
||||||
|
};
|
||||||
|
|
||||||
|
getChannelSetting(key) {
|
||||||
|
if (!this.isConnected() || !this.channel || !this.channel.settings) {
|
||||||
|
return this.offlineChannelSettings[key];
|
||||||
|
}
|
||||||
|
return this.channel.settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
setChannelSettings(settings) {
|
||||||
|
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 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offlineParticipant = {
|
||||||
|
_id: "",
|
||||||
|
name: "",
|
||||||
|
color: "#777"
|
||||||
|
};
|
||||||
|
|
||||||
|
getOwnParticipant() {
|
||||||
|
return this.findParticipantById(this.participantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setParticipants(ppl) {
|
||||||
|
// remove participants who left
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
countParticipants() {
|
||||||
|
var count = 0;
|
||||||
|
for (var i in this.ppl) {
|
||||||
|
if (this.ppl.hasOwnProperty(i)) ++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
participantUpdate(update) {
|
||||||
|
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 {
|
||||||
|
Object.keys(update).forEach(key => {
|
||||||
|
part[key] = update[key];
|
||||||
|
});
|
||||||
|
if (!update.tag) delete part.tag;
|
||||||
|
if (!update.vanished) delete part.vanished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
participantMoveMouse(update) {
|
||||||
|
var part = this.ppl[update.id] || null;
|
||||||
|
if (part !== null) {
|
||||||
|
part.x = update.x;
|
||||||
|
part.y = update.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeParticipant(id) {
|
||||||
|
if (this.ppl.hasOwnProperty(id)) {
|
||||||
|
var part = this.ppl[id];
|
||||||
|
delete this.ppl[id];
|
||||||
|
this.emit("participant removed", part);
|
||||||
|
this.emit("count", this.countParticipants());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findParticipantById(id) {
|
||||||
|
return this.ppl[id] || this.offlineParticipant;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwner() {
|
||||||
|
return (
|
||||||
|
this.channel &&
|
||||||
|
this.channel.crown &&
|
||||||
|
this.channel.crown.participantId === this.participantId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
preventsPlaying() {
|
||||||
|
return (
|
||||||
|
this.isConnected() &&
|
||||||
|
!this.isOwner() &&
|
||||||
|
this.getChannelSetting("crownsolo") === true &&
|
||||||
|
!this.permissions.playNotesAnywhere
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveServerTime(time, echo) {
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
startNote(note, vel) {
|
||||||
|
if (typeof note !== "string") return;
|
||||||
|
if (this.isConnected()) {
|
||||||
|
var vel = typeof vel === "undefined" ? undefined : +vel.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) {
|
||||||
|
if (typeof note !== "string") return;
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPing() {
|
||||||
|
var msg = { m: "t", e: Date.now() };
|
||||||
|
this.sendArray([msg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoginInfo(loginInfo) {
|
||||||
|
this.loginInfo = loginInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.Client = Client;
|
||||||
function mixin(obj1, obj2) {
|
|
||||||
for(var i in obj2) {
|
|
||||||
if(obj2.hasOwnProperty(i)) {
|
|
||||||
obj1[i] = obj2[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function Client(uri) {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
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.bindEventListeners();
|
|
||||||
|
|
||||||
this.emit("status", "(Offline mode)");
|
|
||||||
};
|
|
||||||
|
|
||||||
mixin(Client.prototype, EventEmitter.prototype);
|
|
||||||
|
|
||||||
Client.prototype.constructor = Client;
|
|
||||||
|
|
||||||
Client.prototype.isSupported = function() {
|
|
||||||
return typeof WebSocket === "function";
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.isConnected = function() {
|
|
||||||
return this.isSupported() && this.ws && this.ws.readyState === WebSocket.OPEN;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.isConnecting = function() {
|
|
||||||
return this.isSupported() && this.ws && this.ws.readyState === WebSocket.CONNECTING;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.start = function() {
|
|
||||||
this.canConnect = true;
|
|
||||||
this.connect();
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.stop = function() {
|
|
||||||
this.canConnect = false;
|
|
||||||
this.ws.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.connect = function() {
|
|
||||||
if(!this.canConnect || !this.isSupported() || this.isConnected() || this.isConnecting())
|
|
||||||
return;
|
|
||||||
this.emit("status", "Connecting...");
|
|
||||||
if(typeof module !== "undefined") {
|
|
||||||
// nodejsicle
|
|
||||||
this.ws = new WebSocket(this.uri, {
|
|
||||||
origin: "https://www.multiplayerpiano.com"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// browseroni
|
|
||||||
this.ws = new WebSocket(this.uri);
|
|
||||||
}
|
|
||||||
var self = this;
|
|
||||||
this.ws.addEventListener("close", function(evt) {
|
|
||||||
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.addEventListener("error", function(err) {
|
|
||||||
self.emit("wserror", err);
|
|
||||||
self.ws.close(); // self.ws.emit("close");
|
|
||||||
});
|
|
||||||
this.ws.addEventListener("open", function(evt) {
|
|
||||||
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.addEventListener("message", function(evt) {
|
|
||||||
var transmission = JSON.parse(evt.data);
|
|
||||||
for(var i = 0; i < transmission.length; i++) {
|
|
||||||
var msg = transmission[i];
|
|
||||||
self.emit(msg.m, msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.bindEventListeners = function() {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.send = function(raw) {
|
|
||||||
if(this.isConnected()) this.ws.send(raw);
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.sendArray = function(arr) {
|
|
||||||
this.send(JSON.stringify(arr));
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.setChannel = function(id, set) {
|
|
||||||
this.desiredChannelId = id || this.desiredChannelId || "lobby";
|
|
||||||
this.desiredChannelSettings = set || this.desiredChannelSettings || undefined;
|
|
||||||
this.sendArray([{m: "ch", _id: this.desiredChannelId, set: this.desiredChannelSettings}]);
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.offlineChannelSettings = {
|
|
||||||
color:"#ecfaed"
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.getChannelSetting = function(key) {
|
|
||||||
if(!this.isConnected() || !this.channel || !this.channel.settings) {
|
|
||||||
return this.offlineChannelSettings[key];
|
|
||||||
}
|
|
||||||
return this.channel.settings[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.setChannelSettings = function(settings) {
|
|
||||||
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}]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.offlineParticipant = {
|
|
||||||
_id: "",
|
|
||||||
name: "",
|
|
||||||
color: "#777"
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.getOwnParticipant = function() {
|
|
||||||
return this.findParticipantById(this.participantId);
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.setParticipants = function(ppl) {
|
|
||||||
// remove participants who left
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.countParticipants = function() {
|
|
||||||
var count = 0;
|
|
||||||
for(var i in this.ppl) {
|
|
||||||
if(this.ppl.hasOwnProperty(i)) ++count;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.participantUpdate = function(update) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.removeParticipant = function(id) {
|
|
||||||
if(this.ppl.hasOwnProperty(id)) {
|
|
||||||
var part = this.ppl[id];
|
|
||||||
delete this.ppl[id];
|
|
||||||
this.emit("participant removed", part);
|
|
||||||
this.emit("count", this.countParticipants());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.findParticipantById = function(id) {
|
|
||||||
return this.ppl[id] || this.offlineParticipant;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.isOwner = function() {
|
|
||||||
return this.channel && this.channel.crown && this.channel.crown.participantId === this.participantId;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.preventsPlaying = function() {
|
|
||||||
return this.isConnected() && !this.isOwner() && this.getChannelSetting("crownsolo") === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.receiveServerTime = function(time, echo) {
|
|
||||||
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;
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.startNote = function(note, vel) {
|
|
||||||
if(this.isConnected()) {
|
|
||||||
var vel = typeof vel === "undefined" ? undefined : +vel.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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.stopNote = function(note) {
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 220 B |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
376
index.html
|
@ -1,107 +1,295 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||||
<title>Multiplayer Piano</title>
|
<title>Multiplayer Piano</title>
|
||||||
<meta name="description" content="An online piano you can play alone or with others in real-time. MIDI support, 88 keys, velocity sensitive. You can show off your skill or chat while listening to others play."/>
|
<meta
|
||||||
<link rel="stylesheet" href="/screen.css"/>
|
name="description"
|
||||||
</head>
|
content="An online piano you can play alone or with others in real-time. MIDI support, 88 keys, velocity sensitive. You can show off your skill or chat while listening to others play."
|
||||||
<body>
|
/>
|
||||||
|
<link rel="stylesheet" href="/screen.css" />
|
||||||
|
{% if usersConfig.enableTags %}
|
||||||
|
<link rel="stylesheet" href="/tags.css" />
|
||||||
|
{% endif %} {% if config.topButtons == "mppnet" %}
|
||||||
|
<link rel="stylesheet" href="/top-button.css" />
|
||||||
|
{% endif %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if config.topButtons == "original" %}
|
||||||
|
<div id="social">
|
||||||
|
<div id="more-button"></div>
|
||||||
|
</div>
|
||||||
|
{% elif config.topButtons == "mppnet" %}
|
||||||
|
<a
|
||||||
|
href="https://mpp.community/"
|
||||||
|
title="MPPNet Community Forum"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mppcommunity-button icon-button top-button"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<img src="/mppcommunity.ico" style="vertical-align: middle" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/mppnet/frontend"
|
||||||
|
title="MPPNet Frontend Repo"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="github-button icon-button top-button"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<img src="/github.ico" style="vertical-align: middle" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://www.reddit.com/r/multiplayerpianonet/"
|
||||||
|
title="MPPNet Reddit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="reddit-button icon-button top-button"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<img src="/reddit.ico" style="vertical-align: middle" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/338D2xMufC"
|
||||||
|
title="MPPNet Discord"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="discord-button icon-button top-button"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<img src="/discord.ico" style="vertical-align: middle" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://docs.google.com/document/d/1wQvGwQdaI8PuEjSWxKDDThVIoAlCYIxQOyfyi4o6HcM/edit?usp=sharing"
|
||||||
|
title="Multiplayer Piano Rules"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<button class="mpp-rules-button top-button" aria-hidden="true">
|
||||||
|
Rules
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id="social">
|
<div id="chat">
|
||||||
<div id="more-button"></div>
|
<ul></ul>
|
||||||
</div>
|
<input
|
||||||
|
placeholder="You can chat with this thing."
|
||||||
|
class="translate"
|
||||||
|
maxlength="512"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="chat">
|
<div id="room-notice"></div>
|
||||||
<ul></ul>
|
|
||||||
<input placeholder="You can chat with this thing." class="translate" maxlength="512"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="room-notice"></div>
|
<div id="names"></div>
|
||||||
|
|
||||||
<div id="names"></div>
|
<div id="piano"></div>
|
||||||
|
|
||||||
<div id="piano"></div>
|
<div id="cursors"></div>
|
||||||
|
|
||||||
<div id="cursors"></div>
|
<noscript>
|
||||||
|
<center>
|
||||||
|
<p>
|
||||||
|
Multiplayer Piano is an online, full 88-key piano you can
|
||||||
|
play alone or with others in real-time. Plug up your MIDI
|
||||||
|
keyboard, MIDI in and out are supported. You should be able
|
||||||
|
to hear some seriously talented piano players performing
|
||||||
|
here! Join in or just chat and listen.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For good performance, Chrome is highly recommended. Firefox
|
||||||
|
also supports the requisite Web Audio API, but performance
|
||||||
|
may not be as good. Chrome has Web MIDI.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Of course, first you need to
|
||||||
|
<a href="http://www.enable-javascript.com/" class="link"
|
||||||
|
>Enable Javascript</a
|
||||||
|
>
|
||||||
|
or it won't do anything...!
|
||||||
|
</p>
|
||||||
|
</center>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
<noscript>
|
<div id="bottom">
|
||||||
<center>
|
<div class="relative">
|
||||||
<p>
|
<div id="room">
|
||||||
Multiplayer Piano is an online, full 88-key piano you can play alone or with others in real-time. Plug up your MIDI keyboard, MIDI in and out are supported. You should be able to hear some seriously talented piano players performing here! Join in or just chat and listen.
|
<div class="info"></div>
|
||||||
</p>
|
<div class="expand"></div>
|
||||||
<p>
|
<div class="more">
|
||||||
For good performance, Chrome is highly recommended. Firefox also supports the requisite Web Audio API, but performance may not be as good. Chrome has Web MIDI.
|
<div class="new translate">New Room...</div>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
</div>
|
||||||
Of course, first you need to <a href="http://www.enable-javascript.com/" class="link">Enable Javascript</a> or it won't do anything...!
|
<div id="new-room-btn" class="ugly-button translate">
|
||||||
</p>
|
New Room...
|
||||||
</center>
|
</div>
|
||||||
</noscript>
|
<div id="play-alone-btn" class="ugly-button">Play Alone</div>
|
||||||
|
<div id="room-settings-btn" class="ugly-button">
|
||||||
|
Room Settings
|
||||||
|
</div>
|
||||||
|
<div id="midi-btn" class="ugly-button translate">
|
||||||
|
MIDI In/Out
|
||||||
|
</div>
|
||||||
|
<div id="record-btn" class="ugly-button translate">
|
||||||
|
Record MP3
|
||||||
|
</div>
|
||||||
|
<div id="synth-btn" class="ugly-button translate">Synth</div>
|
||||||
|
<div id="sound-btn" class="ugly-button sound-btn">
|
||||||
|
Sound Select
|
||||||
|
</div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<div id="volume">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="volume-slider"
|
||||||
|
min="0.0"
|
||||||
|
max="1.0"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="volume-label">volume</div>
|
||||||
|
<div id="quota">
|
||||||
|
<div class="value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="bottom">
|
<div id="modal">
|
||||||
<div class="relative">
|
<div class="bg"></div>
|
||||||
<div id="room">
|
<div id="modals">
|
||||||
<div class="info"></div>
|
<div id="sound-warning" class="dialog">
|
||||||
<div class="expand"></div>
|
<p>
|
||||||
<div class="more">
|
This site makes a lot of sound! You may want to adjust
|
||||||
<div class="new translate">New Room...</div>
|
the volume before continuing.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
<button class="submit">PLAY</button>
|
||||||
<div id="new-room-btn" class="ugly-button translate">New Room...</div>
|
</div>
|
||||||
<div id="play-alone-btn" class="ugly-button">Play Alone</div>
|
<div id="new-room" class="dialog">
|
||||||
<div id="room-settings-btn" class="ugly-button">Room Settings</div>
|
<p>
|
||||||
<div id="midi-btn" class="ugly-button translate">MIDI In/Out</div>
|
<input
|
||||||
<div id="record-btn" class="ugly-button translate">Record MP3</div>
|
type="text"
|
||||||
<div id="synth-btn" class="ugly-button translate">Synth</div>
|
name="name"
|
||||||
<div id="sound-btn" class="ugly-button sound-btn">Sound Select</div>
|
placeholder="room name"
|
||||||
<div id="status"></div>
|
class="text translate"
|
||||||
<div id="volume">
|
maxlength="512"
|
||||||
<input type="range" id="volume-slider" min="0.0" max="1.0" step="0.01">
|
/>
|
||||||
</div>
|
</p>
|
||||||
<div id="volume-label">volume</div>
|
<p>
|
||||||
<div id="quota">
|
<label
|
||||||
<div class="value"></div>
|
><input
|
||||||
</div>
|
type="checkbox"
|
||||||
</div>
|
name="visible"
|
||||||
</div>
|
class="checkbox translate"
|
||||||
|
checked
|
||||||
|
/>Visible (open to everyone)</label
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<button class="submit">go</button>
|
||||||
|
</div>
|
||||||
|
<div id="room-settings" class="dialog">
|
||||||
|
<div class="ugly-button drop-crown">Drop crown</div>
|
||||||
|
<p>
|
||||||
|
<label
|
||||||
|
><input
|
||||||
|
type="checkbox"
|
||||||
|
name="visible"
|
||||||
|
class="checkbox translate"
|
||||||
|
checked
|
||||||
|
/>Visible (open to everyone)</label
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label
|
||||||
|
><input
|
||||||
|
type="checkbox"
|
||||||
|
name="chat"
|
||||||
|
class="checkbox translate"
|
||||||
|
checked
|
||||||
|
/>Enable Chat</label
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label
|
||||||
|
><input
|
||||||
|
type="checkbox"
|
||||||
|
name="crownsolo"
|
||||||
|
class="checkbox"
|
||||||
|
/>Only Owner can Play</label
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<button class="submit">APPLY</button>
|
||||||
|
<p>
|
||||||
|
<label
|
||||||
|
>Background color: <input
|
||||||
|
type="color"
|
||||||
|
name="color"
|
||||||
|
placeholder=""
|
||||||
|
maxlength="7"
|
||||||
|
class="color"
|
||||||
|
/></label>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="rename" class="dialog">
|
||||||
|
<p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
placeholder="My Fancy New Name"
|
||||||
|
maxlength="40"
|
||||||
|
class="text"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{% if usersConfig.enableColorChanging %}
|
||||||
|
<p>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
name="color"
|
||||||
|
placeholder=""
|
||||||
|
maxlength="7"
|
||||||
|
class="color"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<button class="submit">USER SET</button>
|
||||||
|
</div>
|
||||||
|
<div id="more">
|
||||||
|
<div class="header"></div>
|
||||||
|
<div class="items"></div>
|
||||||
|
<div class="footer">
|
||||||
|
Keep looking here for the eventual appearance of a blog,
|
||||||
|
help guide, etc. Also there are unofficial
|
||||||
|
(community-run)
|
||||||
|
<a href="http://www.reddit.com/r/multiplayerpiano"
|
||||||
|
>subreddit</a
|
||||||
|
>
|
||||||
|
and
|
||||||
|
<a href="http://www.youtube.com/MultiplayerPiano"
|
||||||
|
>youtube</a
|
||||||
|
>
|
||||||
|
pages. Thanks!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="modal">
|
<div id="config" style="display: none">{{base64config}}</div>
|
||||||
<div class="bg"></div>
|
|
||||||
<div id="modals">
|
|
||||||
<div id="sound-warning" class="dialog">
|
|
||||||
<p>{{ motd }}</p>
|
|
||||||
<button class="submit">PLAY</button>
|
|
||||||
</div>
|
|
||||||
<div id="new-room" class="dialog">
|
|
||||||
<p><input type="text" name="name" placeholder="room name" class="text translate" maxlength="512"/></p>
|
|
||||||
<p><label><input type="checkbox" name="visible" class="checkbox translate" checked>Visible (open to everyone)</label></p>
|
|
||||||
</label></p>
|
|
||||||
<button class="submit">go</button>
|
|
||||||
</div>
|
|
||||||
<div id="room-settings" class="dialog">
|
|
||||||
<p><div class="ugly-button drop-crown">Drop crown</div></p>
|
|
||||||
<p><label><input type="checkbox" name="visible" class="checkbox translate" checked>Visible (open to everyone)</label></p>
|
|
||||||
</label></p>
|
|
||||||
<p><label><input type="checkbox" name="chat" class="checkbox translate" checked>Enable Chat</label></p>
|
|
||||||
<p><label><input type="checkbox" name="crownsolo" class="checkbox">Only Owner can Play<button class="submit">APPLY</button>
|
|
||||||
<p><label>Background color: <input type="color" name="color" placeholder="" maxlength="7" class="color"></label></p>
|
|
||||||
</div>
|
|
||||||
<div id="rename" class="dialog">
|
|
||||||
<p><input type="text" name="name" placeholder="My Fancy New Name" maxlength="40" class="text"/></p>
|
|
||||||
<!--<p><input type="color" name="color" placeholder="" maxlength="7" class="color"/></p>-->
|
|
||||||
<button class="submit">USER SET</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/jquery.min.js"></script>
|
<script src="/jquery.min.js"></script>
|
||||||
<script src="/util.js"></script>
|
<script src="/util.js"></script>
|
||||||
<script src="/Client.js"></script>
|
<script src="/Client.js"></script>
|
||||||
<script src="/NoteQuota.js"></script>
|
<script src="/NoteQuota.js"></script>
|
||||||
<script src="/lame.min.js"></script>
|
<script src="/lame.min.js"></script>
|
||||||
<script src="/Color.js"></script>
|
<script src="/Color.js"></script>
|
||||||
<!--<script src="/ebsprite.js"></script>-->
|
<!--<script src="/ebsprite.js"></script>-->
|
||||||
<script src="/script.js"></script>
|
<script src="/script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,150 @@
|
||||||
|
<div class="items">
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>Multiplayer Piano is available in the Chrome Web Store.</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://chrome.google.com/webstore/detail/multiplayer-piano/cbadoggeokhliehfonkefnfcbgocojid"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img src="ChromeWebStore_BadgeWBorder_v2_206x58.png" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A
|
||||||
|
<a
|
||||||
|
href="https://chrome.google.com/webstore/detail/multiplayer-piano/cbadoggeokhliehfonkefnfcbgocojid/reviews"
|
||||||
|
target="_blank"
|
||||||
|
>review</a
|
||||||
|
>
|
||||||
|
is certainly a helpful thing; we need more of those.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://www.facebook.com/MultiplayerPiano"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img src="FB_FindUsOnFacebook-144.png" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There is a
|
||||||
|
<a
|
||||||
|
href="https://www.facebook.com/MultiplayerPiano"
|
||||||
|
target="_blank"
|
||||||
|
>Facebook page</a
|
||||||
|
>
|
||||||
|
for the site. Click 'Like' to show your support, learn about
|
||||||
|
updates, and discuss ideas.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="fb-like"
|
||||||
|
data-href="http://www.facebook.com/MultiplayerPiano"
|
||||||
|
data-send="false"
|
||||||
|
data-layout="button_count"
|
||||||
|
data-width="300"
|
||||||
|
data-show-faces="false"
|
||||||
|
></div>
|
||||||
|
<p>
|
||||||
|
You could also
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onclick="window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;"
|
||||||
|
>share your current room</a
|
||||||
|
>
|
||||||
|
with your friends.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>You can share using Twitter, too!</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/share"
|
||||||
|
class="twitter-share-button"
|
||||||
|
data-url="http://www.multiplayerpiano.com"
|
||||||
|
data-text="Multiplayer Piano"
|
||||||
|
data-via="MultiplayrPiano"
|
||||||
|
target="_blank"
|
||||||
|
>Tweet</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Follow
|
||||||
|
<a href="http://twitter.com/MultiplayrPiano" target="_blank"
|
||||||
|
>@MultiplayrPiano</a
|
||||||
|
>
|
||||||
|
for updates.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>Hey this rectangle is completely empty!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
Want to donate money to help support the site? That's amazing!
|
||||||
|
You could donate using PayPal and it would help immensely!
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
class="text-small"
|
||||||
|
action="https://www.paypal.com/cgi-bin/webscr"
|
||||||
|
method="post"
|
||||||
|
target="_top"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="hosted_button_id"
|
||||||
|
value="6YDYHR6UX6QSY"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="image"
|
||||||
|
src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif"
|
||||||
|
border="0"
|
||||||
|
name="submit"
|
||||||
|
alt="PayPal - The safer, easier way to pay online!"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<p>
|
||||||
|
<a href="bitcoin:15g16JDpYRvWjNKCkYaVnu2FAHCmmaLvGu"
|
||||||
|
>Bitcoins</a
|
||||||
|
>
|
||||||
|
can be money, too!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
My name is Brandon. You can send an email to ask a question or
|
||||||
|
say hello!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
id="email"
|
||||||
|
href="#"
|
||||||
|
obscured="zhygvcynlrecvnab.pbz@tznvy.pbz"
|
||||||
|
>
|
||||||
|
<img src="email.png" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you encrypt your message using
|
||||||
|
<a
|
||||||
|
href="http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x38202C5F4E0C0715"
|
||||||
|
target="_blank"
|
||||||
|
>my public key</a
|
||||||
|
>, I will think you're awesome automatically!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
1328
screen.css
564
script.js
|
@ -1,20 +1,30 @@
|
||||||
// 钢琴
|
// 钢琴
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
var test_mode =
|
const test_mode =
|
||||||
window.location.hash &&
|
window.location.hash &&
|
||||||
window.location.hash.match(/^(?:#.+)*#test(?:#.+)*$/i);
|
window.location.hash.match(/^(?:#.+)*#test(?:#.+)*$/i);
|
||||||
|
|
||||||
var gSeeOwnCursor =
|
const gSeeOwnCursor =
|
||||||
window.location.hash &&
|
window.location.hash &&
|
||||||
window.location.hash.match(/^(?:#.+)*#seeowncursor(?:#.+)*$/i);
|
window.location.hash.match(/^(?:#.+)*#seeowncursor(?:#.+)*$/i);
|
||||||
|
|
||||||
var gMidiVolumeTest =
|
const gMidiVolumeTest =
|
||||||
window.location.hash &&
|
window.location.hash &&
|
||||||
window.location.hash.match(/^(?:#.+)*#midivolumetest(?:#.+)*$/i);
|
window.location.hash.match(/^(?:#.+)*#midivolumetest(?:#.+)*$/i);
|
||||||
|
|
||||||
var gMidiOutTest;
|
var gMidiOutTest;
|
||||||
|
|
||||||
|
// base64 idea from yellowberry
|
||||||
|
let base64config, configs;
|
||||||
|
|
||||||
|
try {
|
||||||
|
base64config = document.getElementById("config").innerText;
|
||||||
|
configs = JSON.parse(atob(base64config));
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Unable to parse server config:", err);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.prototype.indexOf) {
|
if (!Array.prototype.indexOf) {
|
||||||
Array.prototype.indexOf = function (elt /*, from*/) {
|
Array.prototype.indexOf = function (elt /*, from*/) {
|
||||||
var len = this.length >>> 0;
|
var len = this.length >>> 0;
|
||||||
|
@ -1144,16 +1154,25 @@ $(function () {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var channel_id = decodeURIComponent(window.location.hash);
|
var channel_id = decodeURIComponent(window.location.pathname);
|
||||||
if (channel_id.substr(0, 1) == "#") channel_id = channel_id.substr(1);
|
if (channel_id.substr(0, 1) == "/") channel_id = channel_id.substr(1);
|
||||||
if (channel_id == "") channel_id = "lobby";
|
if (channel_id == "") channel_id = "lobby";
|
||||||
|
|
||||||
const protocol = location.protocol == "https:" ? "wss:" : "ws:";
|
const isSecure = globalThis.location.protocol == "https:";
|
||||||
const wssport =
|
const port = window.location.hostname.includes("multiplayerpiano.dev")
|
||||||
window.location.hostname == "www.multiplayerpiano.dev" ? 443 : 8443;
|
? 443
|
||||||
const gClient = new Client(`${protocol}//${location.hostname}:${wssport}`);
|
: 8443;
|
||||||
|
const gClient = new Client(
|
||||||
|
(isSecure ? "wss://" : "ws://") + window.location.hostname + ":" + port
|
||||||
|
);
|
||||||
|
|
||||||
|
let enableTokens = true;
|
||||||
|
let enableChallenge = true;
|
||||||
|
if (configs.usersConfig.tokenAuth == "none") enableTokens = false;
|
||||||
|
if (configs.usersConfig.browserChallenge == "none") enableChallenge = false;
|
||||||
|
|
||||||
gClient.setChannel(channel_id);
|
gClient.setChannel(channel_id);
|
||||||
gClient.start();
|
gClient.start(enableTokens, enableChallenge);
|
||||||
|
|
||||||
gClient.on("disconnect", function (evt) {
|
gClient.on("disconnect", function (evt) {
|
||||||
console.log(evt);
|
console.log(evt);
|
||||||
|
@ -1212,6 +1231,25 @@ $(function () {
|
||||||
part.nameDiv = $("#names")[0].appendChild(div);
|
part.nameDiv = $("#names")[0].appendChild(div);
|
||||||
$(part.nameDiv).fadeIn(2000);
|
$(part.nameDiv).fadeIn(2000);
|
||||||
|
|
||||||
|
if (part.tag) {
|
||||||
|
if (configs.usersConfig.enableTags) {
|
||||||
|
console.log(part.tag);
|
||||||
|
const tag = document.createElement("div");
|
||||||
|
$(tag).addClass("nametag");
|
||||||
|
$(tag).text(part.tag.text);
|
||||||
|
$(tag).css("background", part.tag.color);
|
||||||
|
part.tagDiv = $(part.nameDiv).prepend(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.tag.text === "ADMIN") {
|
||||||
|
$(part.nameDiv).addClass("admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.tag.text === "OWNER") {
|
||||||
|
$(part.nameDiv).addClass("webmaster");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sort names
|
// sort names
|
||||||
var arr = $("#names .name");
|
var arr = $("#names .name");
|
||||||
arr.sort(function (a, b) {
|
arr.sort(function (a, b) {
|
||||||
|
@ -1261,6 +1299,24 @@ $(function () {
|
||||||
.find(".name")
|
.find(".name")
|
||||||
.text(name)
|
.text(name)
|
||||||
.css("background-color", color);
|
.css("background-color", color);
|
||||||
|
if (part.tag) {
|
||||||
|
if (configs.usersConfig.enableTags) {
|
||||||
|
console.log(part.tag);
|
||||||
|
const tag = document.createElement("div");
|
||||||
|
$(tag).addClass("nametag");
|
||||||
|
$(tag).text(part.tag.text);
|
||||||
|
$(tag).css("background", part.tag.color);
|
||||||
|
part.tagDiv = $(part.nameDiv).prepend(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.tag.text === "ADMIN") {
|
||||||
|
$(part.nameDiv).addClass("admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.tag.text === "OWNER") {
|
||||||
|
$(part.nameDiv).addClass("webmaster");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
gClient.on("ch", function (msg) {
|
gClient.on("ch", function (msg) {
|
||||||
for (var id in gClient.ppl) {
|
for (var id in gClient.ppl) {
|
||||||
|
@ -1545,56 +1601,18 @@ $(function () {
|
||||||
|
|
||||||
var bottom = document.getElementById("bottom");
|
var bottom = document.getElementById("bottom");
|
||||||
|
|
||||||
var duration = 500;
|
document.body.style.setProperty("--color", color1.toHexa());
|
||||||
var step = 0;
|
document.body.style.setProperty("--color2", color2.toHexa());
|
||||||
var steps = 30;
|
|
||||||
var step_ms = duration / steps;
|
bottom.style.setProperty("--color", color1.toHexa());
|
||||||
var difference = new Color(color1.r, color1.g, color1.b);
|
bottom.style.setProperty("--color2", color2.toHexa());
|
||||||
difference.r -= old_color1.r;
|
|
||||||
difference.g -= old_color1.g;
|
|
||||||
difference.b -= old_color1.b;
|
|
||||||
var inc1 = new Color(
|
|
||||||
difference.r / steps,
|
|
||||||
difference.g / steps,
|
|
||||||
difference.b / steps
|
|
||||||
);
|
|
||||||
difference = new Color(color2.r, color2.g, color2.b);
|
|
||||||
difference.r -= old_color2.r;
|
|
||||||
difference.g -= old_color2.g;
|
|
||||||
difference.b -= old_color2.b;
|
|
||||||
var inc2 = new Color(
|
|
||||||
difference.r / steps,
|
|
||||||
difference.g / steps,
|
|
||||||
difference.b / steps
|
|
||||||
);
|
|
||||||
var iv;
|
|
||||||
iv = setInterval(function () {
|
|
||||||
old_color1.add(inc1.r, inc1.g, inc1.b);
|
|
||||||
old_color2.add(inc2.r, inc2.g, inc2.b);
|
|
||||||
document.body.style.background =
|
|
||||||
"radial-gradient(ellipse at center, " +
|
|
||||||
old_color1.toHexa() +
|
|
||||||
" 0%," +
|
|
||||||
old_color2.toHexa() +
|
|
||||||
" 100%)";
|
|
||||||
bottom.style.background = old_color2.toHexa();
|
|
||||||
if (++step >= steps) {
|
|
||||||
clearInterval(iv);
|
|
||||||
old_color1 = color1;
|
|
||||||
old_color2 = color2;
|
|
||||||
document.body.style.background =
|
|
||||||
"radial-gradient(ellipse at center, " +
|
|
||||||
color1.toHexa() +
|
|
||||||
" 0%," +
|
|
||||||
color2.toHexa() +
|
|
||||||
" 100%)";
|
|
||||||
bottom.style.background = color2.toHexa();
|
|
||||||
}
|
|
||||||
}, step_ms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColorToDefault() {
|
function setColorToDefault() {
|
||||||
setColor("#000000", "#000000");
|
setColor(
|
||||||
|
configs.urlChannel.settings.color || "#000000",
|
||||||
|
configs.urlChannel.settings.color2 || "#000000"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setColorToDefault();
|
setColorToDefault();
|
||||||
|
@ -2030,7 +2048,7 @@ $(function () {
|
||||||
eles.remove();
|
eles.remove();
|
||||||
}
|
}
|
||||||
this.domElement = $(
|
this.domElement = $(
|
||||||
'<div class="notification"><div class="notification-body"><div class="title"></div>' +
|
'<div class="notification" style="display: none;"><div class="notification-body"><div class="title"></div>' +
|
||||||
'<div class="text"></div></div><div class="x">Ⓧ</div></div>'
|
'<div class="text"></div></div><div class="x">Ⓧ</div></div>'
|
||||||
);
|
);
|
||||||
this.domElement[0].id = this.id;
|
this.domElement[0].id = this.id;
|
||||||
|
@ -2055,6 +2073,8 @@ $(function () {
|
||||||
self.close();
|
self.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(this.domElement).fadeIn(100);
|
||||||
|
|
||||||
if (this.duration > 0) {
|
if (this.duration > 0) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
self.close();
|
self.close();
|
||||||
|
@ -2083,7 +2103,7 @@ $(function () {
|
||||||
Notification.prototype.close = function () {
|
Notification.prototype.close = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
window.removeEventListener("resize", this.onresize);
|
window.removeEventListener("resize", this.onresize);
|
||||||
this.domElement.fadeOut(500, function () {
|
this.domElement.fadeOut(250, function () {
|
||||||
self.domElement.remove();
|
self.domElement.remove();
|
||||||
self.emit("close");
|
self.emit("close");
|
||||||
});
|
});
|
||||||
|
@ -2224,16 +2244,24 @@ $(function () {
|
||||||
var room_name = "Room" + Math.floor(Math.random() * 1000000000000);
|
var room_name = "Room" + Math.floor(Math.random() * 1000000000000);
|
||||||
changeRoom(room_name, "right", { visible: false });
|
changeRoom(room_name, "right", { visible: false });
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
new Notification({
|
let html =
|
||||||
id: "share",
|
"You are playing alone in a room by yourself, but you can always invite \
|
||||||
title: "Playing alone",
|
friends by sending them the link.";
|
||||||
html:
|
|
||||||
|
if (configs.config.playingAloneSocialLinks) {
|
||||||
|
html =
|
||||||
"You are playing alone in a room by yourself, but you can always invite \
|
"You are playing alone in a room by yourself, but you can always invite \
|
||||||
friends by sending them the link.<br/><br/>\
|
friends by sending them the link.<br/><br/>\
|
||||||
<a href=\"#\" onclick=\"window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;\">Share on Facebook</a><br/><br/>\
|
<a href=\"#\" onclick=\"window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;\">Share on Facebook</a><br/><br/>\
|
||||||
<a href=\"http://twitter.com/home?status=" +
|
<a href=\"http://twitter.com/home?status=" +
|
||||||
encodeURIComponent(location.href) +
|
encodeURIComponent(location.href) +
|
||||||
'" target="_blank">Tweet</a>',
|
'" target="_blank">Tweet</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
new Notification({
|
||||||
|
id: "share",
|
||||||
|
title: "Playing alone",
|
||||||
|
html,
|
||||||
duration: 25000
|
duration: 25000
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -2264,8 +2292,8 @@ $(function () {
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
$(document).off("keydown", modalHandleEsc);
|
$(document).off("keydown", modalHandleEsc);
|
||||||
$("#modal").fadeOut(100);
|
$("#modal").fadeOut(300);
|
||||||
$("#modal #modals > *").hide();
|
// $("#modal #modals > *").hide();
|
||||||
captureKeyboard();
|
captureKeyboard();
|
||||||
gModal = null;
|
gModal = null;
|
||||||
}
|
}
|
||||||
|
@ -2287,6 +2315,18 @@ $(function () {
|
||||||
closeModal();
|
closeModal();
|
||||||
changeRoom(name, "right", settings);
|
changeRoom(name, "right", settings);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
let html =
|
||||||
|
"You can invite friends to your room by sending them the link.<br/><br/>\
|
||||||
|
<a href=\"#\" onclick=\"window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;\">Share on Facebook</a><br/><br/>\
|
||||||
|
<a href=\"http://twitter.com/home?status=" +
|
||||||
|
encodeURIComponent(location.href) +
|
||||||
|
'" target="_blank">Tweet</a>';
|
||||||
|
|
||||||
|
if (configs.config.createdRoomSocialLinks) {
|
||||||
|
html =
|
||||||
|
"You can invite friends to your room by sending them the link.";
|
||||||
|
}
|
||||||
|
|
||||||
new Notification({
|
new Notification({
|
||||||
id: "share",
|
id: "share",
|
||||||
title: "Created a Room",
|
title: "Created a Room",
|
||||||
|
@ -2326,7 +2366,7 @@ $(function () {
|
||||||
if (name == "") name = "lobby";
|
if (name == "") name = "lobby";
|
||||||
if (gClient.channel && gClient.channel._id === name) return;
|
if (gClient.channel && gClient.channel._id === name) return;
|
||||||
if (push) {
|
if (push) {
|
||||||
var url = "/#" + encodeURIComponent(name).replace("'", "%27");
|
var url = "/" + encodeURIComponent(name).replace("'", "%27");
|
||||||
if (window.history && history.pushState) {
|
if (window.history && history.pushState) {
|
||||||
history.pushState(
|
history.pushState(
|
||||||
{ depth: (gHistoryDepth += 1), name: name },
|
{ depth: (gHistoryDepth += 1), name: name },
|
||||||
|
@ -2343,32 +2383,34 @@ $(function () {
|
||||||
|
|
||||||
var t = 0,
|
var t = 0,
|
||||||
d = 100;
|
d = 100;
|
||||||
$("#piano")
|
|
||||||
.addClass("ease-out")
|
if (configs.config.enableSlide) {
|
||||||
.addClass("slide-" + opposite);
|
$("#piano").addClass("slide");
|
||||||
setTimeout(
|
|
||||||
function () {
|
requestAnimationFrame(() => {
|
||||||
$("#piano")
|
$("#piano")
|
||||||
.removeClass("ease-out")
|
.addClass("ease-out")
|
||||||
.removeClass("slide-" + opposite)
|
.addClass("slide-" + opposite);
|
||||||
.addClass("slide-" + direction);
|
setTimeout(function () {
|
||||||
},
|
$("#piano")
|
||||||
(t += d)
|
.removeClass("ease-out")
|
||||||
);
|
.removeClass("slide-" + opposite)
|
||||||
setTimeout(
|
.addClass("slide-" + direction);
|
||||||
function () {
|
}, (t += d));
|
||||||
$("#piano")
|
setTimeout(function () {
|
||||||
.addClass("ease-in")
|
$("#piano")
|
||||||
.removeClass("slide-" + direction);
|
.addClass("ease-in")
|
||||||
},
|
.removeClass("slide-" + direction);
|
||||||
(t += d)
|
}, (t += d));
|
||||||
);
|
setTimeout(function () {
|
||||||
setTimeout(
|
$("#piano").removeClass("ease-in");
|
||||||
function () {
|
|
||||||
$("#piano").removeClass("ease-in");
|
setTimeout(function () {
|
||||||
},
|
$("#piano").removeClass("slide");
|
||||||
(t += d)
|
}, d);
|
||||||
);
|
}, (t += d));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gHistoryDepth = 0;
|
var gHistoryDepth = 0;
|
||||||
|
@ -2697,7 +2739,7 @@ $(function () {
|
||||||
if (typeof input.volume === "undefined") {
|
if (typeof input.volume === "undefined") {
|
||||||
input.volume = 1.0;
|
input.volume = 1.0;
|
||||||
}
|
}
|
||||||
console.log("input", input);
|
// console.log("input", input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (midi.outputs.size > 0) {
|
if (midi.outputs.size > 0) {
|
||||||
|
@ -2712,7 +2754,7 @@ $(function () {
|
||||||
if (typeof output.volume === "undefined") {
|
if (typeof output.volume === "undefined") {
|
||||||
output.volume = 1.0;
|
output.volume = 1.0;
|
||||||
}
|
}
|
||||||
console.log("output", output);
|
// console.log("output", output);
|
||||||
}
|
}
|
||||||
gMidiOutTest = function (note_name, vel, delay_ms) {
|
gMidiOutTest = function (note_name, vel, delay_ms) {
|
||||||
var note_number =
|
var note_number =
|
||||||
|
@ -2928,105 +2970,140 @@ $(function () {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
window.onerror = function (message, url, line) {
|
// window.onerror = function (message, url, line) {
|
||||||
var url = url || "(no url)";
|
// var url = url || "(no url)";
|
||||||
var line = line || "(no line)";
|
// var line = line || "(no line)";
|
||||||
// errors in socket.io
|
// // errors in socket.io
|
||||||
if (url.indexOf("socket.io.js") !== -1) {
|
// if (url.indexOf("socket.io.js") !== -1) {
|
||||||
if (message.indexOf("INVALID_STATE_ERR") !== -1) return;
|
// if (message.indexOf("INVALID_STATE_ERR") !== -1) return;
|
||||||
if (message.indexOf("InvalidStateError") !== -1) return;
|
// if (message.indexOf("InvalidStateError") !== -1) return;
|
||||||
if (message.indexOf("DOM Exception 11") !== -1) return;
|
// if (message.indexOf("DOM Exception 11") !== -1) return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Property 'open' of object #<c> is not a function"
|
// "Property 'open' of object #<c> is not a function"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf("Cannot call method 'close' of undefined") !==
|
// message.indexOf("Cannot call method 'close' of undefined") !==
|
||||||
-1
|
// -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("Cannot call method 'close' of null") !== -1)
|
// if (message.indexOf("Cannot call method 'close' of null") !== -1)
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("Cannot call method 'onClose' of null") !== -1)
|
// if (message.indexOf("Cannot call method 'onClose' of null") !== -1)
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("Cannot call method 'payload' of null") !== -1)
|
// if (message.indexOf("Cannot call method 'payload' of null") !== -1)
|
||||||
return;
|
// return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Unable to get value of the property 'close'"
|
// "Unable to get value of the property 'close'"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) return;
|
// if (message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Unable to get property 'close' of undefined or null reference"
|
// "Unable to get property 'close' of undefined or null reference"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Unable to get value of the property 'close': object is null or undefined"
|
// "Unable to get value of the property 'close': object is null or undefined"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("this.transport is null") !== -1) return;
|
// if (message.indexOf("this.transport is null") !== -1) return;
|
||||||
}
|
// }
|
||||||
// errors in soundmanager2
|
// // errors in soundmanager2
|
||||||
if (url.indexOf("soundmanager2.js") !== -1) {
|
// if (url.indexOf("soundmanager2.js") !== -1) {
|
||||||
// operation disabled in safe mode?
|
// // operation disabled in safe mode?
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Could not complete the operation due to error c00d36ef"
|
// "Could not complete the operation due to error c00d36ef"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (message.indexOf("_s.o._setVolume is not a function") !== -1)
|
// if (message.indexOf("_s.o._setVolume is not a function") !== -1)
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// errors in midibridge
|
// // errors in midibridge
|
||||||
if (url.indexOf("midibridge") !== -1) {
|
// if (url.indexOf("midibridge") !== -1) {
|
||||||
if (message.indexOf("Error calling method on NPObject") !== -1)
|
// if (message.indexOf("Error calling method on NPObject") !== -1)
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// too many failing extensions injected in my html
|
// // too many failing extensions injected in my html
|
||||||
if (url.indexOf(".js") !== url.length - 3) return;
|
// if (url.indexOf(".js") !== url.length - 3) return;
|
||||||
// extensions inject cross-domain embeds too
|
// // extensions inject cross-domain embeds too
|
||||||
if (url.toLowerCase().indexOf("multiplayerpiano.com") == -1) return;
|
// if (url.toLowerCase().indexOf("multiplayerpiano.com") == -1) return;
|
||||||
|
|
||||||
// errors in my code
|
// // errors in my code
|
||||||
if (url.indexOf("script.js") !== -1) {
|
// if (url.indexOf("script.js") !== -1) {
|
||||||
if (
|
// if (
|
||||||
message.indexOf("Object [object Object] has no method 'on'") !==
|
// message.indexOf("Object [object Object] has no method 'on'") !==
|
||||||
-1
|
// -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Object [object Object] has no method 'off'"
|
// "Object [object Object] has no method 'off'"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
if (
|
// if (
|
||||||
message.indexOf(
|
// message.indexOf(
|
||||||
"Property '$' of object [object Object] is not a function"
|
// "Property '$' of object [object Object] is not a function"
|
||||||
) !== -1
|
// ) !== -1
|
||||||
)
|
// )
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
var enc =
|
// var enc =
|
||||||
"/bugreport/" +
|
// "/bugreport/" +
|
||||||
(message ? encodeURIComponent(message) : "") +
|
// (message ? encodeURIComponent(message) : "") +
|
||||||
"/" +
|
// "/" +
|
||||||
(url ? encodeURIComponent(url) : "") +
|
// (url ? encodeURIComponent(url) : "") +
|
||||||
"/" +
|
// "/" +
|
||||||
(line ? encodeURIComponent(line) : "");
|
// (line ? encodeURIComponent(line) : "");
|
||||||
var img = new Image();
|
// var img = new Image();
|
||||||
img.src = enc;
|
// img.src = enc;
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
// more button
|
||||||
|
(function () {
|
||||||
|
var loaded = false;
|
||||||
|
setTimeout(function () {
|
||||||
|
$("#social").fadeIn(250);
|
||||||
|
$("#more-button").click(function () {
|
||||||
|
openModal("#more");
|
||||||
|
if (loaded === false) {
|
||||||
|
$.get("/more.html").success(function (data) {
|
||||||
|
loaded = true;
|
||||||
|
var items = $(data).find(".item");
|
||||||
|
if (items.length > 0) {
|
||||||
|
$("#more .items").append(items);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var ele = document.getElementById("email");
|
||||||
|
var email = ele
|
||||||
|
.getAttribute("obscured")
|
||||||
|
.replace(/[a-zA-Z]/g, function (c) {
|
||||||
|
return String.fromCharCode(
|
||||||
|
(c <= "Z" ? 90 : 122) >=
|
||||||
|
(c = c.charCodeAt(0) + 13)
|
||||||
|
? c
|
||||||
|
: c - 26
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ele.href = "mailto:" + email;
|
||||||
|
ele.textContent = email;
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
})();
|
||||||
|
|
||||||
// API
|
// API
|
||||||
window.MPP = {
|
window.MPP = {
|
||||||
|
@ -3039,7 +3116,8 @@ $(function () {
|
||||||
chat: chat,
|
chat: chat,
|
||||||
noteQuota: gNoteQuota,
|
noteQuota: gNoteQuota,
|
||||||
soundSelector: gSoundSelector,
|
soundSelector: gSoundSelector,
|
||||||
Notification: Notification
|
Notification: Notification,
|
||||||
|
configs
|
||||||
};
|
};
|
||||||
|
|
||||||
// record mp3
|
// record mp3
|
||||||
|
@ -3338,105 +3416,3 @@ $(function () {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
function catSound() {
|
|
||||||
let sounds = [
|
|
||||||
"cat-sounds/meow1.mp3",
|
|
||||||
"cat-sounds/meow2.mp3",
|
|
||||||
"cat-sounds/meow3.mp3",
|
|
||||||
"cat-sounds/meow4.mp3",
|
|
||||||
"cat-sounds/meow5.mp3",
|
|
||||||
"cat-sounds/meow6.mp3",
|
|
||||||
"cat-sounds/meow7.mp3",
|
|
||||||
"cat-sounds/meow8.mp3",
|
|
||||||
"cat-sounds/meow9.mp3",
|
|
||||||
"cat-sounds/meow10.mp3"
|
|
||||||
];
|
|
||||||
let random = sounds[Math.floor(Math.random() * sounds.length)];
|
|
||||||
const meow = new Audio(random);
|
|
||||||
meow.volume = MPP.piano.audio.volume / 2;
|
|
||||||
meow.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("more-button").onclick = catSound;
|
|
||||||
|
|
||||||
// misc
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// analytics
|
|
||||||
window.google_analytics_uacct = "UA-882009-7";
|
|
||||||
var _gaq = _gaq || [];
|
|
||||||
_gaq.push(["_setAccount", "UA-882009-7"]);
|
|
||||||
_gaq.push(["_trackPageview"]);
|
|
||||||
_gaq.push(["_setAllowAnchor", true]);
|
|
||||||
(function () {
|
|
||||||
var ga = document.createElement("script");
|
|
||||||
ga.type = "text/javascript";
|
|
||||||
ga.async = true;
|
|
||||||
ga.src =
|
|
||||||
("https:" == document.location.protocol
|
|
||||||
? "https://ssl"
|
|
||||||
: "http://www") + ".google-analytics.com/ga.js";
|
|
||||||
var s = document.getElementsByTagName("script")[0];
|
|
||||||
s.parentNode.insertBefore(ga, s);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// twitter
|
|
||||||
!(function (d, s, id) {
|
|
||||||
var js,
|
|
||||||
fjs = d.getElementsByTagName(s)[0];
|
|
||||||
if (!d.getElementById(id)) {
|
|
||||||
js = d.createElement(s);
|
|
||||||
js.id = id;
|
|
||||||
js.src = "//platform.twitter.com/widgets.js";
|
|
||||||
fjs.parentNode.insertBefore(js, fjs);
|
|
||||||
}
|
|
||||||
})(document, "script", "twitter-wjs");
|
|
||||||
|
|
||||||
// fb
|
|
||||||
(function (d, s, id) {
|
|
||||||
var js,
|
|
||||||
fjs = d.getElementsByTagName(s)[0];
|
|
||||||
if (d.getElementById(id)) return;
|
|
||||||
js = d.createElement(s);
|
|
||||||
js.id = id;
|
|
||||||
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8";
|
|
||||||
fjs.parentNode.insertBefore(js, fjs);
|
|
||||||
})(document, "script", "facebook-jssdk");
|
|
||||||
|
|
||||||
// non-ad-free experience
|
|
||||||
/*(function() {
|
|
||||||
function adsOn() {
|
|
||||||
if(window.localStorage) {
|
|
||||||
var div = document.querySelector("#inclinations");
|
|
||||||
div.innerHTML = "Ads:<br>ON / <a id=\"adsoff\" href=\"#\">OFF</a>";
|
|
||||||
div.querySelector("#adsoff").addEventListener("click", adsOff);
|
|
||||||
localStorage.ads = true;
|
|
||||||
}
|
|
||||||
// adsterra
|
|
||||||
var script = document.createElement("script");
|
|
||||||
script.src = "//pl132070.puhtml.com/68/7a/97/687a978dd26d579c788cb41e352f5a41.js";
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
function adsOff() {
|
|
||||||
if(window.localStorage) localStorage.ads = false;
|
|
||||||
document.location.reload(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function noAds() {
|
|
||||||
var div = document.querySelector("#inclinations");
|
|
||||||
div.innerHTML = "Ads:<br><a id=\"adson\" href=\"#\">ON</a> / OFF";
|
|
||||||
div.querySelector("#adson").addEventListener("click", adsOn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(window.localStorage) {
|
|
||||||
if(localStorage.ads === undefined || localStorage.ads === "true")
|
|
||||||
adsOn();
|
|
||||||
else
|
|
||||||
noAds();
|
|
||||||
} else {
|
|
||||||
adsOn();
|
|
||||||
}
|
|
||||||
})();*/
|
|
||||||
|
|
After Width: | Height: | Size: 164 B |
2
sounds
|
@ -1 +1 @@
|
||||||
Subproject commit 6b98bd95be700c1003bd67c85ba4e8b971d4701a
|
Subproject commit 26ec744c86f0c77f20d883d7aeb2bde9397a0d2a
|
|
@ -0,0 +1,31 @@
|
||||||
|
#names .nametag {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
padding: 4px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#names .nametag {
|
||||||
|
z-index: 301;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor .name .curtag {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 7px;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-left: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
padding: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
.top-button {
|
||||||
|
height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #111;
|
||||||
|
border: 1px solid #444;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-button:hover {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-button.stuck {
|
||||||
|
background: rgba(204, 187, 170, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 6px;
|
||||||
|
z-index: 200;
|
||||||
|
width: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mppcommunity-button {
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discord-button {
|
||||||
|
right: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-button {
|
||||||
|
right: 66px;
|
||||||
|
background-color: white;
|
||||||
|
filter: invert(100%);
|
||||||
|
border-color: #c6c6c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-button:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reddit-button {
|
||||||
|
right: 96px;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mpp-rules-button {
|
||||||
|
position: fixed;
|
||||||
|
right: 6px;
|
||||||
|
top: 32px;
|
||||||
|
z-index: 200;
|
||||||
|
}
|