fix
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m17s

This commit is contained in:
Smile Rex
2026-01-26 19:43:17 +03:00
parent 3894a8a393
commit 4bd57e1d12

View File

@@ -1,92 +1,124 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
let localVideo: HTMLVideoElement; let localVideo: HTMLVideoElement;
let localStream: MediaStream; let localStream: MediaStream;
let pc: RTCPeerConnection;
let ws: WebSocket;
function createPC() { let ws: WebSocket;
pc = new RTCPeerConnection({ let pc: RTCPeerConnection;
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});
pc.ontrack = (e) => { /* ---------- helpers ---------- */
const video = document.createElement("video");
video.autoplay = true;
video.playsInline = true;
video.srcObject = e.streams[0];
document.body.appendChild(video);
};
pc.onicecandidate = (e) => { function waitForWsOpen(ws: WebSocket): Promise<void> {
if (e.candidate) { return new Promise((resolve) => {
ws.send( if (ws.readyState === WebSocket.OPEN) {
JSON.stringify({ resolve();
type: "ice", } else {
data: e.candidate, ws.addEventListener("open", () => resolve(), { once: true });
}), }
); });
} }
};
}
async function negotiate() { /* ---------- WebRTC ---------- */
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send( function createPeerConnection() {
JSON.stringify({ pc = new RTCPeerConnection({
type: "offer", iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
data: offer, });
}),
);
}
onMount(async () => { pc.ontrack = (e) => {
localStream = await navigator.mediaDevices.getUserMedia({ const video = document.createElement("video");
video: true, video.autoplay = true;
audio: true, video.playsInline = true;
}); video.srcObject = e.streams[0];
document.body.appendChild(video);
};
localVideo.srcObject = localStream; pc.onicecandidate = (e) => {
if (!e.candidate) return;
if (ws.readyState !== WebSocket.OPEN) return;
ws = new WebSocket("wss://meet-api.quizer.space/ws"); ws.send(
JSON.stringify({
type: "ice",
data: e.candidate,
}),
);
};
}
createPC(); async function negotiate() {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
for (const track of localStream.getTracks()) { ws.send(
pc.addTrack(track, localStream); JSON.stringify({
} type: "offer",
data: offer,
}),
);
}
ws.onmessage = async (e) => { /* ---------- lifecycle ---------- */
const msg = JSON.parse(e.data);
switch (msg.type) { onMount(async () => {
case "answer": /* 1. media */
await pc.setRemoteDescription(msg.data); localStream = await navigator.mediaDevices.getUserMedia({
break; video: true,
audio: true,
});
case "ice": localVideo.srcObject = localStream;
await pc.addIceCandidate(msg.data);
break;
case "renegotiate": /* 2. websocket */
await negotiate(); ws = new WebSocket("wss://meet-api.quizer.space/ws");
break; await waitForWsOpen(ws);
}
};
await negotiate(); console.log("[ws] connected");
});
/* 3. peer connection */
createPeerConnection();
for (const track of localStream.getTracks()) {
pc.addTrack(track, localStream);
}
/* 4. ws messages */
ws.onmessage = async (e) => {
const msg = JSON.parse(e.data);
console.log("[ws] <=", msg.type);
switch (msg.type) {
case "answer":
await pc.setRemoteDescription(msg.data);
break;
case "ice":
await pc.addIceCandidate(msg.data);
break;
case "renegotiate":
await negotiate();
break;
}
};
/* 5. initial offer */
await negotiate();
});
</script> </script>
<video bind:this={localVideo} autoplay muted playsinline></video> <video
bind:this={localVideo}
autoplay
muted
playsinline
></video>
<style> <style>
video { video {
width: 240px; width: 240px;
margin: 8px; margin: 8px;
background: black; background: black;
} }
</style> </style>