openvidu-electron: Migrated to livekit

This commit is contained in:
Carlos Santos 2023-10-26 17:51:55 +02:00
parent 1c7fa1b8ec
commit 43cd1ab955
6 changed files with 6412 additions and 1439 deletions

File diff suppressed because it is too large Load Diff

View File

@ -10,22 +10,20 @@
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "echo \"No linting configured\""
},
"repository": "https://github.com/OpenVidu/openvidu-tutorials",
"author": "pablofuenteperez@gmail.com",
"license": "Apache-2.0",
"devDependencies": {
"@electron-forge/cli": "6.0.0-beta.65",
"@electron-forge/maker-deb": "6.0.0-beta.65",
"@electron-forge/maker-rpm": "6.0.0-beta.65",
"@electron-forge/maker-squirrel": "6.0.0-beta.65",
"@electron-forge/maker-zip": "6.0.0-beta.65",
"@electron-forge/cli": "6.0.0",
"@electron-forge/maker-deb": "6.0.0",
"@electron-forge/maker-rpm": "6.0.0",
"@electron-forge/maker-squirrel": "6.0.0",
"@electron-forge/maker-zip": "6.0.0",
"electron": "19.0.10"
},
"dependencies": {
"@electron/remote": "2.0.8",
"axios": "0.27.2",
"@electron/remote": "2.0.12",
"axios": "1.5.1",
"electron-squirrel-startup": "1.0.0",
"openvidu-browser": "2.27.0"
"livekit-client": "1.14.1"
},
"config": {
"forge": {

View File

@ -17,29 +17,34 @@
and Electron <span id="electron-version"></span>.
<div id="join">
<h1>Join a video session</h1>
<h1>Join a Video Room</h1>
<form onsubmit="initPublisher(); return false">
<p>
<label>Session:</label>
<input type="text" id="sessionId" value="SessionA" required>
</p>
<p>
<input id="screen-sharing" type="checkbox" name="screenshare"> Share screen?<br>
</p>
<p>
<input type="submit" value="JOIN">
</p>
<div class="form-group">
<label for="roomName">Room Name:</label>
<input type="text" id="roomName" placeholder="Enter room name" value="RoomA" required>
</div>
<div class="form-group">
<label for="participantName">Your Name:</label>
<input type="text" id="participantName" placeholder="Enter your name" required>
</div>
<div class="form-group screen-chekbox">
<input id="screen-sharing" type="checkbox" name="screenshare">
<label for="screen-sharing">Share screen?</label>
</div>
<div class="form-group">
<button type="submit">JOIN</button>
</div>
</form>
</div>
<div id="session" style="display: none;">
<h1 id="session-header"></h1>
<input type="button" onclick="leaveSession()" value="LEAVE">
<div id="room" style="display: none;">
<h1 id="room-header"></h1>
<input type="button" onclick="leaveRoom()" value="LEAVE">
<div>
<div id="publisher">
<div id="local-participant">
<h3>YOU</h3>
</div>
<div id="subscriber">
<div id="remote-participants">
<h3>OTHERS</h3>
</div>
</div>

View File

@ -1,126 +1,255 @@
const ipcRenderer = require('electron').ipcRenderer;
const { BrowserWindow } = require('@electron/remote');
const { OpenVidu } = require('openvidu-browser');
const { Room, RoomEvent } = require('livekit-client');
const axios = require('axios');
var openvidu;
var session;
var room;
var publisher;
var mySessionId;
var myParticipantName;
var myRoomName;
var isScreenShared = false;
var screenSharePublication;
ipcRenderer.on('screen-share-ready', (event, message) => {
if (!!message) {
// User has chosen a screen to share. screenId is message parameter
showSession();
publisher = openvidu.initPublisher("publisher", {
videoSource: "screen:" + message
});
joinSession();
}
ipcRenderer.on('screen-share-ready', async (event, sourceId) => {
if (sourceId) {
// User has chosen a screen to share. screenId is message parameter
showRoom();
await joinRoom();
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
},
});
console.log('screenVideoTrack ', stream);
const track = stream.getVideoTracks()[0];
const screenPublication = await room.localParticipant.publishTrack(track);
const element = screenPublication.track.attach();
element.className = 'removable';
document.getElementById('local-participant').appendChild(element);
} catch (error) {
console.error('Error enabling screen sharing', error);
}
}
});
function initPublisher() {
async function initPublisher() {
openvidu = new OpenVidu();
const shareScreen = document.getElementById('screen-sharing').checked;
const shareScreen = document.getElementById("screen-sharing").checked;
if (shareScreen) {
openScreenShareModal();
} else {
publisher = openvidu.initPublisher("publisher");
joinSession();
}
if (shareScreen) {
openScreenShareModal();
} else {
await joinRoom();
}
}
async function joinSession() {
async function joinRoom() {
myRoomName = document.getElementById('roomName').value;
myParticipantName = document.getElementById('participantName').value;
session = openvidu.initSession();
session.on("streamCreated", function (event) {
session.subscribe(event.stream, "subscriber");
});
// --- 1) Get a Room object ---
room = new Room();
mySessionId = document.getElementById("sessionId").value;
// --- 2) Specify the actions when events take place in the room ---
const token = await getToken(mySessionId);
// On every new Track received...
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
console.log('TrackSubscribed', track, publication, participant);
const element = track.attach();
element.id = track.sid;
element.className = 'removable';
document.getElementById('remote-participants').appendChild(element);
});
await session.connect(token, { clientData: 'OpenVidu Electron' });
showSession();
session.publish(publisher);
// On every new Track destroyed...
room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
console.log('TrackUnSubscribed', track, publication, participant);
track.detach();
document.getElementById(track.sid)?.remove();
});
const token = await getToken(myRoomName, myParticipantName);
const livekitUrl = getLivekitUrlFromMetadata(token);
await room.connect(livekitUrl, token);
showRoom();
await room.localParticipant.setMicrophoneEnabled(true);
const publication = await room.localParticipant.setCameraEnabled(true);
const element = publication.track.attach();
element.className = 'removable';
document.getElementById('local-participant').appendChild(element);
}
function leaveSession() {
session.disconnect();
hideSession();
async function toggleScreenShare() {
console.log('Toggling screen share');
const enabled = !isScreenShared;
if (enabled) {
// Enable screen sharing
try {
screenSharePublication =
await room.localParticipant?.setScreenShareEnabled(enabled);
} catch (error) {
console.error('Error enabling screen sharing', error);
}
if (screenSharePublication) {
console.log('Screen sharing enabled', screenSharePublication);
isScreenShared = enabled;
// Attach the screen share track to the video container
const element = screenSharePublication.track.attach();
element.id = screenSharePublication.trackSid;
element.className = 'removable';
document.getElementById('local-participant').appendChild(element);
// Listen for the 'ended' event to handle screen sharing stop
screenSharePublication.addListener('ended', async () => {
console.debug('Clicked native stop button. Stopping screen sharing');
await stopScreenSharing();
});
}
} else {
// Disable screen sharing
await stopScreenSharing();
}
}
function showSession() {
document.getElementById("session-header").innerText = mySessionId;
document.getElementById("join").style.display = "none";
document.getElementById("session").style.display = "block";
async function stopScreenSharing() {
try {
await room.localParticipant?.setScreenShareEnabled(false);
isScreenShared = false;
const trackSid = screenSharePublication?.trackSid;
if (trackSid) {
document.getElementById(trackSid)?.remove();
}
screenSharePublication = undefined;
} catch (error) {
console.error('Error stopping screen sharing', error);
}
}
function hideSession() {
document.getElementById("join").style.display = "block";
document.getElementById("session").style.display = "none";
function leaveRoom() {
// --- 6) Leave the room by calling 'disconnect' method over the Room object ---
room.disconnect();
// Removing all HTML elements with user's nicknames.
// HTML videos are automatically removed when leaving a Room
removeAllUserData();
hideRoom();
}
function showRoom() {
document.getElementById('room-header').innerText = myRoomName;
document.getElementById('join').style.display = 'none';
document.getElementById('room').style.display = 'block';
}
function hideRoom() {
document.getElementById('join').style.display = 'block';
document.getElementById('room').style.display = 'none';
}
function removeAllUserData() {
var elementsToRemove = document.getElementsByClassName('removable');
while (elementsToRemove[0]) {
elementsToRemove[0].parentNode.removeChild(elementsToRemove[0]);
}
}
function openScreenShareModal() {
let win = new BrowserWindow({
parent: require('@electron/remote').getCurrentWindow(),
modal: true,
minimizable: false,
maximizable: false,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
},
resizable: false
});
require("@electron/remote").require("@electron/remote/main").enable(win.webContents);
let win = new BrowserWindow({
parent: require('@electron/remote').getCurrentWindow(),
modal: true,
minimizable: false,
maximizable: false,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
},
resizable: false,
});
require('@electron/remote')
.require('@electron/remote/main')
.enable(win.webContents);
win.setMenu(null);
// win.webContents.openDevTools();
win.setMenu(null);
// win.webContents.openDevTools();
var theUrl = 'file://' + __dirname + '/modal.html'
win.loadURL(theUrl);
var theUrl = 'file://' + __dirname + '/modal.html';
win.loadURL(theUrl);
}
function getLivekitUrlFromMetadata(token) {
if (!token) throw new Error('Trying to get metadata from an empty token');
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
window
.atob(base64)
.split('')
.map((c) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
const payload = JSON.parse(jsonPayload);
if (!payload?.metadata) throw new Error('Token does not contain metadata');
const metadata = JSON.parse(payload.metadata);
return metadata.livekitUrl;
} catch (error) {
throw new Error('Error decoding and parsing token: ' + error);
}
}
/**
* --------------------------------------------
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
* --------------------------------------------
* The methods below request the creation of a Session and a Token to
* The methods below request the creation of a Token to
* your application server. This keeps your OpenVidu deployment secure.
*
*
* In this sample code, there is no user control at all. Anybody could
* access your application server endpoints! In a real production
* environment, your application server must identify the user to allow
* access to the endpoints.
*
* Visit https://docs.openvidu.io/en/stable/application-server to learn
* more about the integration of OpenVidu in your application server.
*
*/
var APPLICATION_SERVER_URL = 'http://localhost:5000/';
async function getToken(mySessionId) {
const sessionId = await createSession(mySessionId);
return await createToken(sessionId);
}
async function getToken(roomName, participantName) {
try {
const response = await axios.post(
APPLICATION_SERVER_URL + 'token',
{
roomName,
participantName,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);
async function createSession(sessionId) {
const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions', { customSessionId: sessionId }, {
headers: { 'Content-Type': 'application/json', },
});
return response.data; // The sessionId
console.log(response.data);
return response.data;
} catch (error) {
console.error('No connection to application server', error);
}
}
async function createToken(sessionId) {
const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions/' + sessionId + '/connections', {}, {
headers: { 'Content-Type': 'application/json', },
});
return response.data; // The token
}

