some VC updates

This commit is contained in:
MathMan05 2025-05-23 13:22:28 -05:00
parent ac724bd8dd
commit 3b848d42b7
5 changed files with 128 additions and 22 deletions

View file

@ -1124,12 +1124,26 @@ class Channel extends SnowFlake {
console.log("vid", elm);
box.append(elm);
}
makeBig(box: HTMLElement) {
const par = box.parentElement;
if (!par) return;
if (par.children[0] !== box || !box.classList.contains("bigBox")) {
box.classList.add("bigBox");
if (par.children[0] !== box) {
par.children[0].classList.remove("bigBox");
}
} else {
par.children[0].classList.remove("bigBox");
}
par.prepend(box);
}
decorateLive(id: string) {
if (!this.voice) return;
const box = this.liveMap.get(id);
if (!box) return;
box.innerHTML = "";
const live = this.voice.getLive(id);
const self = id === this.localuser.user.id;
if (!this.voice.open) {
const span = document.createElement("span");
span.textContent = I18n.vc.joinForStream();
@ -1137,18 +1151,27 @@ class Channel extends SnowFlake {
} else if (live) {
const leave = document.createElement("button");
leave.classList.add("leave");
leave.textContent = I18n.vc.leavestream();
leave.onclick = () => {
this.voice?.leaveLive(id);
leave.textContent = self ? I18n.vc.stopstream() : I18n.vc.leavestream();
leave.onclick = (e) => {
e.stopImmediatePropagation();
if (self) {
this.voice?.stopStream();
} else {
this.voice?.leaveLive(id);
}
};
box.append(live, leave);
} else {
} else if (!self) {
const joinB = document.createElement("button");
joinB.textContent = I18n.vc.joinstream();
joinB.classList.add("joinb");
box.append(joinB);
joinB.onclick = () => {
if (!this.voice) return;
box.innerHTML = "";
const span = document.createElement("span");
span.textContent = I18n.vc.joiningStream();
box.append(span);
this.voice.joinLive(id);
};
}
@ -1194,6 +1217,9 @@ class Channel extends SnowFlake {
} else if (!live && change.live && box) {
const livediv = document.createElement("div");
this.liveMap.set(id, livediv);
livediv.onclick = () => {
this.makeBig(livediv);
};
box.parentElement?.prepend(livediv);
this.decorateLive(id);
}
@ -1217,6 +1243,9 @@ class Channel extends SnowFlake {
async makeUserBox(user: User, users: HTMLElement) {
const memb = Member.resolveMember(user, this.guild);
const box = document.createElement("div");
box.onclick = () => {
this.makeBig(box);
};
this.boxMap.set(user.id, box);
if (user.accent_color != undefined) {
box.style.setProperty(
@ -1297,6 +1326,7 @@ class Channel extends SnowFlake {
updateVideoIcon();
video.onclick = async () => {
if (!this.voice) return;
if (!this.voice.open) return;
if (this.localuser.voiceFactory?.video) {
this.voice.stopVideo();
} else {
@ -1317,23 +1347,39 @@ class Channel extends SnowFlake {
video.classList.add("callVoiceIcon");
const updateLiveIcon = () => {
lspan.classList.remove("svg-video", "svg-novideo");
lspan.classList.add(true ? "svg-stream" : "svg-stopstream");
lspan.classList.remove("svg-stream", "svg-stopstream");
lspan.classList.add(this.voice?.isLive() ? "svg-stopstream" : "svg-stream");
};
const live = document.createElement("div");
const lspan = document.createElement("span");
live.append(lspan);
updateLiveIcon();
live.onclick = async () => {
const stream = await navigator.mediaDevices.getDisplayMedia();
this.voice?.createLive(this.localuser.user.id, stream);
if (!this.voice?.open) return;
if (this.voice?.isLive()) {
this.voice?.stopStream();
} else {
const stream = await navigator.mediaDevices.getDisplayMedia();
const v = await this.voice?.createLive(stream);
console.log(v);
}
updateLiveIcon();
};
live.classList.add("callVoiceIcon");
buttonRow.append(mute, call, video, live);
buttonRow.append(mute, video, live, call);
const users = document.createElement("div");
const mut = new MutationObserver(() => {
const arr = Array.from(users.children);
const box = arr.find((_) => _.classList.contains("bigBox"));
if (box && arr[0] !== box) {
users.prepend(box);
}
});
mut.observe(users, {
childList: true,
});
users.classList.add("voiceUsers");
this.voice.userids.forEach(async (_, id) => {
@ -1351,6 +1397,7 @@ class Channel extends SnowFlake {
this.boxVid(id, vid);
};
this.voice.onGotStream = (_vid, id) => {
updateLiveIcon();
this.decorateLive(id);
};
this.voice.onconnect = () => {
@ -1365,6 +1412,7 @@ class Channel extends SnowFlake {
this.voice.onLeave = () => {
updateCallIcon();
updateVideoIcon();
for (const [id] of this.boxMap) {
this.purgeVid(id);
}

View file

@ -0,0 +1 @@
<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg"><g style="opacity:1"><path d="M6.9 1.8a4 4 0 0 0-4 4v21.6a4 4 0 0 0 4 4h33.9a4 4 0 0 0 4-4V5.8a4 4 0 0 0-4-4zm5 3a2 2 0 0 1 1.1.5L23.2 14l10.1-8.7a2 2 0 0 1 1.5-.4 2 2 0 0 1 1.3.7 2 2 0 0 1-.2 2.8l-9.6 8.2 9.6 8.3a2 2 0 0 1 .2 2.8 2 2 0 0 1-2.8.2l-10.1-8.6L13 27.9a2 2 0 0 1-2.9-.2 2 2 0 0 1 .3-2.8l9.6-8.3-9.7-8.2a2 2 0 0 1-.2-2.8 2 2 0 0 1 1.4-.7 2 2 0 0 1 .2 0z" style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-linecap:round;stroke-linejoin:round;enable-background:accumulate;stop-color:#000;stop-opacity:1"/></g><path style="fill:#000;stroke:#000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="M21.8 28.2h3.3v10.7h-3.3z"/><path style="fill:#000;stroke:#000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="M11.9 41.7h23.6v2.2H11.9z"/></svg>

After

Width:  |  Height:  |  Size: 920 B

View file

@ -218,6 +218,7 @@ body {
.leave:hover {
background: color-mix(in srgb, var(--red) 85%, white);
}
.voiceUsers > * {
background: var(--accent_color, var(--primary-bg));
border-radius: 8px;
@ -225,11 +226,11 @@ body {
box-sizing: border-box;
margin: 8px;
width: 340px;
height: 220px;
aspect-ratio: 3/2;
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 60px;
height: 60px;
@ -237,12 +238,20 @@ body {
}
video {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
flex-grow: 1;
}
}
.voiceUsers:has(.bigBox) > * {
width: 220px;
flex-grow: 0;
flex-shrink: 0;
}
.bigBox {
width: min(80%, 600px) !important;
height: unset;
margin: 0 calc((100% - min(80%, 600px)) / 2);
}
.buttonRow > * {
margin-right: 6px;
width: 54px;
@ -529,6 +538,11 @@ textarea {
mask: url(/icons/stream.svg);
mask-size: contain !important;
}
.svg-stopstream {
mask: url(/icons/stopstream.svg);
mask-size: contain !important;
background-color: var(--red);
}
.svg-video {
mask: url(/icons/video.svg);
mask-size: contain !important;

View file

@ -70,6 +70,7 @@ class VoiceFactory {
},
});
}
updateSelf() {
if (this.currentVoice && this.currentVoice.open) {
this.handleGateway({
@ -111,6 +112,16 @@ class VoiceFactory {
op: 4,
};
}
leaveLive() {
const userid = this.settings.id;
const stream_key = `${this.curGuild === "@me" ? "call" : `guild:${this.curGuild}`}:${this.curChan}:${userid}`;
this.handleGateway({
op: 19,
d: {
stream_key,
},
});
}
live = new Map<string, (res: Voice) => void>();
steamTokens = new Map<string, Promise<[string, string]>>();
steamTokensRes = new Map<string, (res: [string, string]) => void>();
@ -134,7 +145,8 @@ class VoiceFactory {
}
islive = false;
liveStream?: MediaStream;
async createLive(userid: string, stream: MediaStream) {
async createLive(stream: MediaStream) {
const userid = this.settings.id;
this.islive = true;
this.liveStream = stream;
const stream_key = `${this.curGuild === "@me" ? "call" : `guild:${this.curGuild}`}:${this.curChan}:${userid}`;
@ -207,6 +219,9 @@ class VoiceFactory {
};
voice2.gotStream(voice, user);
console.warn(voice2);
const res = this.live.get(create.d.stream_key);
if (res) res(voice);
}
}
streamServerUpdate(update: streamServerUpdate) {
@ -410,6 +425,7 @@ class Voice {
if (!ld) throw new Error("localDescription isn't defined");
const parsed = Voice.parsesdp(ld.sdp);
const group = parsed.atr.get("group");
console.warn(parsed);
if (!group) throw new Error("group isn't in sdp");
const [_, ...bundles] = (group.entries().next().value as [string, string])[0].split(" ");
bundles[bundles.length - 1] = bundles[bundles.length - 1].replace("\r", "");
@ -508,13 +524,19 @@ a=rtcp-mux\r`;
const sendOffer = async () => {
console.trace("neg need");
await pc.setLocalDescription();
console.log("set local");
const senders = this.senders.difference(this.ssrcMap);
console.log(senders, this.ssrcMap);
for (const sender of senders) {
for (const thing of (await sender.getStats()) as Map<string, any>) {
console.log(sender);
const d = (await sender.getStats()) as Map<string, any>;
console.log([...d]);
for (const thing of d) {
if (thing[1].ssrc) {
this.ssrcMap.set(sender, thing[1].ssrc);
this.makeOp12(sender);
console.warn("ssrc");
}
}
}
@ -532,14 +554,20 @@ a=rtcp-mux\r`;
this.status = "Done";
}
};
function logState(thing: string, message = "") {
console.log(thing + (message ? ":" + message : ""));
}
pc.addEventListener("negotiationneeded", async () => {
logState("negotiationneeded");
await sendOffer();
console.log(this.ssrcMap);
});
pc.addEventListener("signalingstatechange", async () => {
logState("signalingstatechange", pc.signalingState);
detectDone();
while (!this.counter) await new Promise((res) => setTimeout(res, 100));
if (this.pc && this.counter) {
console.warn("in here :3");
if (pc.signalingState === "have-local-offer") {
const counter = this.counter;
const remote: {sdp: string; type: RTCSdpType} = {
@ -549,17 +577,20 @@ a=rtcp-mux\r`;
console.log(remote);
await pc.setRemoteDescription(remote);
}
} else {
console.warn("uh oh!");
}
});
pc.addEventListener("connectionstatechange", async () => {
logState("connectionstatechange", pc.connectionState);
detectDone();
if (pc.connectionState === "connecting") {
await pc.setLocalDescription();
}
});
pc.addEventListener("icegatheringstatechange", async () => {
logState("icegatheringstatechange", pc.iceGatheringState);
detectDone();
console.log("icegatheringstatechange", pc.iceGatheringState, this.pc, this.counter);
if (this.pc && this.counter) {
if (pc.iceGatheringState === "complete") {
pc.setLocalDescription();
@ -567,6 +598,7 @@ a=rtcp-mux\r`;
}
});
pc.addEventListener("iceconnectionstatechange", async () => {
logState("iceconnectionstatechange", pc.iceConnectionState);
detectDone();
if (pc.iceConnectionState === "checking") {
sendOffer();
@ -754,14 +786,17 @@ a=rtcp-mux\r`;
}
liveMap = new Map<string, HTMLVideoElement>();
voiceMap = new Map<string, Voice>();
isLive() {
return !!this.voiceMap.get(this.userid);
}
getLive(id: string) {
return this.liveMap.get(id);
}
joinLive(id: string) {
return this.owner.joinLive(id);
}
createLive(id: string, stream: MediaStream) {
return this.owner.createLive(id, stream);
createLive(stream: MediaStream) {
return this.owner.createLive(stream);
}
leaveLive(id: string) {
const v = this.voiceMap.get(id);
@ -771,6 +806,10 @@ a=rtcp-mux\r`;
this.liveMap.delete(id);
this.onLeaveStream(id);
}
stopStream() {
this.leaveLive(this.userid);
this.owner.leaveLive();
}
onLeaveStream = (_user: string) => {};
onGotStream = (_v: HTMLVideoElement, _user: string) => {};
gotStream(voice: Voice, user: string) {
@ -1168,7 +1207,9 @@ a=rtcp-mux\r`;
console.warn("leave");
this.open = false;
this.status = "Left voice chat";
if (!this.settings.stream) this.owner.video = false;
this.onLeave();
for (const thing of this.liveMap) {
this.leaveLive(thing[0]);
}