updated and connected webview to C#
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ public static class MauiProgram
|
||||
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
builder.Services.AddHybridWebViewDeveloperTools();
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
return builder.Build();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
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