webrtc
This commit is contained in:
parent
ce7b8a1ccd
commit
cc95fc5829
@ -1 +1,20 @@
|
||||
/**
|
||||
* webrtc page script
|
||||
*
|
||||
* Author: Peter Harpending <peterharpending@qpq.swiss>
|
||||
* Date: 2026-02-04
|
||||
* Copyright: Copyright (c) 2026 QPQ AG
|
||||
*
|
||||
* Reference: https://git.qpq.swiss/QPQ-AG/research-megadoc/src/commit/c7c4592d4b21ad120145ef63334471a1a7ec1e60/paste/2026-02/grok-webrtc.html
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
main();
|
||||
|
||||
async function
|
||||
main()
|
||||
{
|
||||
let pc: RTCPeerConnection = new RTCPeerConnection();
|
||||
}
|
||||
|
||||
|
||||
@ -1,165 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Super Simple WebRTC – Manual Signaling</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 20px; }
|
||||
textarea { width: 100%; height: 90px; font-family: monospace; }
|
||||
button { margin: 6px 0; padding: 8px 16px; }
|
||||
video { max-width: 320px; border: 1px solid #444; margin: 8px; background: #000; }
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<title>FEWD: voice chat demo</title>
|
||||
<link rel="stylesheet" href="/css/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="titlebar">
|
||||
<div class="content">
|
||||
<a href="/" class="tb-home">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>WebRTC 1:1 test (copy-paste signaling)</h2>
|
||||
<div class="content">
|
||||
<h1 class="content-title">FEWD: webrtc demo</h1>
|
||||
|
||||
<div>
|
||||
<button id="btnCreateOffer">1. Create Offer (Peer A)</button>
|
||||
<button id="btnSetRemoteOffer">2. Paste offer → Set remote description</button><br>
|
||||
<textarea id="offerSdp" placeholder="Offer SDP will appear here (Peer A) or paste offer here (Peer B)"></textarea>
|
||||
</div>
|
||||
<div id="init">
|
||||
<label for="initName">username:</label>
|
||||
<input autofocus id="initName" type="text"></input>
|
||||
<button id="initSubmit">Join</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 24px;">
|
||||
<button id="btnCreateAnswer">3. Create Answer (Peer B)</button>
|
||||
<button id="btnSetRemoteAnswer">4. Paste answer → Set remote description</button><br>
|
||||
<textarea id="answerSdp" placeholder="Answer SDP will appear here (Peer B) or paste answer here (Peer A)"></textarea>
|
||||
</div>
|
||||
<div id="peers" hidden>
|
||||
Peers
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button id="btnAddIce">Add ICE candidate manually (if needed)</button><br>
|
||||
<textarea id="iceInput" placeholder="Paste remote ICE candidate here"></textarea>
|
||||
<div id="iceLog"></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<video id="localVideo" autoplay playsinline muted></video>
|
||||
<video id="remoteVideo" autoplay playsinline></video>
|
||||
</div>
|
||||
|
||||
<pre id="status">Status: ready</pre>
|
||||
|
||||
<script>
|
||||
// ────────────────────────────────────────────────
|
||||
const $ = s => document.querySelector(s);
|
||||
const status = $('pre#status');
|
||||
const offerArea = $('#offerSdp');
|
||||
const answerArea = $('#answerSdp');
|
||||
const iceInput = $('#iceInput');
|
||||
const iceLog = $('#iceLog');
|
||||
|
||||
let pc = null;
|
||||
let localStream = null;
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
async function initPeerConnection() {
|
||||
if (pc) pc.close();
|
||||
|
||||
pc = new RTCPeerConnection({
|
||||
iceServers: [
|
||||
{ urls: "stun:stun.l.google.com:19302" },
|
||||
{ urls: "stun:stun.stunprotocol.org" }
|
||||
]
|
||||
});
|
||||
|
||||
pc.onicecandidate = e => {
|
||||
if (e.candidate) {
|
||||
console.log("New ICE candidate:", e.candidate);
|
||||
iceLog.innerHTML += "<div style='color:#c33'>→ " + e.candidate.candidate + "</div>";
|
||||
}
|
||||
};
|
||||
|
||||
pc.oniceconnectionstatechange = () => { status.textContent = "ICE state: " + pc.iceConnectionState;
|
||||
};
|
||||
|
||||
pc.ontrack = e => {
|
||||
console.log("ontrack", e);
|
||||
const remoteVideo = $('#remoteVideo');
|
||||
remoteVideo.srcObject = e.streams[0];
|
||||
status.textContent = "ICE state: " + pc.iceConnectionState + " (receiving media!)";
|
||||
};
|
||||
|
||||
// Add local stream if we have camera/mic
|
||||
if (localStream) {
|
||||
localStream.getTracks().forEach(track => {
|
||||
pc.addTrack(track, localStream);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
async function startLocalCamera() {
|
||||
try {
|
||||
localStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
audio: false // change to true if you want audio too
|
||||
});
|
||||
$('#localVideo').srcObject = localStream;
|
||||
} catch (err) {
|
||||
console.error("Camera access failed", err);
|
||||
status.textContent = "Camera access failed: " + err.message;
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
$('#btnCreateOffer').onclick = async () => {
|
||||
await initPeerConnection();
|
||||
await startLocalCamera();
|
||||
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
|
||||
offerArea.value = JSON.stringify(pc.localDescription, null, 2);
|
||||
status.textContent = "Offer created – copy and send to peer B";
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
$('#btnCreateAnswer').onclick = async () => {
|
||||
await initPeerConnection();
|
||||
await startLocalCamera();
|
||||
|
||||
const offer = JSON.parse(offerArea.value);
|
||||
await pc.setRemoteDescription(offer);
|
||||
|
||||
const answer = await pc.createAnswer();
|
||||
await pc.setLocalDescription(answer);
|
||||
|
||||
answerArea.value = JSON.stringify(pc.localDescription, null, 2);
|
||||
status.textContent = "Answer created – copy and send to peer A";
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
$('#btnSetRemoteOffer').onclick = async () => {
|
||||
if (!pc) await initPeerConnection();
|
||||
|
||||
const remote = JSON.parse(offerArea.value);
|
||||
await pc.setRemoteDescription(remote);
|
||||
status.textContent = "Remote offer set";
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
$('#btnSetRemoteAnswer').onclick = async () => {
|
||||
const remote = JSON.parse(answerArea.value);
|
||||
await pc.setRemoteDescription(remote);
|
||||
status.textContent = "Remote answer set – waiting for ICE/media";
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
$('#btnAddIce').onclick = async () => {
|
||||
if (!iceInput.value.trim()) return;
|
||||
try {
|
||||
const candidate = new RTCIceCandidate(JSON.parse(iceInput.value));
|
||||
await pc.addIceCandidate(candidate);
|
||||
iceLog.innerHTML += "<div style='color:#3a3'>← added ICE candidate</div>";
|
||||
iceInput.value = "";
|
||||
} catch (err) {
|
||||
console.error("Bad ICE candidate", err);
|
||||
}
|
||||
};
|
||||
|
||||
// Optional: auto-start camera when page loads
|
||||
// startLocalCamera();
|
||||
|
||||
</script>
|
||||
<script src="/js/dist/webrtc.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user