View File

@ -1,94 +1,177 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline';"
/>
<title>Screen Sharing Selection</title>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<title>OpenVidu Electron</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
text-align: center;
padding: 20px;
}
<style>
#list-of-screens {
margin-bottom: 60px;
}
h1 {
font-size: 24px;
}
#footer {
position: fixed;
bottom: 0;
width: 100%;
height: 25px;
background: white;
text-align: right;
padding: 10px 0px 10px 0px;
}
#list-of-screens {
/* display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 10px;
justify-items: center;
align-items: start;
margin: 20px 0; */
#share-btn {
margin-right: 20px;
}
</style>
</head>
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px; /* Adjust the gap as needed for spacing */
align-items: stretch; /* Makes all rows the same height */
<body>
<h1>Share your screen</h1>
<div id="list-of-screens"></div>
<div id="footer">
<button id="cancel-btn" onclick="cancelSelection()">Cancel</button>
<button id="share-btn" onclick="sendScreenSelection()" disabled>Share</button>
</div>
</body>
/* Additional styling for the grid container */
padding: 16px;
background-color: #f0f0f0;
}
<script>
var availableScreens = [];
var htmlElements = [];
var selectedElement;
.screen-item {
border: 1px solid #ccc;
padding: 10px;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: background-color 0.3s, border-color 0.3s;
}
const desktopCapturer = require('@electron/remote').desktopCapturer;
const ipcRenderer = require('electron').ipcRenderer;
.screen-item:hover {
background-color: #f5f5f5;
}
// Call Electron API to list all available screens
desktopCapturer.getSources({
types: ['window', 'screen']
}).then(async sources => {
const list = document.getElementById("list-of-screens");
sources.forEach(source => {
// Add new element to the list with the thumbnail of the screen
var el = document.createElement("div");
el.onclick = () => {
// Style the new selected screen and store it as the current selection
htmlElements.forEach(e => {
e.style.border = "none";
e.style.background = "none";
})
el.style.border = "2px solid #0088aa";
el.style.background = "rgba(0, 0, 0, 0.06)";
selectedElement = el;
document.getElementById("share-btn").disabled = false;
}
// Store the new source and the new created HTML element
availableScreens.push(source);
htmlElements.push(el);
var img = document.createElement("img");
var name = document.createElement("span");
img.src = source.thumbnail.toDataURL();
name.innerHTML = source.name;
// Append new elements to the template
el.appendChild(img);
el.appendChild(name);
list.appendChild(el);
});
});
.screen-item img {
max-width: 100px;
max-height: 100px;
margin-bottom: 10px;
}
function sendScreenSelection() {
ipcRenderer.send('screen-share-selected', availableScreens[htmlElements.indexOf(selectedElement)].id);
closeWindow();
}
.screen-item span {
padding: 10px;
font-weight: bold;
text-align: center;
}
function cancelSelection() {
ipcRenderer.send('screen-share-selected', undefined);
closeWindow();
}
#footer {
position: fixed;
bottom: 0;
width: 95%;
height: 50px;
background: #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0px;
}
function closeWindow() {
require('@electron/remote').getCurrentWindow().close();
}
</script>
#share-btn,
#cancel-btn {
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
#share-btn {
background-color: #007bff;
}
#cancel-btn {
background-color: rgb(151, 28, 28);
}
.screen-name {
font-weight: bold;
}
</style>
</head>
<body>
<h1>Select a Screen to Share</h1>
<div id="list-of-screens"></div>
<div id="footer">
<button id="cancel-btn" onclick="cancelSelection()">Cancel</button>
<button id="share-btn" onclick="sendScreenSelection()" disabled>
Share Screen
</button>
</div>
</body>
<script>
var availableScreens = [];
var htmlElements = [];
var selectedElement;
const desktopCapturer = require('@electron/remote').desktopCapturer;
const ipcRenderer = require('electron').ipcRenderer;
// Call Electron API to list all available screens
desktopCapturer
.getSources({
types: ['window', 'screen'],
})
.then(async (sources) => {
const list = document.getElementById('list-of-screens');
sources.forEach((source) => {
// Add new element to the list with the thumbnail of the screen
var el = document.createElement('div');
el.className = 'screen-item';
el.onclick = () => {
// Style the new selected screen and store it as the current selection
htmlElements.forEach((e) => {
e.style.border = 'none';
e.style.background = 'none';
});
el.style.border = '2px solid #0088aa';
el.style.background = 'rgba(0, 0, 0, 0.06)';
selectedElement = el;
document.getElementById('share-btn').disabled = false;
};
// Store the new source and the new created HTML element
availableScreens.push(source);
htmlElements.push(el);
var img = document.createElement('img');
var name = document.createElement('span');
img.src = source.thumbnail.toDataURL();
name.innerHTML = source.name;
img.style.width = '100px';
img.style.height = '100px';
// Append new elements to the template
el.appendChild(img);
el.appendChild(name);
list.appendChild(el);
});
});
function sendScreenSelection() {
ipcRenderer.send(
'screen-share-selected',
availableScreens[htmlElements.indexOf(selectedElement)].id
);
closeWindow();
}
function cancelSelection() {
ipcRenderer.send('screen-share-selected', undefined);
closeWindow();
}
function closeWindow() {
require('@electron/remote').getCurrentWindow().close();
}
</script>
</html>

View File

@ -14,13 +14,13 @@ body {
text-align: center;
}
#publisher {
#local-participant {
float: left;
margin: 10px;
width: 40%;
}
#subscriber {
#remote-participants {
float: right;
margin: 10px;
width: 40%;
@ -30,4 +30,52 @@ video {
width: 70%;
margin: 10px auto 0 auto;
display: block;
}
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
.form-group {
margin: 10px 0;
text-align: left;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
input[type="checkbox"],
button {
width: 95%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
input[type="checkbox"] {
width: auto;
display: inline;
margin-right: 10px;
}
button {
background: #007bff;
color: #fff;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #0056b3;
}
.screen-chekbox {
display: flex;
}