updated and connected webview to C#

This commit is contained in:
2026-03-28 05:18:05 -04:00
parent a5772d7579
commit 0bb3aa28b1
8 changed files with 411 additions and 21 deletions

View File

@@ -59,7 +59,15 @@
StrokeThickness="1"
Padding="10"
IsVisible="False">
<WebView Source="test.html"/>
<!-- <WebView Source="test.html"/> -->
<Grid RowDefinitions="Auto,*"
ColumnDefinitions="*">
<Button Text="Send message to JavaScript"
Clicked="OnSendMessageButtonClicked" />
<HybridWebView x:Name="hybridWebView"
RawMessageReceived="OnHybridWebViewRawMessageReceived"
Grid.Row="1" />
</Grid>
</Border>
<!-- Input -->

View File

@@ -40,6 +40,7 @@ public partial class MainPage : ContentPage
_wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
_wsc.Send("GET_SERVER_KEY");
_wsc.Send("GET_CHANNELS");
hybridWebView.SetInvokeJavaScriptTarget(this);
}
@@ -305,4 +306,72 @@ public partial class MainPage : ContentPage
ViewSwapped.Text = "Swap to Web View";
}
}
private void OnSendMessageButtonClicked(object sender, EventArgs e)
{
hybridWebView.SendRawMessage($"Hello from C#!");
}
private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
{
await DisplayAlertAsync("Raw Message Received", e.Message, "OK");
}
#region syncs
public async void DoSyncWork()
{
await DisplayAlertAsync("Sync Work", "Sync Work", "OK");
}
public async void DoSyncWorkParams(int i, string s)
{
await DisplayAlertAsync("Sync Work", $"{i}:{s}", "OK");
}
public string DoSyncWorkReturn()
{
return "Hello from C#!";
}
public SyncReturn DoSyncWorkParamsReturn(int i, string s)
{
return new SyncReturn
{
Message = $"Hello from C#! {s}",
Value = i
};
}
#endregion
#region asyncs
public async Task DoAsyncWork()
{
await Task.Delay(1000);
}
public async Task DoAsyncWorkParams(int i, string s)
{
await DisplayAlertAsync("Sync Work", $"{i}:{s}", "OK");
}
public async Task<string> DoAsyncWorkReturn()
{
return "Hello from C#!";
}
public async Task<SyncReturn> DoAsyncWorkParamsReturn(int i, string s)
{
await Task.Delay(1000);
return new SyncReturn
{
Message = $"Hello from C# ASync! {s}",
Value = i
};
}
#endregion
public class SyncReturn
{
public string? Message { get; set; }
public int Value { get; set; }
}
}

View File

@@ -22,6 +22,7 @@ public static class MauiProgram
#if DEBUG
builder.Services.AddHybridWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif

View File

@@ -1,3 +1,23 @@
#header{
border: black solid 2px;
@import url('https://fonts.googleapis.com/css2?family=Syne+Mono&display=swap');
body {
font-family: 'Syne Mono', monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin: 80px 10px;
}
video {
width: 40vw;
height: 30vw;
margin: 2rem;
background: #2c3e50;
}
.videos {
display: flex;
align-items: center;
justify-content: center;
}

View File

@@ -1,9 +1,41 @@
<html lang="enus">
<head>
<link rel="stylesheet" href="test.css">
<script src="test.js"></script>
</head>
<body>
<Button id="header" onclick="onClicked()">Hello World!</Button>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebRTC Demo</title>
</head>
<body>
<h2>1. Start your Webcam</h2>
<div class="videos">
<span>
<h3>Local Stream</h3>
<video id="webcamVideo" autoplay playsinline></video>
</span>
<span>
<h3>Remote Stream</h3>
<video id="remoteVideo" autoplay playsinline></video>
</span>
</div>
<button id="webcamButton">Start webcam</button>
<h2>2. Create a new Call</h2>
<button id="callButton" disabled>Create Call (offer)</button>
<h2>3. Join a Call</h2>
<p>Answer the call from a different browser window or device</p>
<input id="callInput" />
<button id="answerButton" disabled>Answer</button>
<h2>4. Hangup</h2>
<button id="hangupButton" disabled>Hangup</button>
<script type="module" src="test.js"></script>
</body>
</html>

