198 lines
6.9 KiB
HTML
198 lines
6.9 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title></title>
|
|
<link rel="icon" href="data:,">
|
|
<link rel="stylesheet" href="styles/app.css">
|
|
<link rel="stylesheet" href="index.css">
|
|
<script src="_framework/hybridwebview.js"></script>
|
|
<script>
|
|
let peerConnection = null;
|
|
let localStream = null;
|
|
let currentTarget = null;
|
|
let currentUsername = null;
|
|
|
|
function LogMessage(msg) {
|
|
const messageLog = document.getElementById("messageLog");
|
|
messageLog.value += '\r\n' + msg;
|
|
}
|
|
|
|
async function ensurePeerConnection() {
|
|
if (peerConnection) return;
|
|
|
|
peerConnection = new RTCPeerConnection({
|
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
});
|
|
|
|
peerConnection.onicecandidate = async (event) => {
|
|
if (!event.candidate || !currentTarget || !currentUsername) return;
|
|
|
|
const payload = {
|
|
type: "rtc_ice_candidate",
|
|
from: currentUsername,
|
|
to: currentTarget,
|
|
candidate: event.candidate.candidate,
|
|
sdpMid: event.candidate.sdpMid,
|
|
sdpMLineIndex: event.candidate.sdpMLineIndex
|
|
};
|
|
|
|
LogMessage("Sending ICE candidate");
|
|
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
|
};
|
|
|
|
peerConnection.ontrack = (event) => {
|
|
LogMessage("Remote track received");
|
|
const remoteVideo = document.getElementById("remoteVideo");
|
|
remoteVideo.srcObject = event.streams[0];
|
|
};
|
|
|
|
peerConnection.onconnectionstatechange = () => {
|
|
LogMessage("Connection state: " + peerConnection.connectionState);
|
|
};
|
|
|
|
peerConnection.oniceconnectionstatechange = () => {
|
|
LogMessage("ICE connection state: " + peerConnection.iceConnectionState);
|
|
};
|
|
|
|
peerConnection.onicegatheringstatechange = () => {
|
|
LogMessage("ICE gathering state: " + peerConnection.iceGatheringState);
|
|
};
|
|
}
|
|
|
|
async function ensureLocalMedia() {
|
|
if (localStream) return;
|
|
|
|
try {
|
|
localStream = await navigator.mediaDevices.getUserMedia({
|
|
video: true,
|
|
audio: true
|
|
});
|
|
|
|
const localVideo = document.getElementById("localVideo");
|
|
localVideo.srcObject = localStream;
|
|
|
|
for (const track of localStream.getTracks()) {
|
|
peerConnection.addTrack(track, localStream);
|
|
}
|
|
|
|
LogMessage("Local media initialized");
|
|
} catch (err) {
|
|
LogMessage("getUserMedia failed: " + err);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function startCall() {
|
|
try {
|
|
currentTarget = document.getElementById("targetUser").value;
|
|
|
|
if (!currentTarget) {
|
|
LogMessage("No target user set.");
|
|
return;
|
|
}
|
|
|
|
await ensurePeerConnection();
|
|
await ensureLocalMedia();
|
|
|
|
const offer = await peerConnection.createOffer();
|
|
await peerConnection.setLocalDescription(offer);
|
|
|
|
const payload = {
|
|
type: "rtc_offer",
|
|
from: currentUsername,
|
|
to: currentTarget,
|
|
sdp: offer.sdp
|
|
};
|
|
|
|
LogMessage("Sending offer to " + currentTarget);
|
|
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
|
} catch (err) {
|
|
LogMessage("startCall failed: " + err);
|
|
}
|
|
}
|
|
|
|
async function handleRtcSignal(rawJson) {
|
|
try {
|
|
const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
|
|
|
|
LogMessage("Received signal: " + msg.type + " from " + msg.from);
|
|
|
|
await ensurePeerConnection();
|
|
|
|
if (msg.type === "rtc_offer") {
|
|
currentTarget = msg.from;
|
|
LogMessage("Incoming call from " + msg.from);
|
|
await ensureLocalMedia();
|
|
|
|
await peerConnection.setRemoteDescription({
|
|
type: "offer",
|
|
sdp: msg.sdp
|
|
});
|
|
|
|
const answer = await peerConnection.createAnswer();
|
|
await peerConnection.setLocalDescription(answer);
|
|
|
|
const payload = {
|
|
type: "rtc_answer",
|
|
from: currentUsername,
|
|
to: msg.from,
|
|
sdp: answer.sdp
|
|
};
|
|
|
|
LogMessage("Sending answer to " + msg.from);
|
|
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
|
return;
|
|
}
|
|
|
|
if (msg.type === "rtc_answer") {
|
|
await peerConnection.setRemoteDescription({
|
|
type: "answer",
|
|
sdp: msg.sdp
|
|
});
|
|
LogMessage("Remote answer applied");
|
|
return;
|
|
}
|
|
|
|
if (msg.type === "rtc_ice_candidate") {
|
|
await peerConnection.addIceCandidate({
|
|
candidate: msg.candidate,
|
|
sdpMid: msg.sdpMid,
|
|
sdpMLineIndex: msg.sdpMLineIndex
|
|
});
|
|
LogMessage("Remote ICE candidate applied");
|
|
}
|
|
} catch (err) {
|
|
LogMessage("handleRtcSignal failed: " + err);
|
|
}
|
|
}
|
|
|
|
window.handleRtcSignal = handleRtcSignal;
|
|
|
|
window.addEventListener("HybridWebViewMessageReceived", function (e) {
|
|
LogMessage("Raw message: " + e.detail.message);
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div>
|
|
<h3>Relay RTC Test</h3>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="targetUser">Target User:</label>
|
|
<input id="targetUser" type="text" value="Ru_Kira" />
|
|
<button onclick="startCall()">Start Call</button>
|
|
</div>
|
|
|
|
<div style="margin-top: 10px;">
|
|
<video id="localVideo" autoplay playsinline muted style="width: 320px; height: 240px; background: #111;"></video>
|
|
<video id="remoteVideo" autoplay playsinline style="width: 320px; height: 240px; background: #111;"></video>
|
|
</div>
|
|
|
|
<div style="margin-top: 10px;">
|
|
<textarea readonly id="messageLog" style="width: 90%; height: 12em;"></textarea>
|
|
</div>
|
|
</body>
|
|
</html> |