mirror of
https://github.com/Nathanwoodburn/hnschat-web.git
synced 2025-01-18 19:58:12 +11:00
493 lines
10 KiB
JavaScript
493 lines
10 KiB
JavaScript
|
export class stream {
|
||
|
constructor(parent) {
|
||
|
this.parent = parent;
|
||
|
|
||
|
this.janus;
|
||
|
this.sfu;
|
||
|
|
||
|
this.janus2;
|
||
|
this.sfu2;
|
||
|
|
||
|
this.dish = new Dish($("#videoContainer")[0]);
|
||
|
this.dish.append();
|
||
|
this.dish.resize();
|
||
|
|
||
|
this.myid;
|
||
|
this.pvtid;
|
||
|
this.pvtid2;
|
||
|
|
||
|
window.addEventListener("resize", () => {
|
||
|
this.dish.resize();
|
||
|
});
|
||
|
|
||
|
Janus.init({debug: "none"});
|
||
|
}
|
||
|
|
||
|
async init(type=false) {
|
||
|
let done = new Promise(resolve => {
|
||
|
let instance = "main";
|
||
|
let j = this;
|
||
|
let janus = j.janus;
|
||
|
if (type == "screen") {
|
||
|
janus = j.janus2;
|
||
|
instance = "screen";
|
||
|
}
|
||
|
|
||
|
if (janus) {
|
||
|
resolve();
|
||
|
}
|
||
|
else {
|
||
|
if (type == "screen") {
|
||
|
j.janus2 = new Janus({
|
||
|
server: j.parent.streamURL,
|
||
|
success: () => {
|
||
|
j.janus2.attach({
|
||
|
plugin: "janus.plugin.videoroom",
|
||
|
success: (pluginHandle) => {
|
||
|
this.handle(instance, "success", pluginHandle).then(() => {
|
||
|
resolve();
|
||
|
});
|
||
|
},
|
||
|
error: (error) => {
|
||
|
this.handle(instance, "error", error).then(() => {
|
||
|
resolve();
|
||
|
});
|
||
|
},
|
||
|
onmessage: (msg, jsep) => {
|
||
|
if (["joined", "event"].includes(msg["videoroom"])) {
|
||
|
if (msg["videoroom"] == "joined") {
|
||
|
this.pvtid2 = msg["private_id"];
|
||
|
}
|
||
|
this.handle(instance, "message", msg, jsep);
|
||
|
}
|
||
|
if (jsep) {
|
||
|
this.sfu2.handleRemoteJsep({ jsep: jsep });
|
||
|
}
|
||
|
},
|
||
|
onlocalstream: (stream) => {
|
||
|
this.handle(instance, "local", stream);
|
||
|
j.parent.ui.setMute("toggleScreen", 0);
|
||
|
},
|
||
|
oncleanup: () => {
|
||
|
this.handle(instance, "cleanup", { display: j.parent.domain });
|
||
|
j.parent.ui.setMute("toggleScreen", 1);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
j.janus = new Janus({
|
||
|
server: j.parent.streamURL,
|
||
|
success: () => {
|
||
|
j.janus.attach({
|
||
|
plugin: "janus.plugin.videoroom",
|
||
|
success: (pluginHandle) => {
|
||
|
this.handle(instance, "success", pluginHandle).then(() => {
|
||
|
resolve();
|
||
|
});
|
||
|
},
|
||
|
error: (error) => {
|
||
|
this.handle(instance, "error", error).then(() => {
|
||
|
resolve();
|
||
|
});
|
||
|
},
|
||
|
onmessage: (msg, jsep) => {
|
||
|
if (["joined", "event", "talking", "stopped-talking"].includes(msg["videoroom"])) {
|
||
|
if (msg["videoroom"] == "joined") {
|
||
|
this.myid = msg["id"];
|
||
|
this.pvtid = msg["private_id"];
|
||
|
}
|
||
|
this.handle(instance, "message", msg, jsep);
|
||
|
}
|
||
|
|
||
|
if (jsep) {
|
||
|
this.sfu.handleRemoteJsep({ jsep: jsep });
|
||
|
}
|
||
|
},
|
||
|
onlocalstream: (stream) => {
|
||
|
this.handle(instance, "local", stream);
|
||
|
},
|
||
|
oncleanup: () => {
|
||
|
this.handle(instance, "cleanup", { display: j.parent.domain });
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return await done;
|
||
|
}
|
||
|
|
||
|
subscribe(publisher) {
|
||
|
let j = this;
|
||
|
let feed;
|
||
|
j.janus.attach({
|
||
|
plugin: "janus.plugin.videoroom",
|
||
|
opaqueId: j.parent.domain,
|
||
|
success: (pluginHandle) => {
|
||
|
feed = pluginHandle;
|
||
|
let message = {
|
||
|
request: "join",
|
||
|
room: j.parent.conversation,
|
||
|
ptype: "subscriber",
|
||
|
feed: publisher.id,
|
||
|
private_id: j.pvtid
|
||
|
};
|
||
|
feed.send({ message: message });
|
||
|
},
|
||
|
onmessage: function(msg, jsep) {
|
||
|
if (jsep) {
|
||
|
Janus.debug("Handling SDP as well...", jsep);
|
||
|
feed.createAnswer({
|
||
|
jsep: jsep,
|
||
|
media: { audioSend: false, videoSend: false },
|
||
|
success: function(jsep) {
|
||
|
Janus.debug("Got SDP!", jsep);
|
||
|
let message = { request: "start", room: j.parent.conversation };
|
||
|
feed.send({ message: message, jsep: jsep });
|
||
|
},
|
||
|
error: function(error) {
|
||
|
Janus.error("WebRTC error:", error);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
onremotestream: (stream) => {
|
||
|
publisher.stream = stream;
|
||
|
j.handle("video", "remote", publisher);
|
||
|
},
|
||
|
oncleanup: () => {
|
||
|
j.handle("video", "cleanup", publisher);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async handle(instance, type, data=false, jsep=false) {
|
||
|
let done = new Promise(resolve => {
|
||
|
switch (type) {
|
||
|
case "success":
|
||
|
if (instance == "screen") {
|
||
|
this.sfu2 = data;
|
||
|
this.register(this.sfu2);
|
||
|
}
|
||
|
else {
|
||
|
this.sfu = data;
|
||
|
if (Object.keys(this.parent.currentVideoUsers()).includes(this.parent.domain)) {
|
||
|
this.register(this.sfu);
|
||
|
}
|
||
|
else {
|
||
|
this.publishers(this.sfu);
|
||
|
}
|
||
|
}
|
||
|
resolve();
|
||
|
break;
|
||
|
|
||
|
case "error":
|
||
|
resolve();
|
||
|
break;
|
||
|
|
||
|
case "message":
|
||
|
switch (data.videoroom) {
|
||
|
case "talking":
|
||
|
this.talking(data.id, true);
|
||
|
break;
|
||
|
|
||
|
case "stopped-talking":
|
||
|
this.talking(data.id, false);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
$.each(data["publishers"], (k, p) => {
|
||
|
if (Object.keys(this.parent.currentVideoUsers()).includes(p.display)) {
|
||
|
this.subscribe(p);
|
||
|
}
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
resolve();
|
||
|
break;
|
||
|
|
||
|
case "local":
|
||
|
if (instance == "screen") {
|
||
|
this.attachScreen(this.parent.domain, data);
|
||
|
}
|
||
|
else {
|
||
|
this.addCam(this.parent.domain, this.myid);
|
||
|
this.attachVideo(this.parent.domain, data);
|
||
|
}
|
||
|
resolve();
|
||
|
break;
|
||
|
|
||
|
case "remote":
|
||
|
if (data.audio_codec || "talking" in data) {
|
||
|
this.addCam(data.display, data.id);
|
||
|
this.attachVideo(data.display, data.stream);
|
||
|
}
|
||
|
else {
|
||
|
this.attachScreen(data.display, data.stream);
|
||
|
}
|
||
|
resolve();
|
||
|
break;
|
||
|
|
||
|
case "cleanup":
|
||
|
if ((data.audio_codec || !data.id) && instance !== "screen") {
|
||
|
this.removeCam(data.display);
|
||
|
}
|
||
|
else {
|
||
|
this.removeScreen();
|
||
|
}
|
||
|
resolve();
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
return await done;
|
||
|
}
|
||
|
|
||
|
getRandomNumber(digit) {
|
||
|
return Math.random().toFixed(digit).split('.')[1];
|
||
|
}
|
||
|
|
||
|
register(sfu) {
|
||
|
let message = {
|
||
|
request: "join",
|
||
|
room: this.parent.conversation,
|
||
|
ptype: "publisher",
|
||
|
id: this.getRandomNumber(16),
|
||
|
display: this.parent.domain
|
||
|
};
|
||
|
sfu.send({ message: message });
|
||
|
}
|
||
|
|
||
|
publishers(sfu) {
|
||
|
let message = {
|
||
|
request: "listparticipants",
|
||
|
room: this.parent.conversation
|
||
|
};
|
||
|
sfu.send({
|
||
|
"message" : message,
|
||
|
success: (result) => {
|
||
|
$.each(result.participants, (k, p) => {
|
||
|
if (Object.keys(this.parent.currentVideoUsers()).includes(p.display)) {
|
||
|
this.subscribe(p);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
leave(sfu) {
|
||
|
let message = {
|
||
|
request: "leave"
|
||
|
};
|
||
|
sfu.send({ message: message });
|
||
|
}
|
||
|
|
||
|
async publish(type=false) {
|
||
|
let sfu = this.sfu;
|
||
|
let published = "main";
|
||
|
let media = { video: "video", audioRecv: false, videoRecv: false, audioSend: true, videoSend: true };
|
||
|
let message = { request: "configure", audio: true, video: true };
|
||
|
|
||
|
let done = new Promise(resolve => {
|
||
|
if (type == "screen") {
|
||
|
this.init("screen").then(() => {
|
||
|
sfu = this.sfu2;
|
||
|
published = "screen";
|
||
|
media = { video: "screen", audioRecv: false, videoRecv: false, audioSend: false, videoSend: true };
|
||
|
message = { request: "configure", audio: false, video: true };
|
||
|
resolve();
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
resolve();
|
||
|
}
|
||
|
});
|
||
|
await done;
|
||
|
|
||
|
sfu.createOffer({
|
||
|
media: media,
|
||
|
success: (jsep) => {
|
||
|
sfu.send({ message: message, jsep: jsep });
|
||
|
if (type !== "screen") {
|
||
|
sfu.muteVideo();
|
||
|
sfu.muteAudio();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
unpublish(type=false) {
|
||
|
let message = {
|
||
|
request: "unpublish"
|
||
|
};
|
||
|
if (type) {
|
||
|
switch (type) {
|
||
|
case "video":
|
||
|
if (this.sfu) {
|
||
|
this.sfu.send({ message: message });
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "screen":
|
||
|
if (this.sfu2) {
|
||
|
this.sfu2.send({ message: message });
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (this.sfu) {
|
||
|
this.sfu.send({ message: message });
|
||
|
}
|
||
|
if (this.sfu2) {
|
||
|
this.sfu2.send({ message: message });
|
||
|
}
|
||
|
|
||
|
$(".controls > .button").addClass("muted");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
toggleScreen() {
|
||
|
let muted = $(".controls .button[data-action=toggleScreen]").hasClass("muted");
|
||
|
if (muted) {
|
||
|
if (!$(".screen").hasClass("shown")) {
|
||
|
this.publish("screen");
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
this.unpublish("screen");
|
||
|
}
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
addCam(id, jid) {
|
||
|
if (!$(`.cam[data-id=${id}]`).length) {
|
||
|
let html = $(`
|
||
|
<div class="cam" data-id="${id}" data-jid="${jid}">
|
||
|
<div class="background"></div>
|
||
|
<video autoplay playsinline></video>
|
||
|
<div class="info">
|
||
|
<table></table>
|
||
|
</div>
|
||
|
</div>
|
||
|
`);
|
||
|
let row = $(`#users .users tr[data-id="${id}"]`).clone();
|
||
|
row.append($(`
|
||
|
<td>
|
||
|
<div class="icon voice"></div>
|
||
|
</td>
|
||
|
`));
|
||
|
html.find("table").append(row);
|
||
|
|
||
|
let row2 = $(`#users .users tr[data-id="${id}"]`).clone();
|
||
|
html.find(".background").append(row2);
|
||
|
|
||
|
if (id == this.parent.domain) {
|
||
|
html.find("video")[0].muted = true;
|
||
|
html.addClass("me");
|
||
|
}
|
||
|
|
||
|
$(".videoHolder .cams").append(html);
|
||
|
}
|
||
|
if (id && jid) {
|
||
|
this.parent.ui.setData($(`.cam[data-id=${id}]`), "jid", jid);
|
||
|
}
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
attachVideo(id, video) {
|
||
|
Janus.attachMediaStream($(`.cam[data-id=${id}] video`).get(0), video);
|
||
|
this.dish.resize();
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
attachScreen(id, video) {
|
||
|
let html = $(`.videoHolder .screen`);
|
||
|
let table = html.find("table");
|
||
|
|
||
|
let row = $(`#users .users tr[data-id="${id}"]`).clone();
|
||
|
table.empty();
|
||
|
table.append(row);
|
||
|
|
||
|
let screen = html.find("video").get(0);
|
||
|
screen.muted = true;
|
||
|
Janus.attachMediaStream(screen, video);
|
||
|
$(".screen").addClass("shown");
|
||
|
this.dish.resize();
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
removeCam(id) {
|
||
|
$(`.cam[data-id=${id}]`).remove();
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
removeScreen() {
|
||
|
$(".screen").removeClass("shown");
|
||
|
this.dish.resize();
|
||
|
}
|
||
|
|
||
|
mute(type, bool) {
|
||
|
switch (type) {
|
||
|
case "audio":
|
||
|
if (bool) {
|
||
|
this.sfu.muteAudio();
|
||
|
}
|
||
|
else {
|
||
|
this.sfu.unmuteAudio();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "video":
|
||
|
if (bool) {
|
||
|
this.sfu.muteVideo();
|
||
|
}
|
||
|
else {
|
||
|
this.sfu.unmuteVideo();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
talking(id, bool) {
|
||
|
if (bool) {
|
||
|
$(`.cam[data-jid=${id}]`).addClass("talking");
|
||
|
}
|
||
|
else {
|
||
|
$(`.cam[data-jid=${id}]`).removeClass("talking");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
close() {
|
||
|
$(".cam").remove();
|
||
|
|
||
|
if (this.janus2) {
|
||
|
this.leave(this.sfu2);
|
||
|
this.sfu2 = null;
|
||
|
this.janus2 = null;
|
||
|
}
|
||
|
|
||
|
if (this.janus) {
|
||
|
this.leave(this.sfu);
|
||
|
this.sfu = null;
|
||
|
this.janus = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|