updated and connected webview to C#
This commit is contained in:
@@ -59,7 +59,15 @@
|
|||||||
StrokeThickness="1"
|
StrokeThickness="1"
|
||||||
Padding="10"
|
Padding="10"
|
||||||
IsVisible="False">
|
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>
|
</Border>
|
||||||
|
|
||||||
<!-- Input -->
|
<!-- Input -->
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public partial class MainPage : ContentPage
|
|||||||
_wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
|
_wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
|
||||||
_wsc.Send("GET_SERVER_KEY");
|
_wsc.Send("GET_SERVER_KEY");
|
||||||
_wsc.Send("GET_CHANNELS");
|
_wsc.Send("GET_CHANNELS");
|
||||||
|
hybridWebView.SetInvokeJavaScriptTarget(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,4 +306,72 @@ public partial class MainPage : ContentPage
|
|||||||
ViewSwapped.Text = "Swap to Web View";
|
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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ public static class MauiProgram
|
|||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
builder.Services.AddHybridWebViewDeveloperTools();
|
||||||
builder.Logging.AddDebug();
|
builder.Logging.AddDebug();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,23 @@
|
|||||||
#header{
|
@import url('https://fonts.googleapis.com/css2?family=Syne+Mono&display=swap');
|
||||||
border: black solid 2px;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,41 @@
|
|||||||
<html lang="enus">
|
<!DOCTYPE html>
|
||||||
<head>
|
<html lang="en">
|
||||||
<link rel="stylesheet" href="test.css">
|
<head>
|
||||||
<script src="test.js"></script>
|
<meta charset="UTF-8" />
|
||||||
</head>
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||||
<body>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<Button id="header" onclick="onClicked()">Hello World!</Button>
|
<title>WebRTC Demo</title>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
@@ -1,10 +1,146 @@
|
|||||||
var toggle = true;
|
import './test.css';
|
||||||
|
|
||||||
function onClicked()
|
import firebase from 'firebase/app';
|
||||||
{
|
import 'firebase/firestore';
|
||||||
if (toggle)
|
|
||||||
document.getElementById("header").style.color = "green";
|
const firebaseConfig = {
|
||||||
else
|
// your config
|
||||||
document.getElementById("header").style.color = "red";
|
};
|
||||||
toggle = !toggle;
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
0
RelayClient/Resources/Raw/wwwroot/index.css
Normal file
0
RelayClient/Resources/Raw/wwwroot/index.css
Normal file
124
RelayClient/Resources/Raw/wwwroot/index.html
Normal file
124
RelayClient/Resources/Raw/wwwroot/index.html
Normal 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>
|
||||||
Reference in New Issue
Block a user