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); console.log("vid", elm);
box.append(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) { decorateLive(id: string) {
if (!this.voice) return; if (!this.voice) return;
const box = this.liveMap.get(id); const box = this.liveMap.get(id);
if (!box) return; if (!box) return;
box.innerHTML = ""; box.innerHTML = "";
const live = this.voice.getLive(id); const live = this.voice.getLive(id);
const self = id === this.localuser.user.id;
if (!this.voice.open) { if (!this.voice.open) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = I18n.vc.joinForStream(); span.textContent = I18n.vc.joinForStream();
@ -1137,18 +1151,27 @@ class Channel extends SnowFlake {
} else if (live) { } else if (live) {
const leave = document.createElement("button"); const leave = document.createElement("button");
leave.classList.add("leave"); leave.classList.add("leave");
leave.textContent = I18n.vc.leavestream(); leave.textContent = self ? I18n.vc.stopstream() : I18n.vc.leavestream();
leave.onclick = () => { leave.onclick = (e) => {
e.stopImmediatePropagation();
if (self) {
this.voice?.stopStream();
} else {
this.voice?.leaveLive(id); this.voice?.leaveLive(id);
}
}; };
box.append(live, leave); box.append(live, leave);
} else { } else if (!self) {
const joinB = document.createElement("button"); const joinB = document.createElement("button");
joinB.textContent = I18n.vc.joinstream(); joinB.textContent = I18n.vc.joinstream();
joinB.classList.add("joinb"); joinB.classList.add("joinb");
box.append(joinB); box.append(joinB);
joinB.onclick = () => { joinB.onclick = () => {
if (!this.voice) return; if (!this.voice) return;
box.innerHTML = "";
const span = document.createElement("span");
span.textContent = I18n.vc.joiningStream();
box.append(span);
this.voice.joinLive(id); this.voice.joinLive(id);
}; };
} }
@ -1194,6 +1217,9 @@ class Channel extends SnowFlake {
} else if (!live && change.live && box) { } else if (!live && change.live && box) {
const livediv = document.createElement("div"); const livediv = document.createElement("div");
this.liveMap.set(id, livediv); this.liveMap.set(id, livediv);
livediv.onclick = () => {
this.makeBig(livediv);
};
box.parentElement?.prepend(livediv); box.parentElement?.prepend(livediv);
this.decorateLive(id); this.decorateLive(id);
} }
@ -1217,6 +1243,9 @@ class Channel extends SnowFlake {
async makeUserBox(user: User, users: HTMLElement) { async makeUserBox(user: User, users: HTMLElement) {
const memb = Member.resolveMember(user, this.guild); const memb = Member.resolveMember(user, this.guild);
const box = document.createElement("div"); const box = document.createElement("div");
box.onclick = () => {
this.makeBig(box);
};
this.boxMap.set(user.id, box); this.boxMap.set(user.id, box);
if (user.accent_color != undefined) { if (user.accent_color != undefined) {
box.style.setProperty( box.style.setProperty(
@ -1297,6 +1326,7 @@ class Channel extends SnowFlake {
updateVideoIcon(); updateVideoIcon();
video.onclick = async () => { video.onclick = async () => {
if (!this.voice) return; if (!this.voice) return;
if (!this.voice.open) return;
if (this.localuser.voiceFactory?.video) { if (this.localuser.voiceFactory?.video) {
this.voice.stopVideo(); this.voice.stopVideo();
} else { } else {
@ -1317,23 +1347,39 @@ class Channel extends SnowFlake {
video.classList.add("callVoiceIcon"); video.classList.add("callVoiceIcon");
const updateLiveIcon = () => { const updateLiveIcon = () => {
lspan.classList.remove("svg-video", "svg-novideo"); lspan.classList.remove("svg-stream", "svg-stopstream");
lspan.classList.add(true ? "svg-stream" : "svg-stopstream"); lspan.classList.add(this.voice?.isLive() ? "svg-stopstream" : "svg-stream");
}; };
const live = document.createElement("div"); const live = document.createElement("div");
const lspan = document.createElement("span"); const lspan = document.createElement("span");
live.append(lspan); live.append(lspan);
updateLiveIcon(); updateLiveIcon();
live.onclick = async () => { live.onclick = async () => {
if (!this.voice?.open) return;
if (this.voice?.isLive()) {
this.voice?.stopStream();
} else {
const stream = await navigator.mediaDevices.getDisplayMedia(); const stream = await navigator.mediaDevices.getDisplayMedia();
this.voice?.createLive(this.localuser.user.id, stream); const v = await this.voice?.createLive(stream);
console.log(v);
}
updateLiveIcon(); updateLiveIcon();
}; };
live.classList.add("callVoiceIcon"); live.classList.add("callVoiceIcon");
buttonRow.append(mute, call, video, live); buttonRow.append(mute, video, live, call);
const users = document.createElement("div"); 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"); users.classList.add("voiceUsers");
this.voice.userids.forEach(async (_, id) => { this.voice.userids.forEach(async (_, id) => {
@ -1351,6 +1397,7 @@ class Channel extends SnowFlake {
this.boxVid(id, vid); this.boxVid(id, vid);
}; };
this.voice.onGotStream = (_vid, id) => { this.voice.onGotStream = (_vid, id) => {
updateLiveIcon();
this.decorateLive(id); this.decorateLive(id);
}; };
this.voice.onconnect = () => { this.voice.onconnect = () => {
@ -1365,6 +1412,7 @@ class Channel extends SnowFlake {
this.voice.onLeave = () => { this.voice.onLeave = () => {
updateCallIcon(); updateCallIcon();
updateVideoIcon();
for (const [id] of this.boxMap) { for (const [id] of this.boxMap) {
this.purgeVid(id); 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 { .leave:hover {
background: color-mix(in srgb, var(--red) 85%, white); background: color-mix(in srgb, var(--red) 85%, white);
} }
.voiceUsers > * { .voiceUsers > * {
background: var(--accent_color, var(--primary-bg)); background: var(--accent_color, var(--primary-bg));
border-radius: 8px; border-radius: 8px;
@ -225,11 +226,11 @@ body {
box-sizing: border-box; box-sizing: border-box;
margin: 8px; margin: 8px;
width: 340px; width: 340px;
height: 220px; aspect-ratio: 3/2;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
justify-content: center;
cursor: pointer;
img { img {
width: 60px; width: 60px;
height: 60px; height: 60px;
@ -237,12 +238,20 @@ body {
} }
video { video {
position: absolute; position: absolute;
top: 0px; flex-grow: 1;
left: 0px;
width: 100%;
height: 100%;
} }
} }
.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 > * { .buttonRow > * {
margin-right: 6px; margin-right: 6px;
width: 54px; width: 54px;
@ -529,6 +538,11 @@ textarea {
mask: url(/icons/stream.svg); mask: url(/icons/stream.svg);
mask-size: contain !important; mask-size: contain !important;
} }
.svg-stopstream {
mask: url(/icons/stopstream.svg);
mask-size: contain !important;
background-color: var(--red);
}
.svg-video { .svg-video {
mask: url(/icons/video.svg); mask: url(/icons/video.svg);
mask-size: contain !important; mask-size: contain !important;

View file

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

View file

@ -8,7 +8,9 @@
"vc": { "vc": {
"joinstream": "Watch stream", "joinstream": "Watch stream",
"leavestream": "Leave stream", "leavestream": "Leave stream",
"joinForStream": "Join the VC to watch" "joinForStream": "Join the VC to watch",
"stopstream": "Stop stream",
"joiningStream": "Joining stream..."
}, },
"readableName": "English", "readableName": "English",
"pinMessage": "Pin Message", "pinMessage": "Pin Message",