Update Electron tutorial
This commit is contained in:
parent
46897e3ad7
commit
0e55ef702e
@ -34,14 +34,14 @@
|
||||
<a href="/" title="Home"><h1>Basic Angular</h1></a>
|
||||
<div id="links">
|
||||
<a
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-js"
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-angular"
|
||||
title="GitHub Repository"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
<a
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/javascript/"
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/angular/"
|
||||
title="Documentation"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
95
application-client/openvidu-electron/.gitignore
vendored
95
application-client/openvidu-electron/.gitignore
vendored
@ -1,3 +1,92 @@
|
||||
node_modules
|
||||
out
|
||||
.git
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
.DS_Store
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
# Vite
|
||||
.vite/
|
||||
|
||||
# Electron-Forge
|
||||
out/
|
||||
|
||||
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -1,14 +0,0 @@
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml)
|
||||
[](https://docs.openvidu.io/en/stable/?badge=stable)
|
||||
[](https://hub.docker.com/r/openvidu/openvidu-server-kms)
|
||||
[](https://openvidu.discourse.group/)
|
||||
|
||||
[![][OpenViduLogo]](http://openvidu.io)
|
||||
|
||||
openvidu-electron
|
||||
===
|
||||
|
||||
Visit [docs.openvidu.io/en/stable/tutorials/openvidu-electron/](http://docs.openvidu.io/en/stable/tutorials/openvidu-electron/)
|
||||
|
||||
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
|
||||
44
application-client/openvidu-electron/forge.config.js
Normal file
44
application-client/openvidu-electron/forge.config.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
|
||||
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['darwin'],
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
config: {},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
name: '@electron-forge/plugin-auto-unpack-natives',
|
||||
config: {},
|
||||
},
|
||||
// Fuses are used to enable/disable various Electron functionality
|
||||
// at package time, before code signing the application
|
||||
new FusesPlugin({
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.RunAsNode]: false,
|
||||
[FuseV1Options.EnableCookieEncryption]: true,
|
||||
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
||||
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
||||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
893
application-client/openvidu-electron/package-lock.json
generated
893
application-client/openvidu-electron/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,55 +1,30 @@
|
||||
{
|
||||
"name": "openvidu-electron",
|
||||
"name": "basic-electron",
|
||||
"productName": "basic-electron",
|
||||
"version": "1.0.0",
|
||||
"description": "OpenVidu Electron Tutorial",
|
||||
"main": "src/main.js",
|
||||
"description": "Simple video-call application built with Electron",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "echo \"No linting configured\""
|
||||
"publish": "electron-forge publish"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "7.4.0",
|
||||
"@electron-forge/maker-deb": "7.4.0",
|
||||
"@electron-forge/maker-rpm": "7.4.0",
|
||||
"@electron-forge/maker-squirrel": "7.4.0",
|
||||
"@electron-forge/maker-zip": "7.4.0",
|
||||
"electron": "30.0.4"
|
||||
"@electron-forge/cli": "^7.4.0",
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-rpm": "^7.4.0",
|
||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||
"@electron-forge/maker-zip": "^7.4.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"electron": "30.0.8"
|
||||
},
|
||||
"author": "OpenVidu",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@electron/remote": "2.1.2",
|
||||
"axios": "1.6.8",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
"livekit-client": "2.1.3"
|
||||
},
|
||||
"config": {
|
||||
"forge": {
|
||||
"packagerConfig": {},
|
||||
"makers": [
|
||||
{
|
||||
"name": "@electron-forge/maker-squirrel",
|
||||
"config": {
|
||||
"name": "my_electron_app"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-zip",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-deb",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-rpm",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"livekit-client": "2.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
159
application-client/openvidu-electron/src/app.js
Normal file
159
application-client/openvidu-electron/src/app.js
Normal file
@ -0,0 +1,159 @@
|
||||
const { Room, RoomEvent } = require("livekit-client");
|
||||
|
||||
// Configure this constants with correct URLs depending on your deployment
|
||||
const APPLICATION_SERVER_URL = "http://localhost:6080/";
|
||||
const LIVEKIT_URL = "ws://localhost:7880/";
|
||||
|
||||
var room;
|
||||
|
||||
async function joinRoom() {
|
||||
// Initialize a new Room object
|
||||
room = new Room();
|
||||
|
||||
// Specify the actions when events take place in the room
|
||||
// On every new Track received...
|
||||
room.on(RoomEvent.TrackSubscribed, (track, _publication, participant) => {
|
||||
addTrack(track, participant.identity);
|
||||
});
|
||||
|
||||
// On every new Track destroyed...
|
||||
room.on(RoomEvent.TrackUnsubscribed, (track, _publication, participant) => {
|
||||
track.detach();
|
||||
document.getElementById(track.sid)?.remove();
|
||||
|
||||
if (track.kind === "video") {
|
||||
removeVideoContainer(participant.identity);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Get the room name and participant name from the form
|
||||
const roomName = document.getElementById("room-name").value;
|
||||
const userName = document.getElementById("participant-name").value;
|
||||
|
||||
// Get a token from your application server with the room name and participant name
|
||||
const token = await getToken(roomName, userName);
|
||||
|
||||
// Connect to the room with the LiveKit URL and the token
|
||||
await room.connect(LIVEKIT_URL, token);
|
||||
|
||||
// Hide the 'Join room' page and show the 'Room' page
|
||||
document.getElementById("room-title").innerText = roomName;
|
||||
document.getElementById("join").hidden = true;
|
||||
document.getElementById("room").hidden = false;
|
||||
|
||||
// Publish your camera and microphone
|
||||
await room.localParticipant.enableCameraAndMicrophone();
|
||||
const localVideoTrack = this.room.localParticipant.videoTrackPublications.values().next().value.track;
|
||||
addTrack(localVideoTrack, userName, true);
|
||||
} catch (error) {
|
||||
console.log("There was an error connecting to the room:", error.message);
|
||||
await leaveRoom();
|
||||
}
|
||||
}
|
||||
|
||||
function addTrack(track, participantIdentity, local = false) {
|
||||
const element = track.attach();
|
||||
element.id = track.sid;
|
||||
|
||||
/* If the track is a video track, we create a container and append the video element to it
|
||||
with the participant's identity */
|
||||
if (track.kind === "video") {
|
||||
const videoContainer = createVideoContainer(participantIdentity, local);
|
||||
videoContainer.append(element);
|
||||
appendParticipantData(videoContainer, participantIdentity + (local ? " (You)" : ""));
|
||||
} else {
|
||||
document.getElementById("layout-container").append(element);
|
||||
}
|
||||
}
|
||||
|
||||
async function leaveRoom() {
|
||||
// Leave the room by calling 'disconnect' method over the Room object
|
||||
await room.disconnect();
|
||||
|
||||
// Remove all HTML elements inside the layout container
|
||||
removeAllLayoutElements();
|
||||
|
||||
// Back to 'Join room' page
|
||||
document.getElementById("join").hidden = false;
|
||||
document.getElementById("room").hidden = true;
|
||||
}
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
room?.disconnect();
|
||||
};
|
||||
|
||||
window.onload = generateFormValues;
|
||||
|
||||
function generateFormValues() {
|
||||
document.getElementById("room-name").value = "Test Room";
|
||||
document.getElementById("participant-name").value = "Participant" + Math.floor(Math.random() * 100);
|
||||
}
|
||||
|
||||
function createVideoContainer(participantIdentity, local = false) {
|
||||
const videoContainer = document.createElement("div");
|
||||
videoContainer.id = `camera-${participantIdentity}`;
|
||||
videoContainer.className = "video-container";
|
||||
const layoutContainer = document.getElementById("layout-container");
|
||||
|
||||
if (local) {
|
||||
layoutContainer.prepend(videoContainer);
|
||||
} else {
|
||||
layoutContainer.append(videoContainer);
|
||||
}
|
||||
|
||||
return videoContainer;
|
||||
}
|
||||
|
||||
function appendParticipantData(videoContainer, participantIdentity) {
|
||||
const dataElement = document.createElement("div");
|
||||
dataElement.className = "participant-data";
|
||||
dataElement.innerHTML = `<p>${participantIdentity}</p>`;
|
||||
videoContainer.prepend(dataElement);
|
||||
}
|
||||
|
||||
function removeVideoContainer(participantIdentity) {
|
||||
const videoContainer = document.getElementById(`camera-${participantIdentity}`);
|
||||
videoContainer?.remove();
|
||||
}
|
||||
|
||||
function removeAllLayoutElements() {
|
||||
const layoutElements = document.getElementById("layout-container").children;
|
||||
Array.from(layoutElements).forEach((element) => {
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------
|
||||
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
|
||||
* --------------------------------------------
|
||||
* The method below request the creation of a token to
|
||||
* your application server. This prevents the need to expose
|
||||
* your LiveKit API key and secret to the client side.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
async function getToken(roomName, participantName) {
|
||||
const response = await fetch(APPLICATION_SERVER_URL + "token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
roomName,
|
||||
participantName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(`Failed to get token: ${error.errorMessage}`);
|
||||
}
|
||||
|
||||
const token = await response.json();
|
||||
return token.token;
|
||||
}
|
||||
@ -1,54 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Basic Electron</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" href="resources/images/favicon.ico" type="image/x-icon" />
|
||||
|
||||
<html>
|
||||
<!-- Bootstrap -->
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
<title>OpenVidu Electron</title>
|
||||
<link rel="styleSheet" href="style.css" type="text/css" media="screen">
|
||||
</head>
|
||||
<!-- Font Awesome -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||
integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
||||
<body>
|
||||
<link rel="stylesheet" href="styles.css" type="text/css" media="screen" />
|
||||
</head>
|
||||
|
||||
<h1>OpenVidu Electron</h1>
|
||||
We are using Node.js <span id="node-version"></span>,
|
||||
Chromium <span id="chrome-version"></span>,
|
||||
and Electron <span id="electron-version"></span>.
|
||||
|
||||
<div id="join">
|
||||
<h1>Join a Video Room</h1>
|
||||
<form onsubmit="joinRoom(); return false">
|
||||
<div class="form-group">
|
||||
<label for="roomName">Room Name:</label>
|
||||
<input type="text" id="roomName" placeholder="Enter room name" value="RoomA" required>
|
||||
<body>
|
||||
<header>
|
||||
<a href="/" title="Home"><h1>Basic Electron</h1></a>
|
||||
<div id="links">
|
||||
<a
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-electron"
|
||||
title="GitHub Repository"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
<a
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/electron/"
|
||||
title="Documentation"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="fa-solid fa-book"></i>
|
||||
</a>
|
||||
</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">
|
||||
<button type="submit">JOIN</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="room" style="display: none;">
|
||||
<h1 id="room-header"></h1>
|
||||
<button class="leave-button" onclick="leaveRoom()" >LEAVE</button>
|
||||
<button class="share-screen-button" onclick="toggleScreenShare()">Toggle screenshare</button>
|
||||
<div>
|
||||
<div id="local-participant">
|
||||
<h3>YOU</h3>
|
||||
<main>
|
||||
<div id="join">
|
||||
<div id="join-dialog">
|
||||
<h2>Join a Video Room</h2>
|
||||
<form onsubmit="joinRoom(); return false">
|
||||
<div>
|
||||
<label for="participant-name">Participant</label>
|
||||
<input id="participant-name" class="form-control" type="text" required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="room-name">Room</label>
|
||||
<input id="room-name" class="form-control" type="text" required />
|
||||
</div>
|
||||
<button class="btn btn-lg btn-success" type="submit">Join!</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="remote-participants">
|
||||
<h3>OTHERS</h3>
|
||||
|
||||
<div id="room" hidden>
|
||||
<div id="room-header">
|
||||
<h2 id="room-title"></h2>
|
||||
<button class="btn btn-danger" id="leave-room-button" onclick="leaveRoom()">Leave Room</button>
|
||||
</div>
|
||||
<div id="layout-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="index.js"></script>
|
||||
<footer>
|
||||
<p class="text">Made with love by <span>OpenVidu Team</span></p>
|
||||
<a href="http://www.openvidu.io/" target="_blank">
|
||||
<img id="openvidu-logo" src="resources/images/openvidu_logo.png" alt="OpenVidu logo" />
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,196 +1,50 @@
|
||||
const axios = require("axios");
|
||||
const { Room, RoomEvent } = require("livekit-client");
|
||||
const { app, BrowserWindow } = require("electron");
|
||||
const path = require("node:path");
|
||||
const livekit = require("livekit-client");
|
||||
|
||||
const ipcRenderer = require("electron").ipcRenderer;
|
||||
const { BrowserWindow } = require("@electron/remote");
|
||||
var room;
|
||||
var myParticipantName;
|
||||
var myRoomName;
|
||||
var isScreenShared = false;
|
||||
var screenSharePublication;
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require("electron-squirrel-startup")) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// Configure this constants with correct URLs depending on your deployment
|
||||
const APPLICATION_SERVER_URL = "http://localhost:6080/";
|
||||
const LIVEKIT_URL = "ws://localhost:7880/";
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 720,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("screen-share-ready", async (event, sourceId) => {
|
||||
if (sourceId) {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: {
|
||||
mandatory: {
|
||||
chromeMediaSource: "desktop",
|
||||
chromeMediaSourceId: sourceId,
|
||||
},
|
||||
},
|
||||
});
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile(path.join(__dirname, "index.html"));
|
||||
|
||||
const track = stream.getVideoTracks()[0];
|
||||
const screenPublication = await room.localParticipant.publishTrack(track);
|
||||
isScreenShared = true;
|
||||
screenSharePublication = screenPublication;
|
||||
const element = screenPublication.track.attach();
|
||||
element.id = screenPublication.trackSid;
|
||||
element.className = "removable";
|
||||
document.getElementById("local-participant").appendChild(element);
|
||||
} catch (error) {
|
||||
console.error("Error enabling screen sharing", error);
|
||||
}
|
||||
}
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function joinRoom() {
|
||||
myRoomName = document.getElementById("roomName").value;
|
||||
myParticipantName = document.getElementById("participantName").value;
|
||||
|
||||
// --- 1) Get a Room object ---
|
||||
room = new Room();
|
||||
|
||||
// --- 2) Specify the actions when events take place in the room ---
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
// --- 3) Connect to the room with a valid access token ---
|
||||
|
||||
// Get a token from the application backend
|
||||
const token = await getToken(myRoomName, myParticipantName);
|
||||
|
||||
await room.connect(LIVEKIT_URL, token);
|
||||
|
||||
showRoom();
|
||||
// --- 4) Publish your local tracks ---
|
||||
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);
|
||||
}
|
||||
|
||||
async function toggleScreenShare() {
|
||||
console.log("Toggling screen share");
|
||||
const enabled = !isScreenShared;
|
||||
|
||||
if (enabled) {
|
||||
openScreenShareModal();
|
||||
} else {
|
||||
// Disable screen sharing
|
||||
await stopScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
async function stopScreenSharing() {
|
||||
try {
|
||||
await room.localParticipant.unpublishTrack(screenSharePublication.track);
|
||||
isScreenShared = false;
|
||||
const trackSid = screenSharePublication?.trackSid;
|
||||
|
||||
if (trackSid) {
|
||||
document.getElementById(trackSid)?.remove();
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
screenSharePublication = undefined;
|
||||
} catch (error) {
|
||||
console.error("Error stopping screen sharing", error);
|
||||
}
|
||||
}
|
||||
|
||||
function leaveRoom() {
|
||||
// --- 5) 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
|
||||
removeAllParticipantElements();
|
||||
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 removeAllParticipantElements() {
|
||||
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);
|
||||
|
||||
win.setMenu(null);
|
||||
// win.webContents.openDevTools();
|
||||
|
||||
var theUrl = "file://" + __dirname + "/modal.html";
|
||||
win.loadURL(theUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------
|
||||
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
|
||||
* --------------------------------------------
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
async function getToken(roomName, participantName) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
APPLICATION_SERVER_URL + "token",
|
||||
{
|
||||
roomName,
|
||||
participantName,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("No connection to application server", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,64 +0,0 @@
|
||||
// Modules to control application life and create native browser window
|
||||
const { app, BrowserWindow, ipcMain } = require("electron");
|
||||
const path = require("path");
|
||||
require("@electron/remote/main").initialize();
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 720,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Enable remote module in the main window WebContents.
|
||||
require("@electron/remote/main").enable(mainWindow.webContents);
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile("src/index.html");
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
ipcMain.on("screen-share-selected", (event, message) => {
|
||||
mainWindow.webContents.send("screen-share-ready", message);
|
||||
});
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
app.on("activate", function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on("window-all-closed", function () {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on(
|
||||
"certificate-error",
|
||||
(event, webContents, url, error, certificate, callback) => {
|
||||
// On certificate error we disable default behaviour (stop loading the page)
|
||||
// and we then say "it is all fine - true" to the callback
|
||||
event.preventDefault();
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here.
|
||||
@ -1,179 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#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; */
|
||||
|
||||
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 */
|
||||
|
||||
/* Additional styling for the grid container */
|
||||
padding: 16px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.screen-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.screen-item img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.screen-item span {
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 95%;
|
||||
height: 50px;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0px;
|
||||
}
|
||||
|
||||
#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>
|
||||
// Define arrays to store available screens and HTML elements
|
||||
var availableScreens = [];
|
||||
var htmlElements = [];
|
||||
var selectedElement;
|
||||
|
||||
// Import required Electron modules
|
||||
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>
|
||||
@ -1,12 +0,0 @@
|
||||
// All of the Node.js APIs are available in the preload process.
|
||||
// It has the same sandbox as a Chrome extension.
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency])
|
||||
}
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@ -1,92 +0,0 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
display: table;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#local-participant {
|
||||
float: left;
|
||||
margin: 10px;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#remote-participants {
|
||||
float: right;
|
||||
margin: 10px;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.leave-button,
|
||||
.share-screen-button {
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
width: 50%;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
.leave-button {
|
||||
background-color: rgb(151, 28, 28);
|
||||
}
|
||||
.share-screen-button {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
264
application-client/openvidu-electron/src/styles.css
Normal file
264
application-client/openvidu-electron/src/styles.css
Normal file
@ -0,0 +1,264 @@
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-top: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 30px;
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header a {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
color: #a9a9a9;
|
||||
}
|
||||
|
||||
header i {
|
||||
padding: 5px 5px;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#join {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#join-dialog {
|
||||
width: 70%;
|
||||
max-width: 900px;
|
||||
padding: 60px;
|
||||
border-radius: 6px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#join-dialog h2 {
|
||||
color: #4d4d4d;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#join-dialog form {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#join-dialog label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: #0088aa;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
color: #0088aa;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
color: #0088aa;
|
||||
border-color: #0088aa;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(0, 136, 170, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(0, 136, 170, 0.6);
|
||||
}
|
||||
|
||||
#join-dialog button {
|
||||
display: block;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #06d362;
|
||||
border-color: #06d362;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #1abd61;
|
||||
border-color: #1abd61;
|
||||
}
|
||||
|
||||
#room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#room-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#room-title {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#layout-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
background: #3b3b3b;
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-container video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-container .participant-data {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.participant-data p {
|
||||
background: #f8f8f8;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
color: #777777;
|
||||
font-weight: bold;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
padding: 10px 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer .text {
|
||||
color: #ccc;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer .text span {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#openvidu-logo {
|
||||
height: 35px;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
-moz-transition: all 0.1s ease-in-out;
|
||||
-o-transition: all 0.1s ease-in-out;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
#openvidu-logo:hover {
|
||||
-webkit-filter: grayscale(0.5);
|
||||
filter: grayscale(0.5);
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
@media screen and (max-width: 768px) {
|
||||
header {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
#join-dialog {
|
||||
width: 90%;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
#join-dialog h2 {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
#layout-container {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
header {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#join-dialog {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#join-dialog h2 {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
@ -33,14 +33,14 @@
|
||||
<a href="/" title="Home"><h1>Basic React</h1></a>
|
||||
<div id="links">
|
||||
<a
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-js"
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-react"
|
||||
title="GitHub Repository"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
<a
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/javascript/"
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/react/"
|
||||
title="Documentation"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
@ -34,14 +34,14 @@
|
||||
<a href="/" title="Home"><h1>Basic Vue</h1></a>
|
||||
<div id="links">
|
||||
<a
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-js"
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/application-client/openvidu-vue"
|
||||
title="GitHub Repository"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
<a
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/javascript/"
|
||||
href="https://livekit-tutorials.openvidu.io/tutorials/application-client/vue/"
|
||||
title="Documentation"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user