View File

@@ -1,10 +1,146 @@
var toggle = true;
import './test.css';
function onClicked()
{
if (toggle)
document.getElementById("header").style.color = "green";
else
document.getElementById("header").style.color = "red";
toggle = !toggle;
import firebase from 'firebase/app';
import 'firebase/firestore';
const firebaseConfig = {
// your config
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
const firestore = firebase.firestore();
const servers = {
iceServers: [
{
urls: ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'],
},
],
iceCandidatePoolSize: 10,
};
// Global State
const pc = new RTCPeerConnection(servers);
let localStream = null;
let remoteStream = null;
// HTML elements
const webcamButton = document.getElementById('webcamButton');
const webcamVideo = document.getElementById('webcamVideo');
const callButton = document.getElementById('callButton');
const callInput = document.getElementById('callInput');
const answerButton = document.getElementById('answerButton');
const remoteVideo = document.getElementById('remoteVideo');
const hangupButton = document.getElementById('hangupButton');
// 1. Setup media sources
webcamButton.onclick = async () => {
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
remoteStream = new MediaStream();
// Push tracks from local stream to peer connection
localStream.getTracks().forEach((track) => {
pc.addTrack(track, localStream);
});
// Pull tracks from remote stream, add to video stream
pc.ontrack = (event) => {
event.streams[0].getTracks().forEach((track) => {
remoteStream.addTrack(track);
});
};
webcamVideo.srcObject = localStream;
remoteVideo.srcObject = remoteStream;
callButton.disabled = false;
answerButton.disabled = false;
webcamButton.disabled = true;
};
// 2. Create an offer
callButton.onclick = async () => {
// Reference Firestore collections for signaling
const callDoc = firestore.collection('calls').doc();
const offerCandidates = callDoc.collection('offerCandidates');
const answerCandidates = callDoc.collection('answerCandidates');
callInput.value = callDoc.id;
// Get candidates for caller, save to db
pc.onicecandidate = (event) => {
event.candidate && offerCandidates.add(event.candidate.toJSON());
};
// Create offer
const offerDescription = await pc.createOffer();
await pc.setLocalDescription(offerDescription);
const offer = {
sdp: offerDescription.sdp,
type: offerDescription.type,
};
await callDoc.set({ offer });
// Listen for remote answer
callDoc.onSnapshot((snapshot) => {
const data = snapshot.data();
if (!pc.currentRemoteDescription && data?.answer) {
const answerDescription = new RTCSessionDescription(data.answer);
pc.setRemoteDescription(answerDescription);
}
});
// When answered, add candidate to peer connection
answerCandidates.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'added') {
const candidate = new RTCIceCandidate(change.doc.data());
pc.addIceCandidate(candidate);
}
});
});
hangupButton.disabled = false;
};
// 3. Answer the call with the unique ID
answerButton.onclick = async () => {
const callId = callInput.value;
const callDoc = firestore.collection('calls').doc(callId);
const answerCandidates = callDoc.collection('answerCandidates');
const offerCandidates = callDoc.collection('offerCandidates');
pc.onicecandidate = (event) => {
event.candidate && answerCandidates.add(event.candidate.toJSON());
};
const callData = (await callDoc.get()).data();
const offerDescription = callData.offer;
await pc.setRemoteDescription(new RTCSessionDescription(offerDescription));
const answerDescription = await pc.createAnswer();
await pc.setLocalDescription(answerDescription);
const answer = {
type: answerDescription.type,
sdp: answerDescription.sdp,
};
await callDoc.update({ answer });
offerCandidates.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
console.log(change);
if (change.type === 'added') {
let data = change.doc.data();
pc.addIceCandidate(new RTCIceCandidate(data));
}
});
});
};

