some additional patches for live streaming

not quite done yet, but it's getting closer :3
This commit is contained in:
MathMan05 2025-05-21 19:44:03 -05:00
parent 2cbb5aecbf
commit 8de2d8b351
2 changed files with 110 additions and 35 deletions

View file

@ -253,6 +253,8 @@ body {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
z-index: 1;
* { * {
width: 32px !important; width: 32px !important;
height: 32px !important; height: 32px !important;

View file

@ -147,6 +147,13 @@ class VoiceFactory {
preferred_region: null, preferred_region: null,
}, },
}); });
this.handleGateway({
op: 22,
d: {
paused: false,
stream_key,
},
});
return new Promise<Voice>(async (res) => { return new Promise<Voice>(async (res) => {
this.live.set(stream_key, res); this.live.set(stream_key, res);
this.steamTokens.set( this.steamTokens.set(
@ -164,7 +171,12 @@ class VoiceFactory {
if (create.d.stream_key.startsWith("guild")) { if (create.d.stream_key.startsWith("guild")) {
const [_, _guild, chan, user] = create.d.stream_key.split(":"); const [_, _guild, chan, user] = create.d.stream_key.split(":");
const voice2 = this.voiceChannels.get(chan); const voice2 = this.voiceChannels.get(chan);
if (!voice2 || !voice2.session_id) throw new Error("oops"); if (!voice2 || !voice2.session_id) throw new Error("oops");
if (voice2.voiceMap.has(user)) {
voice2.makeOp12();
return;
}
let stream: undefined | MediaStream = undefined; let stream: undefined | MediaStream = undefined;
console.error(user, this.settings.id); console.error(user, this.settings.id);
if (user === this.settings.id) { if (user === this.settings.id) {
@ -175,7 +187,6 @@ class VoiceFactory {
{ {
bitrate: 10000, bitrate: 10000,
stream: true, stream: true,
live: stream,
}, },
{ {
url: endpoint, url: endpoint,
@ -185,6 +196,15 @@ class VoiceFactory {
); );
voice.join(); voice.join();
voice.startWS(voice2.session_id, create.d.rtc_server_id); voice.startWS(voice2.session_id, create.d.rtc_server_id);
let video = false;
voice.onSatusChange = (e) => {
console.warn(e);
if (e === "Done" && stream && !video) {
console.error("starting to stream");
voice.startVideo(stream);
video = true;
}
};
voice2.gotStream(voice, user); voice2.gotStream(voice, user);
} }
@ -197,6 +217,16 @@ class VoiceFactory {
voiceStateUpdate(update: voiceStatus) { voiceStateUpdate(update: voiceStatus) {
const prev = this.userMap.get(update.user_id); const prev = this.userMap.get(update.user_id);
console.log(prev, this.userMap); console.log(prev, this.userMap);
if (update.user_id === this.settings.id && this.liveStream && !update.self_stream) {
const stream_key = `${this.curGuild === "@me" ? "call" : `guild:${this.curGuild}`}:${this.curChan}:${this.settings.id}`;
this.handleGateway({
op: 22,
d: {
paused: false,
stream_key,
},
});
}
if (prev && prev !== this.voiceChannels.get(update.channel_id)) { if (prev && prev !== this.voiceChannels.get(update.channel_id)) {
prev.disconnect(update.user_id); prev.disconnect(update.user_id);
this.onLeave(prev); this.onLeave(prev);
@ -415,7 +445,7 @@ a=group:BUNDLE ${bundles.join(" ")}\r`;
for (const grouping of parsed.medias) { for (const grouping of parsed.medias) {
let mode = "inactive"; let mode = "inactive";
if (i < 2) { if (i < 2) {
mode = "sendonly"; mode = "recvonly";
} }
if (grouping.media === "audio") { if (grouping.media === "audio") {
build += ` build += `
@ -469,7 +499,6 @@ a=rtcp-mux\r`;
i++; i++;
} }
build += "\n"; build += "\n";
console.log(build);
return build; return build;
} }
counter?: string; counter?: string;
@ -489,12 +518,26 @@ a=rtcp-mux\r`;
} }
} }
} }
if (this.senders.size === 0) {
console.warn("this was ran :3");
this.makeOp12();
}
};
const detectDone = () => {
if (
pc.signalingState === "stable" &&
pc.iceConnectionState === "connected" &&
pc.connectionState === "connected"
) {
this.status = "Done";
}
}; };
pc.addEventListener("negotiationneeded", async () => { pc.addEventListener("negotiationneeded", async () => {
await sendOffer(); await sendOffer();
console.log(this.ssrcMap); console.log(this.ssrcMap);
}); });
pc.addEventListener("signalingstatechange", async () => { pc.addEventListener("signalingstatechange", async () => {
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) {
if (pc.signalingState === "have-local-offer") { if (pc.signalingState === "have-local-offer") {
@ -509,11 +552,13 @@ a=rtcp-mux\r`;
} }
}); });
pc.addEventListener("connectionstatechange", async () => { pc.addEventListener("connectionstatechange", async () => {
detectDone();
if (pc.connectionState === "connecting") { if (pc.connectionState === "connecting") {
await pc.setLocalDescription(); await pc.setLocalDescription();
} }
}); });
pc.addEventListener("icegatheringstatechange", async () => { pc.addEventListener("icegatheringstatechange", async () => {
detectDone();
console.log("icegatheringstatechange", pc.iceGatheringState, this.pc, this.counter); 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") {
@ -521,13 +566,19 @@ a=rtcp-mux\r`;
} }
} }
}); });
pc.addEventListener("iceconnectionstatechange", async () => {
detectDone();
if (pc.iceConnectionState === "checking") {
sendOffer();
}
});
} }
} }
async makeOp12( async makeOp12(
sender: RTCRtpSender | undefined | [RTCRtpSender, number] = this.ssrcMap.entries().next().value, sender: RTCRtpSender | undefined | [RTCRtpSender, number] = this.ssrcMap.entries().next().value,
) { ) {
console.warn("making 12?");
if (!this.ws) return; if (!this.ws) return;
if (!sender) return;
if (sender instanceof Array) { if (sender instanceof Array) {
sender = sender[0]; sender = sender[0];
} }
@ -537,20 +588,26 @@ a=rtcp-mux\r`;
let width = 1280; let width = 1280;
let height = 720; let height = 720;
if (this.cam && this.cammera) { if (this.cam && this.cammera) {
const stats = (await this.cam.sender.getStats()) as Map<string, any>; do {
Array.from(stats).forEach((_) => { const stats = (await this.cam.sender.getStats()) as Map<string, any>;
if (_[1].ssrc) { Array.from(stats).forEach((_) => {
video_ssrc = _[1].ssrc; if (_[1].ssrc) {
} video_ssrc = _[1].ssrc;
if (_[1].rtxSsrc) { console.warn(_);
rtx_ssrc = _[1].rtxSsrc; }
console.log(_); if (_[1].rtxSsrc) {
} rtx_ssrc = _[1].rtxSsrc;
}); }
const settings = this.cammera.getSettings(); });
console.error(settings); const settings = this.cammera.getSettings();
console.error(settings);
await new Promise((res) => setTimeout(res, 100));
} while (!video_ssrc || !rtx_ssrc);
//width = settings.width || 0; //width = settings.width || 0;
//height = settings.height || 0; //height = settings.height || 0;
} else if (!sender) {
return;
} }
console.log(this.ssrcMap); console.log(this.ssrcMap);
@ -558,7 +615,8 @@ a=rtcp-mux\r`;
JSON.stringify({ JSON.stringify({
op: 12, op: 12,
d: { d: {
audio_ssrc: this.ssrcMap.get(sender), audio_ssrc:
sender?.track?.kind === "audio" ? this.ssrcMap.get(sender as RTCRtpSender) : 0,
video_ssrc, video_ssrc,
rtx_ssrc, rtx_ssrc,
streams: [ streams: [
@ -695,7 +753,7 @@ a=rtcp-mux\r`;
}); });
} }
liveMap = new Map<string, HTMLVideoElement>(); liveMap = new Map<string, HTMLVideoElement>();
private voiceMap = new Map<string, Voice>(); voiceMap = new Map<string, Voice>();
getLive(id: string) { getLive(id: string) {
return this.liveMap.get(id); return this.liveMap.get(id);
} }
@ -723,15 +781,15 @@ a=rtcp-mux\r`;
this.voiceMap.set(user, voice); this.voiceMap.set(user, voice);
} }
videoStarted = false; videoStarted = false;
async startVideo(caml: MediaStream, early = false) { async startVideo(caml: MediaStream) {
console.warn("test test test test video sent!");
while (!this.cam) { while (!this.cam) {
await new Promise((res) => setTimeout(res, 100)); await new Promise((res) => setTimeout(res, 100));
} }
console.warn("test test test test video sent!");
const tracks = caml.getVideoTracks(); const tracks = caml.getVideoTracks();
const [cam] = tracks; const [cam] = tracks;
this.owner.video = true; if (!this.settings.stream) this.owner.video = true;
this.cammera = cam; this.cammera = cam;
@ -742,10 +800,16 @@ a=rtcp-mux\r`;
video.autoplay = true; video.autoplay = true;
this.cam.direction = "sendonly"; this.cam.direction = "sendonly";
const sender = this.cam.sender; const sender = this.cam.sender;
if (!early) { this.senders.add(sender);
await sender.replaceTrack(cam);
this.pc?.setLocalDescription();
sender.setStreams(caml);
await sender.replaceTrack(cam);
console.warn("replaced track", cam);
this.pc?.setLocalDescription();
if (this.settings.stream) {
this.makeOp12();
} else {
this.owner.updateSelf(); this.owner.updateSelf();
} }
} }
@ -790,23 +854,30 @@ a=rtcp-mux\r`;
this.recivers.add(e.receiver); this.recivers.add(e.receiver);
console.log(this.recivers); console.log(this.recivers);
}; };
const audioStream = await navigator.mediaDevices.getUserMedia({video: false, audio: true});
const [track] = audioStream.getAudioTracks();
if (!this.settings.stream) { if (!this.settings.stream) {
const audioStream = await navigator.mediaDevices.getUserMedia({video: false, audio: true});
const [track] = audioStream.getAudioTracks();
this.setupMic(audioStream); this.setupMic(audioStream);
const sender = pc.addTrack(track); const sender = pc.addTrack(track);
this.cam = pc.addTransceiver("video", {
direction: "sendonly",
sendEncodings: [
{active: true, maxBitrate: 2500000, scaleResolutionDownBy: 1, maxFramerate: 20},
],
});
this.mic = sender; this.mic = sender;
this.micTrack = track; this.micTrack = track;
track.enabled = !this.owner.mute; track.enabled = !this.owner.mute;
this.senders.add(sender); this.senders.add(sender);
console.log(sender); console.log(sender);
} else {
pc.addTransceiver("audio", {
direction: "inactive",
streams: [],
sendEncodings: [{active: true, maxBitrate: this.settings.bitrate}],
});
} }
this.cam = pc.addTransceiver("video", {
direction: "sendonly",
sendEncodings: [
{active: true, maxBitrate: 2500000, scaleResolutionDownBy: 1, maxFramerate: 20},
],
});
const count = this.settings.stream ? 1 : 10; const count = this.settings.stream ? 1 : 10;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
pc.addTransceiver("audio", { pc.addTransceiver("audio", {
@ -822,7 +893,8 @@ a=rtcp-mux\r`;
{active: true, maxBitrate: 2500000, scaleResolutionDownBy: 1, maxFramerate: 20}, {active: true, maxBitrate: 2500000, scaleResolutionDownBy: 1, maxFramerate: 20},
], ],
}); });
this.startVideo(this.settings.live, true); await this.startVideo(this.settings.live);
this.makeOp12();
} else { } else {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
pc.addTransceiver("video", { pc.addTransceiver("video", {
@ -1078,10 +1150,11 @@ a=rtcp-mux\r`;
user_id: this.userid, user_id: this.userid,
session_id, session_id,
token: this.urlobj.token, token: this.urlobj.token,
video: false, max_secure_frames_version: 0,
video: !!this.settings.live,
streams: [ streams: [
{ {
type: "video", type: this.settings.live ? "screen" : "video",
rid: "100", rid: "100",
quality: 100, quality: 100,
}, },