View File

@@ -0,0 +1,124 @@
<!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>
function LogMessage(msg) {
var messageLog = document.getElementById("messageLog");
messageLog.value += '\r\n' + msg;
}
window.addEventListener(
"HybridWebViewMessageReceived",
function (e) {
LogMessage("Raw message: " + e.detail.message);
});
function AddNumbers(a, b) {
var result = {
"result": a + b,
"operationName": "Addition"
};
return result;
}
var count = 0;
async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) {
const response = await fetch("/asyncdata.txt");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
var jsonData = await response.json();
jsonData[s1] = s2;
const msg = 'JSON data is available: ' + JSON.stringify(jsonData);
window.HybridWebView.SendRawMessage(msg)
return jsonData;
}
async function InvokeDoSyncWork() {
LogMessage("Invoking DoSyncWork");
await window.HybridWebView.InvokeDotNet('DoSyncWork');
LogMessage("Invoked DoSyncWork");
}
async function InvokeDoSyncWorkParams() {
LogMessage("Invoking DoSyncWorkParams");
await window.HybridWebView.InvokeDotNet('DoSyncWorkParams', [123, 'hello']);
LogMessage("Invoked DoSyncWorkParams");
}
async function InvokeDoSyncWorkReturn() {
LogMessage("Invoking DoSyncWorkReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkReturn');
LogMessage("Invoked DoSyncWorkReturn, return value: " + retValue);
}
async function InvokeDoSyncWorkParamsReturn() {
LogMessage("Invoking DoSyncWorkParamsReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkParamsReturn', [123, 'hello']);
LogMessage("Invoked DoSyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value);
}
async function InvokeDoAsyncWork() {
LogMessage("Invoking DoAsyncWork");
await window.HybridWebView.InvokeDotNet('DoAsyncWork');
LogMessage("Invoked DoAsyncWork");
}
async function InvokeDoAsyncWorkParams() {
LogMessage("Invoking DoAsyncWorkParams");
await window.HybridWebView.InvokeDotNet('DoAsyncWorkParams', [123, 'hello']);
LogMessage("Invoked DoAsyncWorkParams");
}
async function InvokeDoAsyncWorkReturn() {
LogMessage("Invoking DoAsyncWorkReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkReturn');
LogMessage("Invoked DoAsyncWorkReturn, return value: " + retValue);
}
async function InvokeDoAsyncWorkParamsReturn() {
LogMessage("Invoking DoAsyncWorkParamsReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkParamsReturn', [123, 'hello']);
LogMessage("Invoked DoAsyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value);
}
</script>
</head>
<body>
<div>
Hybrid sample!
</div>
<div>
<button onclick="window.HybridWebView.SendRawMessage('Message from JS! ' + (count++))">Send message to C#</button>
</div>
<div>
<button onclick="InvokeDoSyncWork()">Call C# sync method (no params)</button>
<button onclick="InvokeDoSyncWorkParams()">Call C# sync method (params)</button>
<button onclick="InvokeDoSyncWorkReturn()">Call C# method (no params) and get simple return value</button>
<button onclick="InvokeDoSyncWorkParamsReturn()">Call C# method (params) and get complex return value</button>
</div>
<div>
<button onclick="InvokeDoAsyncWork()">Call C# async method (no params)</button>
<button onclick="InvokeDoAsyncWorkParams()">Call C# async method (params)</button>
<button onclick="InvokeDoAsyncWorkReturn()">Call C# async method (no params) and get simple return value</button>
<button onclick="InvokeDoAsyncWorkParamsReturn()">Call C# async method (params) and get complex return value</button>
</div>
<div>
Log: <textarea readonly id="messageLog" style="width: 80%; height: 10em;"></textarea>
</div>
<div>
Consider checking out this PDF: <a href="docs/sample.pdf">sample.pdf</a>
</div>
</body>
</html>