vdo.ninja docker

This commit is contained in:
Elias Stepanik 2023-09-25 19:43:56 +02:00
parent 7694dec33a
commit a0286f213e
279 changed files with 73673 additions and 73623 deletions

7
app/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM nginx:alpine
WORKDIR /app
COPY .. .
COPY nginx.conf /etc/nginx/nginx.conf

10
app/docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
version: "3.9"
services:
app:
container_name: app
image: app
build: .
restart: always
ports:
- "8080:80"

33
app/nginx.conf Normal file
View File

@ -0,0 +1,33 @@
events {
worker_connections 1024;
}
http {
include mime.types;
sendfile on;
server {
listen 80;
listen [::]:80;
server_name vdo.ninja;
root /app/static;
index index.html;
location ~ ^/([^/]+)/([^/?]+)$ {
root /app/static;
try_files /$1/$2 /$1/$2.html /$1/$2/ /$2 /$2/ /$1/index.html;
add_header Access-Control-Allow-Origin *;
}
location / {
if ($request_uri ~ ^/(.*)\.html$) {
return 302 /$1;
}
try_files $uri $uri.html $uri/ /index.html;
add_header Access-Control-Allow-Origin *;
}
}
}

View File

View File

@ -1,19 +1,19 @@
# Contributor License Agreement (CLA) # Contributor License Agreement (CLA)
To ensure the long-term viability of this project, and for the protection of its creator and its users, we request that contributors to the project first agree to some basic terms. The terms when accepted applies to all of your past, present and future contributions. To ensure the long-term viability of this project, and for the protection of its creator and its users, we request that contributors to the project first agree to some basic terms. The terms when accepted applies to all of your past, present and future contributions.
# Contribution Policy # Contribution Policy
You are invited to digitally sign the CLA with the provided CLA Assissant service, with a link to sign it provided automatically after contributing to this Github project. You may also print, sign, scan, and then email the CLA to steve@seguin.email. You are invited to digitally sign the CLA with the provided CLA Assissant service, with a link to sign it provided automatically after contributing to this Github project. You may also print, sign, scan, and then email the CLA to steve@seguin.email.
It is not required that you sign the CLA for every contribution. Once you execute a CLA, it is valid until the CLA agreement is meaningfully changed and requires updating. It is not required that you sign the CLA for every contribution. Once you execute a CLA, it is valid until the CLA agreement is meaningfully changed and requires updating.
If you are contributing on behalf of your company, an officer of your company (usually a VP or higher title) must sign the CLA on behalf of the company, indicating his or her title. The company can choose to list the specific individuals authorized to make contributions on the "Full Name" line, or may cover all employees with a blanket CLA by not limiting contributors to an authorized list. If necessary, the company may provide a list of authorized contributors in an attachment. The executive signing the CLA must be the first name on such an attached list, and this executive must sign the attachment as well. It may well be the case that your company already has signed a company-wide CLA with Stephen Seguin. Please check this first. If you are contributing on behalf of your company, an officer of your company (usually a VP or higher title) must sign the CLA on behalf of the company, indicating his or her title. The company can choose to list the specific individuals authorized to make contributions on the "Full Name" line, or may cover all employees with a blanket CLA by not limiting contributors to an authorized list. If necessary, the company may provide a list of authorized contributors in an attachment. The executive signing the CLA must be the first name on such an attached list, and this executive must sign the attachment as well. It may well be the case that your company already has signed a company-wide CLA with Stephen Seguin. Please check this first.
You can stop your participation in a project at any time, but you cannot rescind your assignments or grants with respect to prior contributions. You can stop your participation in a project at any time, but you cannot rescind your assignments or grants with respect to prior contributions.
You hereby grant Steve Seguin (Steve) a perpetual, worldwide, royalty-free, irrevocable, non-exclusive, and transferable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, distribute the submissions, and to sublicense such rights to others. The rights granted may be exercised in any form or format, and Steve may distribute and sublicense to others on any licensing terms, including without limitation: (a) open source licenses like the GNU General Public License (GPL), or the Berkeley Software Distribution license (BSD); or (b) binary, proprietary, or commercial licenses. You hereby grant Steve Seguin (Steve) a perpetual, worldwide, royalty-free, irrevocable, non-exclusive, and transferable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, distribute the submissions, and to sublicense such rights to others. The rights granted may be exercised in any form or format, and Steve may distribute and sublicense to others on any licensing terms, including without limitation: (a) open source licenses like the GNU General Public License (GPL), or the Berkeley Software Distribution license (BSD); or (b) binary, proprietary, or commercial licenses.
You hereby represent that you are the sole and original author of all Submissions and that, to the best of your knowledge, the submissions do not infringe upon the rights of any third party. You hereby represent that you are the sole and original author of all Submissions and that, to the best of your knowledge, the submissions do not infringe upon the rights of any third party.
You agree to these terms with your continued submission. You agree to these terms with your continued submission.

View File

@ -1,14 +1,14 @@
The VDO.Ninja source repository is governed by the GNU AFFERO GENERAL PUBLIC LICENSE. (AGPL-3.0) The VDO.Ninja source repository is governed by the GNU AFFERO GENERAL PUBLIC LICENSE. (AGPL-3.0)
That AGPL-3.0 licence can be found here: [AGPLv3.md](https://github.com/steveseguin/vdo.ninja/blob/master/AGPLv3.md) That AGPL-3.0 licence can be found here: [AGPLv3.md](https://github.com/steveseguin/vdo.ninja/blob/master/AGPLv3.md)
In essence, VDO.Ninja is open-source and free to use, both for commercial and non-commercial use. In essence, VDO.Ninja is open-source and free to use, both for commercial and non-commercial use.
Modifications of AGPL-3.0 licenced code must be made publicly accessible. Please refer to that licence. Modifications of AGPL-3.0 licenced code must be made publicly accessible. Please refer to that licence.
Some individual source files may contain different licencing term and perhaps different copyright holders. Some individual source files may contain different licencing term and perhaps different copyright holders.
Such licencing and copyright information will be contained in the file's header and be limited to those files. Such licencing and copyright information will be contained in the file's header and be limited to those files.
If no such header is present in a file, the default AGPL-3.0 licence applies. If no such header is present in a file, the default AGPL-3.0 licence applies.
Unless stated otherwise, all code is copyright 2021 Stephen Seguin. All rights reserved. Unless stated otherwise, all code is copyright 2021 Stephen Seguin. All rights reserved.
Contributors to the VDO.Ninja project must first agree to the Contributor License Agreement (CLA). Contributors to the VDO.Ninja project must first agree to the Contributor License Agreement (CLA).
Thank you for your understanding. Thank you for your understanding.

File diff suppressed because it is too large Load Diff

View File

@ -1,267 +1,267 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Generate Cloudflare Auth</title> <title>Generate Cloudflare Auth</title>
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background-color: #f5f5f5; background-color: #f5f5f5;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: #f0f0f0; background-color: #f0f0f0;
background-image: background-image:
linear-gradient(to right, #e0e0e0 1px, transparent 1px), linear-gradient(to right, #e0e0e0 1px, transparent 1px),
linear-gradient(to bottom, #e0e0e0 1px, transparent 1px); linear-gradient(to bottom, #e0e0e0 1px, transparent 1px);
background-size: 10px 10px; background-size: 10px 10px;
backdrop-filter: blur(3px); backdrop-filter: blur(3px);
} }
h1 { h1 {
color: #333; color: #333;
margin-bottom: 20px; margin-bottom: 20px;
} }
form { form {
background-color: #fff; background-color: #fff;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
width: 300px; width: 300px;
} }
label { label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
color: #555; color: #555;
} }
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="number"] { input[type="number"] {
width: 93%; width: 93%;
padding: 8px; padding: 8px;
margin-bottom: 15px; margin-bottom: 15px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
} }
input[type="number"] { input[type="number"] {
max-width:80px; max-width:80px;
} }
button { button {
background-color: #007bff; background-color: #007bff;
color: #fff; color: #fff;
padding: 10px 20px; padding: 10px 20px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
} }
button:hover { button:hover {
background-color: #0056b3; background-color: #0056b3;
} }
textarea { textarea {
width: 100%; width: 100%;
padding: 8px; padding: 8px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
resize: vertical; resize: vertical;
max-width: 500px; max-width: 500px;
min-height: 100px; min-height: 100px;
} }
.section{ .section{
max-width:700px; max-width:700px;
padding: 20px; padding: 20px;
overflow: auto; overflow: auto;
} }
.main { .main {
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
align-content: space-around; align-content: space-around;
justify-content: center; justify-content: center;
} }
.secondary { .secondary {
padding: 50px; padding: 50px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
align-content: space-around; align-content: space-around;
justify-content: center; justify-content: center;
max-width:1200px; max-width:1200px;
margin:auto; margin:auto;
} }
</style> </style>
</head> </head>
<body> <body>
<div class='main'> <div class='main'>
<h1>Generate Cloudflare Auth for VDO.Ninja</h1> <h1>Generate Cloudflare Auth for VDO.Ninja</h1>
<form id="postForm"> <form id="postForm">
<label for="userId">Cloudflare Account ID:</label> <label for="userId">Cloudflare Account ID:</label>
<input type="text" id="userId" name="userId" required> <input type="text" id="userId" name="userId" required>
<br><br> <br><br>
<label for="accessToken">Cloudflare Stream Access Token:</label> <label for="accessToken">Cloudflare Stream Access Token:</label>
<input type="password" id="accessToken" name="accessToken" required> <input type="password" id="accessToken" name="accessToken" required>
<br><br> <br><br>
<label for="expiration">Hours until expiration</label> <label for="expiration">Hours until expiration</label>
<input min="0" type="number" id="expiration" name="expiration" placeholder="optional"> <input min="0" type="number" id="expiration" name="expiration" placeholder="optional">
<br><br> <br><br>
<button type="button" id="submitButton">Generate</button> <button type="button" id="submitButton">Generate</button>
</form> </form>
<br><br> <br><br>
<label for="response">Generated URL parameter to add to VDO.Ninja:</label> <label for="response">Generated URL parameter to add to VDO.Ninja:</label>
<textarea id="response" rows="5" cols="50" readonly placeholder="Use this parameter with your VDO.Ninja links in place of &meshcast"></textarea> <textarea id="response" rows="5" cols="50" readonly placeholder="Use this parameter with your VDO.Ninja links in place of &meshcast"></textarea>
<div class="section"> <div class="section">
<h2> <h2>
What you can do with Cloudflare + VDO.Ninja? What you can do with Cloudflare + VDO.Ninja?
</h2> </h2>
<h3>Restream VDO.Ninja as an RTMP Output</h3> <h3>Restream VDO.Ninja as an RTMP Output</h3>
<p>Live Video Inputs (Cloudflare feature) can be set up to forward any input to another input. This can be a RTMP(S) service such as YouTube, Twitch or Facebook Live.</p> <p>Live Video Inputs (Cloudflare feature) can be set up to forward any input to another input. This can be a RTMP(S) service such as YouTube, Twitch or Facebook Live.</p>
<p>In theory you could publish from VDO.Ninja WHIP output to Cloudstream, and then to your RTMP destinations, like Youtube.</p> <p>In theory you could publish from VDO.Ninja WHIP output to Cloudstream, and then to your RTMP destinations, like Youtube.</p>
<p></p> <p></p>
<h3>Meshcast-alternative</h3> <h3>Meshcast-alternative</h3>
<p>Instead of using Meshcast to broadcast video from director to guest, or guest to scene, you can use Cloudflare instead.</p> <p>Instead of using Meshcast to broadcast video from director to guest, or guest to scene, you can use Cloudflare instead.</p>
<p>Meshcast, or any compatible WHIP/WHEP service, can help reduce CPU and network load of guests by offloading distribution to a server, compared to using the peer-to-peer default of VDO.Ninja <p>Meshcast, or any compatible WHIP/WHEP service, can help reduce CPU and network load of guests by offloading distribution to a server, compared to using the peer-to-peer default of VDO.Ninja
<h3>Automatic isolated guest recording</h3> <h3>Automatic isolated guest recording</h3>
<p>Cloudflare will automatically record incoming videos, allowing you (in theory) to have a backup of each guest in a room.</p> <p>Cloudflare will automatically record incoming videos, allowing you (in theory) to have a backup of each guest in a room.</p>
<p>This offers a redundant backup for your recordings, but also makes it easier to do higher quality VODs edits after the live ends.</p> <p>This offers a redundant backup for your recordings, but also makes it easier to do higher quality VODs edits after the live ends.</p>
<h3>SRT, HLS, DASH, MP4, WHIP/WHEP options</h3> <h3>SRT, HLS, DASH, MP4, WHIP/WHEP options</h3>
<p>Lots input and output options, although if you're here, you're probably interested in the WHIP/WHEP mainly.</p> <p>Lots input and output options, although if you're here, you're probably interested in the WHIP/WHEP mainly.</p>
<p>VDO.Ninja is compatible with WHEP and WHIP!</p> <p>VDO.Ninja is compatible with WHEP and WHIP!</p>
<h3>Very competitive pricing</h3> <h3>Very competitive pricing</h3>
<p>There's a free tier, which is more than enough for testing.</p> <p>There's a free tier, which is more than enough for testing.</p>
<p>Or pay $1 per $1000 minutes of streaming.</p> <p>Or pay $1 per $1000 minutes of streaming.</p>
</div> </div>
</div> </div>
<div class='secondary'> <div class='secondary'>
<h2> <h2>
How it works? How it works?
</h2> </h2>
<p>When used with VDO.Ninja, video is published to Cloudflare via WHIP, and the WHEP playback URL is distributed to viewers. Unless otherwise specified, viewers will use the WHEP URL as the source of media from the publisher, instead of using the normal peer-to-peer mode. This has the effect of reducing the CPU and network load when sharing media with multiple videos, as instead of distributing media via peer-to-peer, the media is distributed via a server. This approach does have some downsides also, so its not normally advisable unless desired or needed.</p> <p>When used with VDO.Ninja, video is published to Cloudflare via WHIP, and the WHEP playback URL is distributed to viewers. Unless otherwise specified, viewers will use the WHEP URL as the source of media from the publisher, instead of using the normal peer-to-peer mode. This has the effect of reducing the CPU and network load when sharing media with multiple videos, as instead of distributing media via peer-to-peer, the media is distributed via a server. This approach does have some downsides also, so its not normally advisable unless desired or needed.</p>
<h2> <h2>
Why do I need a special URL parameter? Why do I need a special URL parameter?
</h2> </h2>
<p>The reason we need a special generated URL parameter is because Cloudflare requires user accounts, unlike Meshcast. While you can generate WHIP URLs within your Cloudflare dashboard, and use them on VDO.Ninja links using &whipout, you'd need to create one per guest. Instead here, we're using our Cloudflare credentials to automatically create unique WHIP ingest URLs on demand for each guest, so you can get away with one-invite link for all your guests.</p> <p>The reason we need a special generated URL parameter is because Cloudflare requires user accounts, unlike Meshcast. While you can generate WHIP URLs within your Cloudflare dashboard, and use them on VDO.Ninja links using &whipout, you'd need to create one per guest. Instead here, we're using our Cloudflare credentials to automatically create unique WHIP ingest URLs on demand for each guest, so you can get away with one-invite link for all your guests.</p>
<p>Since it's not advisable to share your Cloudflare credentials, particularly with random guests, this page will encrypt your credentials into URL-friendly parameter. Only the VDO.Ninja servers knows the decryption key, which limits what guest can do with the encrypted key. You can delete or restrict the credentials provided to VDO.Ninja from your Cloudflare dashboard, allowing you to limit or revoke any trust provided to VDO.Ninja.</p> <p>Since it's not advisable to share your Cloudflare credentials, particularly with random guests, this page will encrypt your credentials into URL-friendly parameter. Only the VDO.Ninja servers knows the decryption key, which limits what guest can do with the encrypted key. You can delete or restrict the credentials provided to VDO.Ninja from your Cloudflare dashboard, allowing you to limit or revoke any trust provided to VDO.Ninja.</p>
<h2> <h2>
Where to get my Cloudflare account ID and token? Where to get my Cloudflare account ID and token?
</h2> </h2>
<p>The Cloudflare account ID can be found on the right-hand side of the Workers & Pages (Overview) page, or it can be found on the right-lower side of any of your Website (domain) overview pages.</p> <p>The Cloudflare account ID can be found on the right-hand side of the Workers & Pages (Overview) page, or it can be found on the right-lower side of any of your Website (domain) overview pages.</p>
<p> <p>
As for the API token, you'll need to create it, with limited permissions. As for the API token, you'll need to create it, with limited permissions.
<ul> <ul>
<li>Go to <a href='https://dash.cloudflare.com/profile/api-tokens' target="_blank">https://dash.cloudflare.com/profile/api-tokens</a></li> <li>Go to <a href='https://dash.cloudflare.com/profile/api-tokens' target="_blank">https://dash.cloudflare.com/profile/api-tokens</a></li>
<li>Click to Create Token (API Tokens)</li> <li>Click to Create Token (API Tokens)</li>
<li>Click Get started with the Create Custom Token option</li> <li>Click Get started with the Create Custom Token option</li>
<li>Provide a token name, and for the permissions select Account -> Stream -> Edit</li> <li>Provide a token name, and for the permissions select Account -> Stream -> Edit</li>
<li>You can define a time-to-live (TTL), if you wish for the token to auto-expire.</li> <li>You can define a time-to-live (TTL), if you wish for the token to auto-expire.</li>
</ul> </ul>
You should now have access to both access token and account ID. You should now have access to both access token and account ID.
</p> </p>
<h2> <h2>
Can I self-host or hard-code my Cloudflare credentials? Can I self-host or hard-code my Cloudflare credentials?
</h2> </h2>
<p>Yes, the code is open-sourced and it can be self-hosted, however please be aware there is limited support for those self-hosting.</p> <p>Yes, the code is open-sourced and it can be self-hosted, however please be aware there is limited support for those self-hosting.</p>
<h2> <h2>
Does VDO.Ninja track me or store my private information? Does VDO.Ninja track me or store my private information?
</h2> </h2>
<p>Please refer to the <a href='https://docs.vdo.ninja/help/privacy-and-security-details'>privacy policy</a>, although the short answer is no. I can't say the same for Cloudflare, so please refer to their terms of service. <p>Please refer to the <a href='https://docs.vdo.ninja/help/privacy-and-security-details'>privacy policy</a>, although the short answer is no. I can't say the same for Cloudflare, so please refer to their terms of service.
</div> </div>
<script> <script>
function removeStorage(cname){ function removeStorage(cname){
localStorage.removeItem(cname); localStorage.removeItem(cname);
} }
function clearStorage(){ function clearStorage(){
localStorage.clear(); localStorage.clear();
if (!session.cleanOutput){ if (!session.cleanOutput){
warnUser("The local storage and saved settings have been cleared", 1000); warnUser("The local storage and saved settings have been cleared", 1000);
} }
} }
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date(); var now = new Date();
var item = { var item = {
value: cvalue, value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000), expiry: now.getTime() + (hours * 60 * 60 * 1000),
}; };
try{ try{
localStorage.setItem(cname, JSON.stringify(item)); localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);} }catch(e){errorlog(e);}
} }
function getStorage(cname) { function getStorage(cname) {
try { try {
var itemStr = localStorage.getItem(cname); var itemStr = localStorage.getItem(cname);
} catch(e){ } catch(e){
errorlog(e); errorlog(e);
return; return;
} }
if (!itemStr) { if (!itemStr) {
return ""; return "";
} }
var item = JSON.parse(itemStr); var item = JSON.parse(itemStr);
var now = new Date(); var now = new Date();
if (now.getTime() > item.expiry) { if (now.getTime() > item.expiry) {
localStorage.removeItem(cname); localStorage.removeItem(cname);
return ""; return "";
} }
return item.value; return item.value;
} }
document.getElementById("accessToken").value = getStorage("accessToken") || ""; document.getElementById("accessToken").value = getStorage("accessToken") || "";
document.getElementById("userId").value = getStorage("userId") || ""; document.getElementById("userId").value = getStorage("userId") || "";
document.getElementById("expiration").value = getStorage("expiration") || ""; document.getElementById("expiration").value = getStorage("expiration") || "";
document.getElementById("submitButton").addEventListener("click", async function () { document.getElementById("submitButton").addEventListener("click", async function () {
const accessToken = document.getElementById("accessToken").value; const accessToken = document.getElementById("accessToken").value;
const userId = document.getElementById("userId").value; const userId = document.getElementById("userId").value;
const expiration = document.getElementById("expiration").value; const expiration = document.getElementById("expiration").value;
if (!accessToken || !userId) { if (!accessToken || !userId) {
alert("Access Token and User ID are required."); alert("Access Token and User ID are required.");
return; return;
} }
const data = { const data = {
accessToken: accessToken, accessToken: accessToken,
userId: userId, userId: userId,
expiration: Math.round(expiration*60) expiration: Math.round(expiration*60)
}; };
try { try {
const response = await fetch("https://cloudflare.vdo.ninja/encode/", { const response = await fetch("https://cloudflare.vdo.ninja/encode/", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
const responseData = await response.text(); const responseData = await response.text();
console.log(responseData); console.log(responseData);
setStorage("accessToken",accessToken); setStorage("accessToken",accessToken);
setStorage("userId", userId); setStorage("userId", userId);
setStorage("expiration", expiration); setStorage("expiration", expiration);
document.getElementById("response").value = "&cftoken="+responseData; document.getElementById("response").value = "&cftoken="+responseData;
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
document.getElementById("response").value = "An error occurred."; document.getElementById("response").value = "An error occurred.";
} }
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,84 +1,84 @@
<html> <html>
<body> <body>
<div id="output"></div> <div id="output"></div>
<script> <script>
function getSupportedMimeTypes(media, types, codecs) { function getSupportedMimeTypes(media, types, codecs) {
const isSupported = MediaRecorder.isTypeSupported; const isSupported = MediaRecorder.isTypeSupported;
const supported = []; const supported = [];
types.forEach((type) => { types.forEach((type) => {
const mimeType = `${media}/${type}`; const mimeType = `${media}/${type}`;
acodecs.forEach((codec2) => { acodecs.forEach((codec2) => {
codecs.forEach((codec) => [ codecs.forEach((codec) => [
mimeType+';codecs="'+codec+', '+codec2+'"', mimeType+';codecs="'+codec+', '+codec2+'"',
mimeType+';codecs:"'+codec+', '+codec2+'"', mimeType+';codecs:"'+codec+', '+codec2+'"',
mimeType+';codecs="'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"', mimeType+';codecs="'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"',
mimeType+';codecs:"'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"' mimeType+';codecs:"'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"'
].forEach(variation => { ].forEach(variation => {
if (isSupported(variation)){ if (isSupported(variation)){
try { try {
options.mimeType = variation; options.mimeType = variation;
new MediaRecorder(stream, options); new MediaRecorder(stream, options);
mediaSource.addSourceBuffer(variation); mediaSource.addSourceBuffer(variation);
supported.push(variation); supported.push(variation);
} catch(e){ } catch(e){
console.error(e); console.error(e);
} }
} }
})); }));
}); });
if (isSupported(mimeType)) if (isSupported(mimeType))
supported.push(mimeType); supported.push(mimeType);
}); });
return supported; return supported;
}; };
var options = {}; var options = {};
options.videoBitsPerSecond = parseInt(2500 * 1024); options.videoBitsPerSecond = parseInt(2500 * 1024);
const videoTypes = ["webm", "ogg", "mp4", "x-matroska"]; const videoTypes = ["webm", "ogg", "mp4", "x-matroska"];
const audioTypes = ["webm", "ogg", "mp3", "x-matroska"]; const audioTypes = ["webm", "ogg", "mp3", "x-matroska"];
const codecs = ["vp9", "vp9.0", "vp8", "vp8.0", "avc1", "av1", "h265", "h.265", "h264", "h.264"] const codecs = ["vp9", "vp9.0", "vp8", "vp8.0", "avc1", "av1", "h265", "h.265", "h264", "h.264"]
const acodecs = ["opus", "pcm", "aac", "mpeg", "mp4a", "mp3", "vorbis"]; const acodecs = ["opus", "pcm", "aac", "mpeg", "mp4a", "mp3", "vorbis"];
document.getElementById("output").innerHTML= ""; document.getElementById("output").innerHTML= "";
var supportedVideos = null; var supportedVideos = null;
var supportedAudios = null; var supportedAudios = null;
// Usage ------------------ // Usage ------------------
var stream = null; var stream = null;
var mediaSource = new MediaSource(); var mediaSource = new MediaSource();
var video = document.createElement("video"); var video = document.createElement("video");
video.autoplay = true; video.autoplay = true;
video.muted = false; video.muted = false;
video.setAttribute("playsinline",""); video.setAttribute("playsinline","");
video.src = URL.createObjectURL(mediaSource); video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen); mediaSource.addEventListener('sourceopen', sourceOpen);
console.log("1"); console.log("1");
function sourceOpen(e) { function sourceOpen(e) {
console.log("2"); console.log("2");
navigator.mediaDevices.getUserMedia({audio:true,video:true}).then(function(s) { navigator.mediaDevices.getUserMedia({audio:true,video:true}).then(function(s) {
stream = s; stream = s;
supportedVideos = getSupportedMimeTypes("video", videoTypes, codecs); supportedVideos = getSupportedMimeTypes("video", videoTypes, codecs);
supportedAudios = getSupportedMimeTypes("audio", audioTypes, codecs); supportedAudios = getSupportedMimeTypes("audio", audioTypes, codecs);
for (var i=0;i<supportedVideos.length;i++){ for (var i=0;i<supportedVideos.length;i++){
document.getElementById("output").innerHTML += supportedVideos[i]+"<br/>"; document.getElementById("output").innerHTML += supportedVideos[i]+"<br/>";
} }
}).catch(function(err) { }).catch(function(err) {
/* handle the error */ /* handle the error */
}); });
} }
//for (var i=0;i<supportedAudios.length;i++){ //for (var i=0;i<supportedAudios.length;i++){
//document.getElementById("output").innerHTML += supportedAudios[i]+"<br/>"; //document.getElementById("output").innerHTML += supportedAudios[i]+"<br/>";
//} //}
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

View File

@ -1,121 +1,121 @@
<html> <html>
<head> <head>
<style> <style>
body { body {
margin:0; margin:0;
padding:0; padding:0;
height:100%; height:100%;
width:100%; width:100%;
border:0; border:0;
overflow:hidden; overflow:hidden;
} }
</style> </style>
</head> </head>
<body id="body"> <body id="body">
<button onclick='send({"abc1231":[0,0,50,50],abc1232:[50,0,50,50],abc1233:[0,50,50,50],abc1234:[50,50,50,50]});'>2x2</button> <button onclick='send({"abc1231":[0,0,50,50],abc1232:[50,0,50,50],abc1233:[0,50,50,50],abc1234:[50,50,50,50]});'>2x2</button>
<button onclick='send({"abc1231":[0,0,100,100],abc1232:[0, 0, 0, 0 ],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>1</button> <button onclick='send({"abc1231":[0,0,100,100],abc1232:[0, 0, 0, 0 ],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>1</button>
<button onclick='send({"abc1231":[0,0,50 ,100],abc1232:[50,0,100,100],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>2x1</button> <button onclick='send({"abc1231":[0,0,50 ,100],abc1232:[50,0,100,100],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>2x1</button>
<button onclick='send({"abc1231":[0,0,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button> <button onclick='send({"abc1231":[0,0,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
<script> <script>
function updateURL(param, force=false) { function updateURL(param, force=false) {
var para = param.split('=')[0]; var para = param.split('=')[0];
if (!(urlParams.has(para)) || (force)){ if (!(urlParams.has(para)) || (force)){
if (history.pushState){ if (history.pushState){
var arr = window.location.href.split('?'); var arr = window.location.href.split('?');
var newurl; var newurl;
if (arr.length > 1 && arr[1] !== '') { if (arr.length > 1 && arr[1] !== '') {
newurl = window.location.href + '&' +param; newurl = window.location.href + '&' +param;
} else { } else {
newurl = window.location.href + '?' +param; newurl = window.location.href + '?' +param;
} }
window.history.pushState({path:newurl},'',newurl); window.history.pushState({path:newurl},'',newurl);
} }
} }
} }
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams || function (searchString) { w.URLSearchParams = w.URLSearchParams || function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} }
else { else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
function generateStreamID(){ function generateStreamID(){
var text = ""; var text = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"; var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
for (var i = 0; i < 7; i++){ for (var i = 0; i < 7; i++){
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
} }
return text; return text;
}; };
var roomID = "undefined"; var roomID = "undefined";
if (urlParams.has("room")){ if (urlParams.has("room")){
roomID = urlParams.get("room"); roomID = urlParams.get("room");
} else { } else {
roomID = generateStreamID(); roomID = generateStreamID();
updateURL("room="+roomID); updateURL("room="+roomID);
} }
var url = document.URL.substr(0,document.URL.lastIndexOf('/')); var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => { navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
/* clipboard successfully set */ /* clipboard successfully set */
}, () => { }, () => {
/* clipboard write failed */ /* clipboard write failed */
}); });
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID; document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
var socket = new WebSocket("wss://api.action.wtf:666"); var socket = new WebSocket("wss://api.action.wtf:666");
socket.onclose = function (){ socket.onclose = function (){
setTimeout(function(){window.location.reload(true);},100); setTimeout(function(){window.location.reload(true);},100);
}; };
socket.onopen = function (){ socket.onopen = function (){
socket.send(JSON.stringify({"join":roomID})); socket.send(JSON.stringify({"join":roomID}));
} }
socket.addEventListener('message', function (event) { socket.addEventListener('message', function (event) {
if (event.data){ if (event.data){
var data = JSON.parse(event.data); var data = JSON.parse(event.data);
log(data); log(data);
} }
}); });
socket.onclose = function (){ socket.onclose = function (){
setTimeout(function(){window.location.reload(true);},100); setTimeout(function(){window.location.reload(true);},100);
}; };
var counter=0; var counter=0;
function send(scene){ function send(scene){
counter+=1; counter+=1;
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter})); socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,285 +1,285 @@
<head> <head>
<link rel="stylesheet" href="./main.css?ver=40" /> <link rel="stylesheet" href="main.css?ver=40" />
<style> <style>
.container { .container {
max-width: min(80%,875px); max-width: min(80%,875px);
width: fit-content; width: fit-content;
margin: 0 auto; margin: 0 auto;
} }
h1 { h1 {
margin-top: 1em; margin-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
padding: 10px; padding: 10px;
} }
a { a {
color: #00538c; color: #00538c;
} }
a:link { a:link {
color: #00538c; color: #00538c;
} }
a:visited { a:visited {
color: #00538c; color: #00538c;
} }
.card { .card {
margin: 10px; margin: 10px;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%); box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
background-color: #ddd; background-color: #ddd;
color: black; color: black;
margin-bottom: 1.5em; margin-bottom: 1.5em;
} }
.card>div { .card>div {
padding: 10px; padding: 10px;
} }
.card h2 { .card h2 {
font-size: 1.5em; font-size: 1.5em;
padding: 10px; padding: 10px;
background-color: #457b9d; background-color: #457b9d;
color: white; color: white;
border-bottom: 2px solid #3b6a87; border-bottom: 2px solid #3b6a87;
} }
small { small {
font-style: italic; font-style: italic;
display: block; display: block;
margin-left: 1em; margin-left: 1em;
} }
span.warning { span.warning {
color: rgb(212, 191, 0); color: rgb(212, 191, 0);
} }
input { input {
padding: 10px; padding: 10px;
} }
video { video {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
audio { audio {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
div#processing { div#processing {
display: none; display: none;
justify-content: center; justify-content: center;
place-items: center; place-items: center;
position: absolute; position: absolute;
inset: 0; inset: 0;
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
background: #141926; background: #141926;
flex-direction: column; flex-direction: column;
} }
</style> </style>
</head> </head>
<body style='color:white'> <body style='color:white'>
<div id="header"> <div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px"> <a id="logoname" href="../.." style="text-decoration: none; color: white; margin: 2px">
<span data-translate="logo-header"> <span data-translate="logo-header">
<font id="qos">V</font>DO.Ninja <font id="qos">V</font>DO.Ninja
</span> </span>
</a> </a>
</div> </div>
<div class="container"> <div class="container">
<div id="info"> <div id="info">
<h1>Web-based Media Conversion Tools</h1> <h1>Web-based Media Conversion Tools</h1>
<div class="card"> <div class="card">
<h2>WebM (or MKV/FLV) to MP4 (fixed 1280x720 resolution) <span class='warning'>(very slow!)</span></h2> <h2>WebM (or MKV/FLV) to MP4 (fixed 1280x720 resolution) <span class='warning'>(very slow!)</span></h2>
<div> <div>
<small>The same as: <i>ffmpeg -i input.webm -vf scale="1280:720" output.mp4</i></small> <small>The same as: <i>ffmpeg -i input.webm -vf scale="1280:720" output.mp4</i></small>
<input type="file" accept=".mkv, .flv, .webm" id="uploader" title="Convert WebM to MP4"> <input type="file" accept=".mkv, .flv, .webm" id="uploader" title="Convert WebM to MP4">
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>WebM to MP4 files (no transcoding, *attempts* to force high resolutions)</h2> <h2>WebM to MP4 files (no transcoding, *attempts* to force high resolutions)</h2>
<div> <div>
<small>The same as: <i>ffmpeg.exe -i concat:"<a href='cap.webm' target="_blank">cap.webm</a>|input.webm" -safe 0 -c copy -avoid_negative_ts 1 -strict experimental output.mp4</i></small> <small>The same as: <i>ffmpeg.exe -i concat:"<a href='cap.webm' target="_blank">cap.webm</a>|input.webm" -safe 0 -c copy -avoid_negative_ts 1 -strict experimental output.mp4</i></small>
<input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4"> <input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>WebM to Audio-only files (opus or wav)</h2> <h2>WebM to Audio-only files (opus or wav)</h2>
<div> <div>
<small>The same as: <i>ffmpeg -i input.webm -vn -acodec copy output.wav</i></small> <small>The same as: <i>ffmpeg -i input.webm -vn -acodec copy output.wav</i></small>
<input type="file" id="uploader4" accept=".webm" title="Convert WebM to OPUS (or WAV)"> <input type="file" id="uploader4" accept=".webm" title="Convert WebM to OPUS (or WAV)">
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>MKV (or FLV/WebM) to MP4 (no transcoding)</h2> <h2>MKV (or FLV/WebM) to MP4 (no transcoding)</h2>
<div> <div>
<small>The same as: <i>ffmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</i></small> <small>The same as: <i>ffmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</i></small>
<input type="file" id="uploader2" accept=".mkv, .flv, .webm" title="Convert MKV (or FLV) to MP4"> <input type="file" id="uploader2" accept=".mkv, .flv, .webm" title="Convert MKV (or FLV) to MP4">
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Having problems?</h2> <h2>Having problems?</h2>
<div> <div>
For larger files, over 2-gigabytes in size, the browser may not be able to properly process the video in memory. For larger files, over 2-gigabytes in size, the browser may not be able to properly process the video in memory.
</div> </div>
<div> <div>
Please consider using FFmpeg <a href='https://ffmpeg.org/download.html' target="_blank">[get it free here]</a> to run these processes from the command-line instead. The corresponding commands are provided above, where you need to replace input.webm with your own file. Please consider using FFmpeg <a href='https://ffmpeg.org/download.html' target="_blank">[get it free here]</a> to run these processes from the command-line instead. The corresponding commands are provided above, where you need to replace input.webm with your own file.
</div> </div>
<div> <div>
Other users who find FFmpeg too challenging have had luck using the graphical <a href="https://handbrake.fr/" target="_blank">Handbrake</a> application instead. Other users who find FFmpeg too challenging have had luck using the graphical <a href="https://handbrake.fr/" target="_blank">Handbrake</a> application instead.
</div> </div>
</div> </div>
<div id="processing"> <div id="processing">
<span id="message"></span> <span id="message"></span>
<video id="player" controls style="display:none"></video> <video id="player" controls style="display:none"></video>
<audio id="player2" controls style="display:none"></audio> <audio id="player2" controls style="display:none"></audio>
</div> </div>
</div> </div>
<script> <script>
if (window.location.hostname.indexOf("vdo.ninja") == 0) { if (window.location.hostname.indexOf("vdo.ninja") == 0) {
window.location = window.location.href.replace("vdo.ninja","isolated.vdo.ninja"); // FFMPEG requires an isolated domain. window.location = window.location.href.replace("vdo.ninja","isolated.vdo.ninja"); // FFMPEG requires an isolated domain.
} }
</script> </script>
<script src="./thirdparty/ffmpeg.min.js"></script> <script src="thirdpartyfmpeg.min.js"></script>
<script> <script>
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) { window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
console.error(errorMsg); console.error(errorMsg);
alert("An error occured.\n\nIf your file is larger than 2-GB, you may need to run the FFmpeg commands locally or use Handbrake instead."); alert("An error occured.\n\nIf your file is larger than 2-GB, you may need to run the FFmpeg commands locally or use Handbrake instead.");
return false; return false;
}; };
function download(data, filename) { function download(data, filename) {
const blob = new Blob([data.buffer]); const blob = new Blob([data.buffer]);
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.style.display = 'none'; a.style.display = 'none';
a.href = url; a.href = url;
a.download = filename; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
setTimeout(() => { setTimeout(() => {
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 100); }, 100);
} }
const { createFFmpeg, fetchFile } = FFmpeg; const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true }); const ffmpeg = createFFmpeg({ log: true });
const transcode = async ({ target: { files } }) => { const transcode = async ({ target: { files } }) => {
const { name } = files[0]; const { name } = files[0];
console.log(files[0]); console.log(files[0]);
if (files[0].size>2147483648){ if (files[0].size>2147483648){
alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. "); alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. ");
return; return;
} }
document.getElementById('uploader').style.display = "none"; document.getElementById('uploader').style.display = "none";
document.getElementById('uploader2').style.display = "none"; document.getElementById('uploader2').style.display = "none";
document.getElementById('uploader3').style.display = "none"; document.getElementById('uploader3').style.display = "none";
document.getElementById('message').innerText = "Transcoding file... this will take a while"; document.getElementById('message').innerText = "Transcoding file... this will take a while";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
await ffmpeg.load(); await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0])); ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
await ffmpeg.run('-i', name, '-vf', 'scale=1280:720', 'output.mp4'); await ffmpeg.run('-i', name, '-vf', 'scale=1280:720', 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4'); const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player'); const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
video.style.display = "block"; video.style.display = "block";
document.getElementById('message').innerText = "Operation Done. Play video or download it."; document.getElementById('message').innerText = "Operation Done. Play video or download it.";
} }
const transmux = async ({ target: { files } }) => { const transmux = async ({ target: { files } }) => {
const { name } = files[0]; const { name } = files[0];
if (files[0].size>2147483648){ if (files[0].size>2147483648){
alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. "); alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. ");
return; return;
} }
document.getElementById('uploader').style.display = "none"; document.getElementById('uploader').style.display = "none";
document.getElementById('uploader2').style.display = "none"; document.getElementById('uploader2').style.display = "none";
document.getElementById('uploader3').style.display = "none"; document.getElementById('uploader3').style.display = "none";
document.getElementById('message').innerText = "Transcoding file... this will take a while"; document.getElementById('message').innerText = "Transcoding file... this will take a while";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
await ffmpeg.load(); await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0])); ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
await ffmpeg.run('-i', name, '-vcodec', 'copy', '-acodec', 'copy', 'output.mp4'); await ffmpeg.run('-i', name, '-vcodec', 'copy', '-acodec', 'copy', 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4'); const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player'); const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
video.style.display = "block"; video.style.display = "block";
document.getElementById('message').innerText = "Operation Done. Play video or download it."; document.getElementById('message').innerText = "Operation Done. Play video or download it.";
} }
const force1080 = async ({ target: { files } }) => { const force1080 = async ({ target: { files } }) => {
const { name } = files[0]; const { name } = files[0];
if (files[0].size>2147483648){ if (files[0].size>2147483648){
alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. "); alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. ");
return; return;
} }
const sourceBuffer = await fetch("./media/cap.webm").then(r => r.arrayBuffer()); const sourceBuffer = await fetch("./media/cap.webm").then(r => r.arrayBuffer());
document.getElementById('uploader').style.display = "none"; document.getElementById('uploader').style.display = "none";
document.getElementById('uploader2').style.display = "none"; document.getElementById('uploader2').style.display = "none";
document.getElementById('uploader3').style.display = "none"; document.getElementById('uploader3').style.display = "none";
document.getElementById('message').innerText = "Tweaking file... this will take a moment"; document.getElementById('message').innerText = "Tweaking file... this will take a moment";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
await ffmpeg.load(); await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0])); ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
ffmpeg.FS("writeFile", "cap.webm", new Uint8Array(sourceBuffer, 0, sourceBuffer.byteLength)); ffmpeg.FS("writeFile", "cap.webm", new Uint8Array(sourceBuffer, 0, sourceBuffer.byteLength));
await ffmpeg.run("-i", "concat:cap.webm|" + name, "-safe", "0", "-c", "copy", "-avoid_negative_ts", "1", "-strict", "experimental", "output.mp4"); await ffmpeg.run("-i", "concat:cap.webm|" + name, "-safe", "0", "-c", "copy", "-avoid_negative_ts", "1", "-strict", "experimental", "output.mp4");
const data = ffmpeg.FS('readFile', 'output.mp4'); const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player'); const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
video.style.display = "block"; video.style.display = "block";
document.getElementById('message').innerText = "Operation Done. Play video or download it."; document.getElementById('message').innerText = "Operation Done. Play video or download it.";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
} }
const convertToAudioOnly = async ({ target: { files } }) => { const convertToAudioOnly = async ({ target: { files } }) => {
const { name } = files[0]; const { name } = files[0];
if (files[0].size>2147483648){ if (files[0].size>2147483648){
alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. "); alert("Warning: The largest file size currently supported is 2-GB.\n\nFor larger files, please instead consider using the FFmpeg commands locally or use Handbrake. ");
return; return;
} }
document.getElementById('message').innerText = "Transcoding file... this will take a while"; document.getElementById('message').innerText = "Transcoding file... this will take a while";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
await ffmpeg.load(); await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0])); ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
const video = document.getElementById('player'); const video = document.getElementById('player');
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.opus'); await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.opus');
const data = ffmpeg.FS('readFile', 'output.opus'); const data = ffmpeg.FS('readFile', 'output.opus');
console.log(data.buffer.byteLength); console.log(data.buffer.byteLength);
if (data.buffer.byteLength) { if (data.buffer.byteLength) {
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/opus' })); video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/opus' }));
download(data, name.split(".")[0] + ".opus"); download(data, name.split(".")[0] + ".opus");
} else { } else {
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.wav'); await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.wav');
const data2 = ffmpeg.FS('readFile', 'output.wav'); const data2 = ffmpeg.FS('readFile', 'output.wav');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/pcm' })); video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/pcm' }));
download(data2, name.split(".")[0] + ".wav"); download(data2, name.split(".")[0] + ".wav");
} }
video.style.display = "block"; video.style.display = "block";
document.getElementById('message').innerText = "Operation Done. Play audio or download it."; document.getElementById('message').innerText = "Operation Done. Play audio or download it.";
document.getElementById('processing').style.display = 'flex'; document.getElementById('processing').style.display = 'flex';
} }
document.getElementById('uploader').addEventListener('change', transcode); document.getElementById('uploader').addEventListener('change', transcode);
document.getElementById('uploader2').addEventListener('change', transmux); document.getElementById('uploader2').addEventListener('change', transmux);
document.getElementById('uploader3').addEventListener('change', force1080); document.getElementById('uploader3').addEventListener('change', force1080);
document.getElementById('uploader4').addEventListener('change', convertToAudioOnly); document.getElementById('uploader4').addEventListener('change', convertToAudioOnly);
</script> </script>
</body> </body>

View File

@ -1,227 +1,227 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" /> <link rel="stylesheet" href="lineawesome/css/line-awesome.min.css" />
<link rel="stylesheet" href="./main.css?ver=11" /> <link rel="stylesheet" href="main.css?ver=11" />
<link rel="stylesheet" href="./devices.css?ver=1" /> <link rel="stylesheet" href="devices.css?ver=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf8" /> <meta charset="utf8" />
</head> </head>
<body> <body>
<div id="header"> <div id="header">
<a <a
id="logoname" id="logoname"
href="./" href="../.."
style="text-decoration: none; color: white; margin: 2px" style="text-decoration: none; color: white; margin: 2px"
> >
<span data-translate="logo-header"> <span data-translate="logo-header">
<font id="qos">V</font>DO.Ninja <font id="qos">V</font>DO.Ninja
</span> </span>
</a> </a>
</div> </div>
<div id="devices"> <div id="devices">
<div class="notice"> <div class="notice">
Device IDs are bound to a combination of domain and browser. <br />If Device IDs are bound to a combination of domain and browser. <br />If
you want to use electron-capture, open this URL on the electron-capture you want to use electron-capture, open this URL on the electron-capture
app. <br/> app. <br/>
Click device names to preset them. Select multiple audio inputs by clicking multiple devices. Click device names to preset them. Select multiple audio inputs by clicking multiple devices.
</div> </div>
<div class="notice"> <div class="notice">
Check for browser and camera capabilities <a href="/supports">here</a>. Check for browser and camera capabilities <a href="/supports">here</a>.
</div> </div>
<div class="card"> <div class="card">
<h1>🎤 Audio Inputs</h1> <h1>🎤 Audio Inputs</h1>
<div id="audioInputs"></div> <div id="audioInputs"></div>
</div> </div>
<div class="card"> <div class="card">
<h1>📹 Video Inputs</h1> <h1>📹 Video Inputs</h1>
<div id="videoInputs"></div> <div id="videoInputs"></div>
</div> </div>
<div class="card"> <div class="card">
<h1>🔉 Audio Outputs</h1> <h1>🔉 Audio Outputs</h1>
<div id="audioOutputs"></div> <div id="audioOutputs"></div>
</div> </div>
</div> </div>
<div id="sharedDevices" style="display: none"> <div id="sharedDevices" style="display: none">
<span>Click to copy. Use this URL to preset audio/video devices.</span> <span>Click to copy. Use this URL to preset audio/video devices.</span>
<span id="close" onclick="this.parentNode.style.display='none'">×</span> <span id="close" onclick="this.parentNode.style.display='none'">×</span>
<input id="devicesUrl" value="" /> <input id="devicesUrl" value="" />
</div> </div>
<script> <script>
const list = []; const list = [];
const url = new URL(document.location.origin); const url = new URL(document.location.origin);
const audioInputDevices = []; const audioInputDevices = [];
function isAudioInput(value) { function isAudioInput(value) {
return value.kind === "audioinput"; return value.kind === "audioinput";
} }
function isAudioOutput(value) { function isAudioOutput(value) {
return value.kind === "audiooutput"; return value.kind === "audiooutput";
} }
function isVideoInput(value) { function isVideoInput(value) {
return value.kind === "videoinput"; return value.kind === "videoinput";
} }
function sanitizeDeviceName(deviceName) { function sanitizeDeviceName(deviceName) {
return String(deviceName).toLowerCase().replace(/[\W]+/g, "_"); return String(deviceName).toLowerCase().replace(/[\W]+/g, "_");
} }
function addDevice(element) { function addDevice(element) {
const type = element.dataset.deviceType; const type = element.dataset.deviceType;
const device = sanitizeDeviceName(element.querySelector('span').innerText); const device = sanitizeDeviceName(element.querySelector('span').innerText);
if (type === "audioinput") { if (type === "audioinput") {
setAudioSearchParams(element); setAudioSearchParams(element);
} }
if (type === "videoinput") { if (type === "videoinput") {
setVideoSearchParams(element); setVideoSearchParams(element);
} }
if (type === "audiooutput") { if (type === "audiooutput") {
setAudioOutputSearchParams(element); setAudioOutputSearchParams(element);
} }
/* /*
Allows for multiple audio devices to be selected Allows for multiple audio devices to be selected
Will be output as a comma separated string to &ad Will be output as a comma separated string to &ad
*/ */
function setAudioSearchParams(info) { function setAudioSearchParams(info) {
// Device was already selected // Device was already selected
if (info.className === "device selected") { if (info.className === "device selected") {
// Remove device from list of selected devices // Remove device from list of selected devices
const index = audioInputDevices.indexOf(device); const index = audioInputDevices.indexOf(device);
if (index !== -1) { if (index !== -1) {
audioInputDevices.splice(index, 1); audioInputDevices.splice(index, 1);
} }
// Set the url param to the devices that are left // Set the url param to the devices that are left
url.searchParams.set("ad", audioInputDevices.join(",")); url.searchParams.set("ad", audioInputDevices.join(","));
element.className = "device"; element.className = "device";
// If no audio devices remained, just remove the param completely // If no audio devices remained, just remove the param completely
if (audioInputDevices.length === 0) { if (audioInputDevices.length === 0) {
url.searchParams.delete("ad"); url.searchParams.delete("ad");
} }
} else { } else {
// Device is unselected // Device is unselected
audioInputDevices.push(device); audioInputDevices.push(device);
url.searchParams.set("ad", audioInputDevices.join(",")); url.searchParams.set("ad", audioInputDevices.join(","));
element.className = "device selected"; element.className = "device selected";
} }
} }
/* /*
Only allows for a single video device to be selected Only allows for a single video device to be selected
*/ */
function setVideoSearchParams(info) { function setVideoSearchParams(info) {
// Device was already selected // Device was already selected
if (info.className === "device selected") { if (info.className === "device selected") {
element.className = "device"; element.className = "device";
// Set the url param to the devices that are left // Set the url param to the devices that are left
url.searchParams.set("vd", device); url.searchParams.set("vd", device);
element.className = "device"; element.className = "device";
// If no devices remained, just remove the param completely // If no devices remained, just remove the param completely
if (audioInputDevices.length === 0) { if (audioInputDevices.length === 0) {
url.searchParams.delete("vd"); url.searchParams.delete("vd");
} }
} else { } else {
// Device is unselected // Device is unselected
try { try {
element.parentElement.querySelector('.device.selected').className = "device"; element.parentElement.querySelector('.device.selected').className = "device";
} catch (error) { } catch (error) {
console.log("There was no video device already selected."); console.log("There was no video device already selected.");
} }
url.searchParams.set("vd", device); url.searchParams.set("vd", device);
element.className = "device selected"; element.className = "device selected";
} }
} }
/* /*
Only allows for a single audio output device to be selected Only allows for a single audio output device to be selected
*/ */
function setAudioOutputSearchParams(info) { function setAudioOutputSearchParams(info) {
// Device was already selected // Device was already selected
if (info.className === "device selected") { if (info.className === "device selected") {
element.className = "device"; element.className = "device";
// Set the url param to the devices that are left // Set the url param to the devices that are left
url.searchParams.set("od", device); url.searchParams.set("od", device);
element.className = "device"; element.className = "device";
// If no devices remained, just remove the param completely // If no devices remained, just remove the param completely
if (audioInputDevices.length === 0) { if (audioInputDevices.length === 0) {
url.searchParams.delete("od"); url.searchParams.delete("od");
} }
} else { } else {
// Device is unselected // Device is unselected
try { try {
element.parentElement.querySelector('.device.selected').className = "device"; element.parentElement.querySelector('.device.selected').className = "device";
} catch (error) { } catch (error) {
console.log("There was no video device already selected."); console.log("There was no video device already selected.");
} }
url.searchParams.set("od", device); url.searchParams.set("od", device);
element.className = "device selected"; element.className = "device selected";
} }
} }
// Update UI // Update UI
showDeviceIdsPopup(); showDeviceIdsPopup();
} }
function showDeviceIdsPopup() { function showDeviceIdsPopup() {
document.getElementById("devicesUrl").value = decodeURIComponent(url); document.getElementById("devicesUrl").value = decodeURIComponent(url);
document.getElementById("sharedDevices").style.display = "block"; document.getElementById("sharedDevices").style.display = "block";
} }
function prettyPrint(json, element) { function prettyPrint(json, element) {
let output = "<div class='prettyJson two-col'>"; let output = "<div class='prettyJson two-col'>";
let nestedObjs; let nestedObjs;
Object.entries(json) Object.entries(json)
.sort() .sort()
.forEach(([key, value]) => { .forEach(([key, value]) => {
output += ` output += `
<div class='device' onclick='addDevice(this)' data-device-type='${value.kind}'> <div class='device' onclick='addDevice(this)' data-device-type='${value.kind}'>
<span class='device-name'>${value.label}</span> <span class='device-name'>${value.label}</span>
<span class='device-id'> <span class='device-id'>
${value.deviceId} ${value.deviceId}
</span> </span>
</div>`; </div>`;
}); });
output += "</div>"; output += "</div>";
document.getElementById(element).innerHTML = output; document.getElementById(element).innerHTML = output;
} }
document.getElementById("devicesUrl").onclick = (e) => { document.getElementById("devicesUrl").onclick = (e) => {
e.target.select(); e.target.select();
document.execCommand("copy"); document.execCommand("copy");
}; };
navigator.mediaDevices navigator.mediaDevices
.enumerateDevices() .enumerateDevices()
.then((devices) => { .then((devices) => {
devices.forEach((device) => { devices.forEach((device) => {
console.log( console.log(
`${device.kind}: ${device.label} id = ${device.deviceId}` `${device.kind}: ${device.label} id = ${device.deviceId}`
); );
list.push(device); list.push(device);
}); });
prettyPrint(devices.filter(isAudioInput), "audioInputs"); prettyPrint(devices.filter(isAudioInput), "audioInputs");
prettyPrint(devices.filter(isAudioOutput), "audioOutputs"); prettyPrint(devices.filter(isAudioOutput), "audioOutputs");
prettyPrint(devices.filter(isVideoInput), "videoInputs"); prettyPrint(devices.filter(isVideoInput), "videoInputs");
}) })
.catch((err) => { .catch((err) => {
console.log(`${err.name}: ${err.message}`); console.log(`${err.name}: ${err.message}`);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,24 +1,24 @@
<html> <html>
<head><meta charset="UTF-8"></head> <head><meta charset="UTF-8"></head>
<body> <body>
<script> <script>
var list = []; var list = [];
navigator.mediaDevices.enumerateDevices() navigator.mediaDevices.enumerateDevices()
.then(function(devices) { .then(function(devices) {
devices.forEach(function(device) { devices.forEach(function(device) {
console.log(device.kind + ": " + device.label + console.log(device.kind + ": " + device.label +
" id = " + device.deviceId); " id = " + device.deviceId);
list.push(device); list.push(device);
}); });
document.write(JSON.stringify(list, null, 2)); document.write(JSON.stringify(list, null, 2));
}) })
.catch(function(err) { .catch(function(err) {
console.log(err.name + ": " + err.message); console.log(err.name + ": " + err.message);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,298 +1,298 @@
<html> <html>
<head> <head>
<style> <style>
body { body {
transform: scale(0.7); transform: scale(0.7);
transform-origin: 0 0; transform-origin: 0 0;
margin:2px; margin:2px;
padding:0; padding:0;
border:0; border:0;
color: #FFF; color: #FFF;
background-color: #1F1E1F; background-color: #1F1E1F;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
width:300px; width:300px;
overflow:hidden; overflow:hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
min-width:1px; min-width:1px;
} }
#container-links { #container-links {
z-index:10; z-index:10;
width:100%; width:100%;
height:100%; height:100%;
display:none; display:none;
} }
#container-setup { #container-setup {
width:100%; width:100%;
height:100%; height:100%;
display:block; display:block;
} }
.red { .red {
background-color:#FCC; background-color:#FCC;
} }
.green { .green {
background-color:#CFC; background-color:#CFC;
} }
.task { .task {
cursor:grab; cursor:grab;
width:100%; width:100%;
padding:5px; padding:5px;
border:2px solid black; border:2px solid black;
margin:0; margin:0;
} }
button{ button{
padding:5px; padding:5px;
transform: scale(1.4); transform: scale(1.4);
transform-origin: 0 0; transform-origin: 0 0;
} }
.gone { .gone {
position: absolute; position: absolute;
display:inline-block; display:inline-block;
left: -9999px; left: -9999px;
} }
</style> </style>
</head> </head>
<body> <body>
<script> <script>
function getById(id) { function getById(id) {
var el = document.getElementById(id); var el = document.getElementById(id);
if (!el) { if (!el) {
console.warn(id + " is not defined; skipping."); console.warn(id + " is not defined; skipping.");
el = document.createElement("span"); // create a fake element el = document.createElement("span"); // create a fake element
} }
return el; return el;
} }
function copyFunction(copyText) { function copyFunction(copyText) {
copyText.select(); copyText.select();
copyText.setSelectionRange(0, 99999); copyText.setSelectionRange(0, 99999);
document.execCommand("copy"); document.execCommand("copy");
} }
function generateStreamID(){ function generateStreamID(){
var text = ""; var text = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"; var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
for (var i = 0; i < 7; i++){ for (var i = 0; i < 7; i++){
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
} }
console.log(text); console.log(text);
return text; return text;
}; };
function toHexString(byteArray){ function toHexString(byteArray){
return Array.prototype.map.call(byteArray, function(byte){ return Array.prototype.map.call(byteArray, function(byte){
return ('0' + (byte & 0xFF).toString(16)).slice(-2); return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join(''); }).join('');
} }
generateHash = function (str, length=false){ generateHash = function (str, length=false){
var buffer = new TextEncoder("utf-8").encode(str); var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then( return crypto.subtle.digest("SHA-256", buffer).then(
function (hash) { function (hash) {
hash = new Uint8Array(hash); hash = new Uint8Array(hash);
if (length){ if (length){
hash = hash.slice(0, parseInt(parseInt(length)/2)); hash = hash.slice(0, parseInt(parseInt(length)/2));
} }
hash = toHexString(hash); hash = toHexString(hash);
return hash; return hash;
} }
); );
}; };
function generateInvite(){ function generateInvite(){
var title = encodeURI(getById("videoname").value.replace(/[\W]+/g,"_")); var title = encodeURI(getById("videoname").value.replace(/[\W]+/g,"_"));
if (title.length){ if (title.length){
title = "&label="+title; title = "&label="+title;
} }
var sid = generateStreamID(); var sid = generateStreamID();
var viewstr = ""; var viewstr = "";
var sendstr = ""; var sendstr = "";
if (getById("invite_bitrate").checked){ if (getById("invite_bitrate").checked){
viewstr+="&bitrate=20000"; viewstr+="&bitrate=20000";
} }
if (getById("invite_vp9").checked){ if (getById("invite_vp9").checked){
viewstr+="&codec=vp9"; viewstr+="&codec=vp9";
} }
if (getById("invite_h264").checked){ if (getById("invite_h264").checked){
viewstr+="&codec=h264"; viewstr+="&codec=h264";
} }
if (getById("invite_stereo").checked){ if (getById("invite_stereo").checked){
viewstr+="&stereo"; viewstr+="&stereo";
sendstr+="&stereo"; sendstr+="&stereo";
} }
//if (getById("invite_secure").checked){ //if (getById("invite_secure").checked){
// sendstr+="&secure"; // sendstr+="&secure";
//} //}
if (getById("invite_hidescreen").checked){ if (getById("invite_hidescreen").checked){
sendstr+="&webcam"; sendstr+="&webcam";
} }
//if (getById("invite_remotecontrol").checked){ // //if (getById("invite_remotecontrol").checked){ //
// var remote_gen_id = generateStreamID(); // var remote_gen_id = generateStreamID();
// sendstr+="&remote="+remote_gen_id; // security // sendstr+="&remote="+remote_gen_id; // security
// viewstr+="&remote="+remote_gen_id; // viewstr+="&remote="+remote_gen_id;
//} //}
if (getById("invite_joinroom").value.trim().length){ if (getById("invite_joinroom").value.trim().length){
sendstr+="&room="+getById("invite_joinroom").value.replace(/[\W]+/g,"_"); sendstr+="&room="+getById("invite_joinroom").value.replace(/[\W]+/g,"_");
viewstr+="&scene&room="+getById("invite_joinroom").value.replace(/[\W]+/g,"_"); viewstr+="&scene&room="+getById("invite_joinroom").value.replace(/[\W]+/g,"_");
} }
if (getById("invite_group_chat_type").value){ // 0 is default if (getById("invite_group_chat_type").value){ // 0 is default
if (getById("invite_group_chat_type").value==1){ // no video if (getById("invite_group_chat_type").value==1){ // no video
sendstr+="&novideo"; sendstr+="&novideo";
} else if (getById("invite_group_chat_type").value==2){ // no view or audio } else if (getById("invite_group_chat_type").value==2){ // no view or audio
sendstr+="&view"; sendstr+="&view";
} }
} }
if (getById("invite_quality").value){ if (getById("invite_quality").value){
if (getById("invite_quality").value==0){ if (getById("invite_quality").value==0){
sendstr+="&quality=0"; sendstr+="&quality=0";
} else if (getById("invite_quality").value==1){ } else if (getById("invite_quality").value==1){
sendstr+="&quality=1"; sendstr+="&quality=1";
} else if (getById("invite_quality").value==2){ } else if (getById("invite_quality").value==2){
sendstr+="&quality=2"; sendstr+="&quality=2";
} }
} }
var href = window.location.href; var href = window.location.href;
var dir = href.substring(0, href.lastIndexOf('/')) + "/"; var dir = href.substring(0, href.lastIndexOf('/')) + "/";
var salt = location.hostname; // "vdo.ninja" is the expected default. You will want to change this if hosting dock.html locally. var salt = location.hostname; // "vdo.ninja" is the expected default. You will want to change this if hosting dock.html locally.
if (getById("invite_password").value.trim().length){ if (getById("invite_password").value.trim().length){
generateHash(getById("invite_password").value.trim().replace(/[\W]+/g,"_")+salt,4).then(function(hash){ generateHash(getById("invite_password").value.trim().replace(/[\W]+/g,"_")+salt,4).then(function(hash){
sendstr+="&hash="+hash; sendstr+="&hash="+hash;
viewstr+="&password="+getById("invite_password").value.trim(); viewstr+="&password="+getById("invite_password").value.trim();
sendstr = dir+'?push=' + sid + sendstr; sendstr = dir+'?push=' + sid + sendstr;
viewstr = dir+'?view=' + sid + viewstr + title; viewstr = dir+'?view=' + sid + viewstr + title;
getById("container-setup").style.display="none"; getById("container-setup").style.display="none";
getById("container-links").style.display="block"; getById("container-links").style.display="block";
getById("guest-link").value = sendstr; getById("guest-link").value = sendstr;
getById("obs-link").value = viewstr; getById("obs-link").value = viewstr;
}); });
} else { } else {
sendstr = dir+'?push=' + sid + sendstr; sendstr = dir+'?push=' + sid + sendstr;
viewstr = dir+'?view=' + sid + viewstr + title; viewstr = dir+'?view=' + sid + viewstr + title;
getById("container-setup").style.display="none"; getById("container-setup").style.display="none";
getById("container-links").style.display="block"; getById("container-links").style.display="block";
getById("guest-link").value = sendstr; getById("guest-link").value = sendstr;
getById("obs-link").value = viewstr; getById("obs-link").value = viewstr;
} }
} }
function goBack(){ function goBack(){
getById("container-setup").style.display="block"; getById("container-setup").style.display="block";
getById("container-links").style.display="none"; getById("container-links").style.display="none";
} }
document.addEventListener("dragstart", event => { document.addEventListener("dragstart", event => {
var url = event.target.href || event.target.value; var url = event.target.href || event.target.value;
if (!url || !url.startsWith('https://')) return; if (!url || !url.startsWith('https://')) return;
if (event.target.dataset.drag!="1"){ if (event.target.dataset.drag!="1"){
return; return;
} }
//event.target.ondragend = function(){event.target.blur();} //event.target.ondragend = function(){event.target.blur();}
var streamId = url.split('view='); var streamId = url.split('view=');
var label = url.split('label='); var label = url.split('label=');
url += '&layer-name=OBSN'; url += '&layer-name=OBSN';
if (streamId.length>1) url += ': ' + streamId[1].split('&')[0]; if (streamId.length>1) url += ': ' + streamId[1].split('&')[0];
if (label.length>1) url += ' - ' + decodeURI(label[1].split('&')[0]); if (label.length>1) url += ' - ' + decodeURI(label[1].split('&')[0]);
url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
url += '&layer-height=1080'; url += '&layer-height=1080';
event.dataTransfer.setDragImage(document.querySelector('#dragImage'), 24, 24); event.dataTransfer.setDragImage(document.querySelector('#dragImage'), 24, 24);
event.dataTransfer.setData("text/uri-list", encodeURI(url)); event.dataTransfer.setData("text/uri-list", encodeURI(url));
//event.dataTransfer.setData("url", encodeURI(url)); //event.dataTransfer.setData("url", encodeURI(url));
//warnlog(event); //warnlog(event);
}); });
</script> </script>
<div id="container-setup" > <div id="container-setup" >
<button style="padding:5px;" onclick="generateInvite()" > <button style="padding:5px;" onclick="generateInvite()" >
<span data-translate="generate-invite-link">GENERATE THE INVITE LINK</span> <span data-translate="generate-invite-link">GENERATE THE INVITE LINK</span>
</button> </button>
<br /><br /> <br /><br />
<input type="checkbox" id="invite_bitrate" /><label for="invite_bitrate"> <span data-translate="unlock-video-bitrate">Unlock Video Bitrate (20mbps)</span></label> <input type="checkbox" id="invite_bitrate" /><label for="invite_bitrate"> <span data-translate="unlock-video-bitrate">Unlock Video Bitrate (20mbps)</span></label>
<br /> <br />
<input type="checkbox" id="invite_vp9" onclick="getById('invite_h264').checked=false;" /><label for="invite_vp9"> <span data-translate="force-vp9-video-codec">VP9 Video Codec</span></label> <input type="checkbox" id="invite_vp9" onclick="getById('invite_h264').checked=false;" /><label for="invite_vp9"> <span data-translate="force-vp9-video-codec">VP9 Video Codec</span></label>
<br /> <br />
<input type="checkbox" id="invite_h264" onclick="getById('invite_vp9').checked=false;" /><label for="invite_h264"> <span data-translate="force-h264-video-codec">H264 Video Codec</span></label> <input type="checkbox" id="invite_h264" onclick="getById('invite_vp9').checked=false;" /><label for="invite_h264"> <span data-translate="force-h264-video-codec">H264 Video Codec</span></label>
<br /> <br />
<input type="checkbox" id="invite_stereo" /><label for="invite_stereo"> <span data-translate="enable-stereo-and-pro">Stereo and Pro HD Audio</span></label> <input type="checkbox" id="invite_stereo" /><label for="invite_stereo"> <span data-translate="enable-stereo-and-pro">Stereo and Pro HD Audio</span></label>
<br /> <br />
<br /> <br />
<label for="invite_quality" data-translate="video-resolution">Video Resolution: </label> <label for="invite_quality" data-translate="video-resolution">Video Resolution: </label>
<select id="invite_quality" name="invite_quality"> <select id="invite_quality" name="invite_quality">
<option value="-1" selected>User Selectable</option> <option value="-1" selected>User Selectable</option>
<option value="0">Maximum Resolution</option> <option value="0">Maximum Resolution</option>
<option value="1">Balanced</option> <option value="1">Balanced</option>
<option value="2">Smooth and Cool</option> <option value="2">Smooth and Cool</option>
</select> </select>
<br /> <br />
<br /> <br />
<input type="checkbox" id="invite_hidescreen" /> <input type="checkbox" id="invite_hidescreen" />
<label for="invite_hidescreen"> <span data-translate="hide-screen-share">Hide Screenshare Option</span></label> <label for="invite_hidescreen"> <span data-translate="hide-screen-share">Hide Screenshare Option</span></label>
<br /> <br />
<br /> <br />
<label for="videoname">Stream Label:</label> <label for="videoname">Stream Label:</label>
<input id="videoname" placeholder="Give stream a description" /> <input id="videoname" placeholder="Give stream a description" />
<br /> <br />
<br /> <br />
<span data-translate="add-a-password-to-stream"> Add password:</span> <span data-translate="add-a-password-to-stream"> Add password:</span>
<input id="invite_password" placeholder="Add an optional password" /> <input id="invite_password" placeholder="Add an optional password" />
<br /><br /> <br /><br />
<span data-translate="add-the-guest-to-a-room"> Add to a room:</span> <span data-translate="add-the-guest-to-a-room"> Add to a room:</span>
<input id="invite_joinroom" placeholder="Enter Room name here" oninput="document.getElementById('invitegroupchat').style.display='block';" /> <input id="invite_joinroom" placeholder="Enter Room name here" oninput="document.getElementById('invitegroupchat').style.display='block';" />
<br /> <br />
<br /> <br />
<span id="invitegroupchat" style="display:none;"> <span id="invitegroupchat" style="display:none;">
<label for="invite_group_chat_type" data-translate="invite-group-chat-type">This room guest can:</label><br /> <label for="invite_group_chat_type" data-translate="invite-group-chat-type">This room guest can:</label><br />
<select id="invite_group_chat_type" name="invite_group_chat_type"> <select id="invite_group_chat_type" name="invite_group_chat_type">
<option value="0" selected data-translate="can-see-and-hear">Can see and hear the group chat</option> <option value="0" selected data-translate="can-see-and-hear">Can see and hear the group chat</option>
<option value="1" data-translate="can-hear-only">Can only hear the group chat</option> <option value="1" data-translate="can-hear-only">Can only hear the group chat</option>
<option value="2" data-translate="cant-see-or-hear">Cannot hear or see the group chat</option> <option value="2" data-translate="cant-see-or-hear">Cannot hear or see the group chat</option>
</select> </select>
</span> </span>
</div> </div>
<div id="container-links" > <div id="container-links" >
<button onclick="goBack()" > <button onclick="goBack()" >
<span >Go Back</span> <span >Go Back</span>
</button> </button>
<div id="container-links-inner" > <div id="container-links-inner" >
<br /><br /> <br /><br />
<h3>Guest Invite Link:</h3> <h3>Guest Invite Link:</h3>
<input id="guest-link" class="task green" onclick="copyFunction(this)" onmousedown="copyFunction(this)"/> <input id="guest-link" class="task green" onclick="copyFunction(this)" onmousedown="copyFunction(this)"/>
<br /><br /> <br /><br />
<h3>OBS Browser Source Link:</h3> <h3>OBS Browser Source Link:</h3>
<input id="obs-link" class="task red" data-drag="1" onmousedown="copyFunction(this)" onclick="copyFunction(this)" /> <input id="obs-link" class="task red" data-drag="1" onmousedown="copyFunction(this)" onclick="copyFunction(this)" />
<br /> <br />
<br /> <br />
<i>(links are draggable)</i> <i>(links are draggable)</i>
</div> </div>
</div> </div>
<div class="gone" > <div class="gone" >
<!-- This image is used when dragging elements --> <!-- This image is used when dragging elements -->
<img src="./media/favicon-32x32.png" id="dragImage" /> <img src="media/favicon-32x32.png" id="dragImage" />
</div> </div>
</body> </body>
</html> </html>

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" /> <link rel="stylesheet" href="lineawesome/css/line-awesome.min.css" />
<style> <style>
html { html {
border:0; border:0;
@ -403,7 +403,7 @@ if (location.hostname.toLowerCase() == "vdo.ninja"){
} else{ } else{
document.getElementById("version").innerHTML = "Elevate app privilleges to see current version"; document.getElementById("version").innerHTML = "Elevate app privilleges to see current version";
try{ try{
const ipcRenderer = require('electron').ipcRenderer; const ipcRenderer = require('app/static/electron').ipcRenderer;
console.log("ELECTRON DETECTED"); console.log("ELECTRON DETECTED");
ipcRenderer.on('appVersion', function(event, version) { ipcRenderer.on('appVersion', function(event, version) {
console.log("version: "+version); console.log("version: "+version);

View File

@ -1,131 +1,131 @@
<html> <html>
<head> <head>
<title>IFRAME Example</title> <title>IFRAME Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:90% height:90%
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
</style> </style>
<script> <script>
function loadIframe(){ function loadIframe(){
document.getElementById("container").innerHTML = ""; document.getElementById("container").innerHTML = "";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view="; var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
var listOfStreamIDs = [ var listOfStreamIDs = [
"1234_pov", "1234_pov",
"2345_pov", "2345_pov",
"3456_pov", "3456_pov",
"4567_pov", "4567_pov",
"5678_pov" "5678_pov"
]; ];
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "List connected StreamIDs"; button.innerHTML = "List connected StreamIDs";
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');}; button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "HIDE ALL"; button.innerHTML = "HIDE ALL";
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');}; button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
for (var i=0;i<listOfStreamIDs.length;i++){ for (var i=0;i<listOfStreamIDs.length;i++){
if (i!==0){ if (i!==0){
iframesrc+=","; iframesrc+=",";
} }
iframesrc+=listOfStreamIDs[i]; iframesrc+=listOfStreamIDs[i];
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "SHOW "+listOfStreamIDs[i]; button.innerHTML = "SHOW "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i]; button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*'); iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
iframe.contentWindow.postMessage({"target":this.dataset.sid, "add":true, "settings":{"style":{"width":"100%", "height":"100%", "display":"block"}}}, '*'); iframe.contentWindow.postMessage({"target":this.dataset.sid, "add":true, "settings":{"style":{"width":"100%", "height":"100%", "display":"block"}}}, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
} }
iframe.src = iframesrc; iframe.src = iframesrc;
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "event: "+e.data.action+"<br />"; outputWindow.innerHTML = "event: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "streamID list:<br />"; outputWindow.innerHTML = "streamID list:<br />";
for (var key in e.data.streamIDs) { for (var key in e.data.streamIDs) {
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n"; outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
} }
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
}); });
} }
</script> </script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<button onclick="loadIframe();">CONNECT</button> <button onclick="loadIframe();">CONNECT</button>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,145 +1,145 @@
<html> <html>
<head> <head>
<title>IFRAME Example</title> <title>IFRAME Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:90% height:90%
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
</style> </style>
<script> <script>
function loadIframe(){ function loadIframe(){
document.getElementById("container").innerHTML = ""; document.getElementById("container").innerHTML = "";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = "../?dir=teststeve123&password=1234"; iframe.src = "../?dir=teststeve123&password=1234";
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
var listOfStreamIDs = [ var listOfStreamIDs = [
"1234_pov", "1234_pov",
"2345_pov", "2345_pov",
"3456_pov", "3456_pov",
"4567_pov", "4567_pov",
"5678_pov" "5678_pov"
]; ];
for (var i=0;i<listOfStreamIDs.length;i++){ for (var i=0;i<listOfStreamIDs.length;i++){
var button = document.createElement("a"); var button = document.createElement("a");
button.innerHTML = "Invite "+listOfStreamIDs[i]; button.innerHTML = "Invite "+listOfStreamIDs[i];
button.target = "_blank"; button.target = "_blank";
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i]; button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "TOGGLE "+listOfStreamIDs[i]; button.innerHTML = "TOGGLE "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "addScene", action: "addScene",
value: "1", value: "1",
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "mic", action: "mic",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
} }
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "event: "+e.data.action+"<br />"; outputWindow.innerHTML = "event: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "streamID list:<br />"; outputWindow.innerHTML = "streamID list:<br />";
for (var key in e.data.streamIDs) { for (var key in e.data.streamIDs) {
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n"; outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
} }
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
}); });
} }
</script> </script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<button onclick="loadIframe();">Go to Directors Room</button> <button onclick="loadIframe();">Go to Directors Room</button>
<br /> <br />
The password for guests is 1234<br /> The password for guests is 1234<br />
<br /> <br />
<br /> <br />
Custom guest invites and toggles for add/removing from scene=1 are on the bottom. Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
<br /> <br />
<br /> <br />
Scene=1 link: <a target="_blank" href="https://vdo.ninja/?scene=1&room=teststeve123&password=1234">https://vdo.ninja/?scene=1&room=teststeve123&password=1234</a> Scene=1 link: <a target="_blank" href="https://vdo.ninja/?scene=1&room=teststeve123&password=1234">https://vdo.ninja/?scene=1&room=teststeve123&password=1234</a>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,121 +1,121 @@
<html> <html>
<head><title>Twitch + Video</title> <head><title>Twitch + Video</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
position:absolute; position:absolute;
display:block; display:block;
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
margin:10%; margin:10%;
} }
#startButton{ #startButton{
margin: 10px; margin: 10px;
padding: 20px padding: 20px
display: block; display: block;
border-radius: 50px; border-radius: 50px;
font-size:1.5em; font-size:1.5em;
} }
#toggleMute{ #toggleMute{
margin: 10px; margin: 10px;
padding: 30px 0; padding: 30px 0;
border-radius: 50px; border-radius: 50px;
font-size:1.5em; font-size:1.5em;
display: block; display: block;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width:200px; width:200px;
left: calc(50% - 100px); left: calc(50% - 100px);
} }
.pressed { .pressed {
background-color: red; background-color: red;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="clean"> <div id="clean">
<center> <center>
<input placeholder="Enter a VDON stream ID here" id="viewlink" type="text" /> <input placeholder="Enter a VDON stream ID here" id="viewlink" type="text" />
<button id="startButton" onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button></center> <button id="startButton" onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button></center>
</div> </div>
<script> <script>
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Announce the new orientation number // Announce the new orientation number
// alert(window.orientation); // alert(window.orientation);
}, false); }, false);
function loadIframes(url=false){ function loadIframes(url=false){
var streamID = document.getElementById("viewlink").value; var streamID = document.getElementById("viewlink").value;
https://vdo.ninja/?label&webcam&cleanoutput&ad=1&vd=1 https://vdo.ninja/?label&webcam&cleanoutput&ad=1&vd=1
var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
var streamSrc = "https://"+path+"/?push="+streamID+"&label&webcam&cleanoutput&ad=1&vd=1"; var streamSrc = "https://"+path+"/?push="+streamID+"&label&webcam&cleanoutput&ad=1&vd=1";
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean")); document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = streamSrc; iframe.src = streamSrc;
document.body.appendChild(iframe); document.body.appendChild(iframe);
function sendSelfCommand(action, value=null){ function sendSelfCommand(action, value=null){
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*'); iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
} }
var button = document.createElement("button"); var button = document.createElement("button");
button.id = "toggleMute"; button.id = "toggleMute";
button.innerHTML = "Mute"; button.innerHTML = "Mute";
button.dataset.value = "true"; button.dataset.value = "true";
document.body.appendChild(button); document.body.appendChild(button);
button.onclick = function(){ button.onclick = function(){
if (this.dataset.value=="true"){ if (this.dataset.value=="true"){
this.dataset.value = "false"; this.dataset.value = "false";
this.classList.add("pressed"); this.classList.add("pressed");
this.innerHTML = "Un-Mute"; this.innerHTML = "Un-Mute";
sendSelfCommand("mic",false); sendSelfCommand("mic",false);
} else { } else {
this.classList.remove("pressed"); this.classList.remove("pressed");
this.innerHTML = "Mute"; this.innerHTML = "Mute";
this.dataset.value = "true"; this.dataset.value = "true";
sendSelfCommand("mic",true); sendSelfCommand("mic",true);
} }
} }
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,27 +1,27 @@
<html><body><script> <html><body><script>
var generateHash = function (str, length=false){ var generateHash = function (str, length=false){
var buffer = new TextEncoder("utf-8").encode(str); var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then( return crypto.subtle.digest("SHA-256", buffer).then(
function (hash) { function (hash) {
hash = new Uint8Array(hash); hash = new Uint8Array(hash);
if (length){ if (length){
hash = hash.slice(0, parseInt(parseInt(length)/2)); hash = hash.slice(0, parseInt(parseInt(length)/2));
} }
hash = toHexString(hash); hash = toHexString(hash);
return hash; return hash;
} }
); );
}; };
function toHexString(byteArray){ function toHexString(byteArray){
return Array.prototype.map.call(byteArray, function(byte){ return Array.prototype.map.call(byteArray, function(byte){
return ('0' + (byte & 0xFF).toString(16)).slice(-2); return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join(''); }).join('');
} }
var password = prompt("Please enter the password"); var password = prompt("Please enter the password");
password = password.trim(); password = password.trim();
password = encodeURIComponent(password); password = encodeURIComponent(password);
generateHash(password + location.hostname, 4).then(function(hash) { // million to one error. generateHash(password + location.hostname, 4).then(function(hash) { // million to one error.
alert("hash value: "+hash) alert("hash value: "+hash)
}); });
</script></body></html> </script></body></html>

View File

@ -1,159 +1,159 @@
<html> <html>
<head> <head>
<meta charset="utf8" /> <meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OBSN Chat Overlay</title> <title>OBSN Chat Overlay</title>
<style> <style>
@font-face { @font-face {
font-family: 'Cousine'; font-family: 'Cousine';
src: url('fonts/Cousine-Bold.ttf') format('truetype'); src: url('fonts/Cousine-Bold.ttf') format('truetype');
} }
body { body {
margin:0; margin:0;
padding:0 10px; padding:0 10px;
height:100%; height:100%;
border: 0; border: 0;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
position:absolute; position:absolute;
bottom:0; bottom:0;
overflow:hidden; overflow:hidden;
max-width:100%; max-width:100%;
} }
div { div {
margin:0; margin:0;
background-color: black; background-color: black;
padding: 8px 8px 0px 8px; padding: 8px 8px 0px 8px;
color: white; color: white;
font-family: Cousine, monospace; font-family: Cousine, monospace;
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1em; line-height: 1.1em;
letter-spacing: 0.0em; letter-spacing: 0.0em;
text-transform: uppercase; text-transform: uppercase;
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1); text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
max-width:100%; max-width:100%;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
display:inline-block; display:inline-block;
} }
a { a {
color:white; color:white;
font-size:1.2em; font-size:1.2em;
text-transform: none; text-transform: none;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
} }
</style> </style>
<script> <script>
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams =
w.URLSearchParams || w.URLSearchParams ||
function (searchString) { function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec( var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
self.searchString self.searchString
); );
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
function loadIframe() { function loadIframe() {
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var view= ""; var view= "";
if (urlParams.has("view")) { if (urlParams.has("view")) {
view = "&view="+(urlParams.get("view") || ""); view = "&view="+(urlParams.get("view") || "");
} }
var room=""; var room="";
if (urlParams.has("room")) { if (urlParams.has("room")) {
room = "&room="+urlParams.get("room"); room = "&room="+urlParams.get("room");
} }
var password=""; var password="";
if (urlParams.has("password")) { if (urlParams.has("password")) {
password = "&password="+urlParams.get("password"); password = "&password="+urlParams.get("password");
} }
iframe.allow = "autoplay"; iframe.allow = "autoplay";
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password; var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
iframe.src = srcString; iframe.src = srcString;
iframe.style.width="0"; iframe.style.width="0";
iframe.style.height="0"; iframe.style.height="0";
iframe.style.border="0"; iframe.style.border="0";
document.body.appendChild(iframe); document.body.appendChild(iframe);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.log(e); console.log(e);
if ("gotChat" in e.data){ if ("gotChat" in e.data){
logData(e.data.gotChat.label,e.data.gotChat.msg); logData(e.data.gotChat.label,e.data.gotChat.msg);
} }
}); });
} }
function printValues(obj) { function printValues(obj) {
var out = ""; var out = "";
for (var key in obj) { for (var key in obj) {
if (typeof obj[key] === "object") { if (typeof obj[key] === "object") {
out += "<br />"; out += "<br />";
out += printValues(obj[key]); out += printValues(obj[key]);
} else { } else {
if (key.startsWith("_")) { if (key.startsWith("_")) {
} else { } else {
out += "<b>" + key + "</b>: " + obj[key] + "<br />"; out += "<b>" + key + "</b>: " + obj[key] + "<br />";
} }
} }
} }
return out; return out;
} }
function logData(type, data) { function logData(type, data) {
var span = document.createElement('span'); var span = document.createElement('span');
var entry = document.createElement('div'); var entry = document.createElement('div');
if (type){ if (type){
type = "<i>"+type.replace(/_/g, ' ')+"</i>"; type = "<i>"+type.replace(/_/g, ' ')+"</i>";
} }
entry.innerHTML = type + data; entry.innerHTML = type + data;
span.appendChild(entry); span.appendChild(entry);
document.body.prepend(span); document.body.prepend(span);
} }
</script> </script>
</head> </head>
<body onload="loadIframe();"> <body onload="loadIframe();">
</body> </body>
</html> </html>

View File

@ -1,162 +1,162 @@
<html> <html>
<head> <head>
<meta charset="utf8" /> <meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>VDON Chat Overlay</title> <title>VDON Chat Overlay</title>
<style> <style>
@font-face { @font-face {
font-family: 'Cousine'; font-family: 'Cousine';
src: url('fonts/Cousine-Bold.ttf') format('truetype'); src: url('fonts/Cousine-Bold.ttf') format('truetype');
} }
body { body {
margin:0; margin:0;
padding:0 10px; padding:0 10px;
height:100%; height:100%;
border: 0; border: 0;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
position:absolute; position:absolute;
bottom:0; bottom:0;
overflow:hidden; overflow:hidden;
max-width:100%; max-width:100%;
} }
div { div {
margin:0; margin:0;
background-color: black; background-color: black;
padding: 8px 8px 0px 8px; padding: 8px 8px 0px 8px;
color: white; color: white;
font-family: Cousine, monospace; font-family: Cousine, monospace;
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1em; line-height: 1.1em;
letter-spacing: 0.0em; letter-spacing: 0.0em;
text-transform: uppercase; text-transform: uppercase;
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1); text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
max-width:100%; max-width:100%;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
display:inline-block; display:inline-block;
} }
a { a {
color:white; color:white;
font-size:1.2em; font-size:1.2em;
text-transform: none; text-transform: none;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
} }
</style> </style>
<script> <script>
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams =
w.URLSearchParams || w.URLSearchParams ||
function (searchString) { function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec( var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
self.searchString self.searchString
); );
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
function loadIframe() { function loadIframe() {
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var view= ""; var view= "";
var room=""; var room="";
var password=""; var password="";
if (urlParams.has("view")) { if (urlParams.has("view")) {
view = "&view="+(urlParams.get("view") || ""); view = "&view="+(urlParams.get("view") || "");
} else if (urlParams.has("room")) { } else if (urlParams.has("room")) {
room = "&room="+urlParams.get("room"); room = "&room="+urlParams.get("room");
} else { } else {
var help = document.createElement("h2"); var help = document.createElement("h2");
help.innerHTML = "This app supports <i>&room, &view, </i>and<i> &password</i> URL parameters."; help.innerHTML = "This app supports <i>&room, &view, </i>and<i> &password</i> URL parameters.";
document.body.appendChild(help); document.body.appendChild(help);
return; return;
} }
if (urlParams.has("password")) { if (urlParams.has("password")) {
password = "&password="+urlParams.get("password"); password = "&password="+urlParams.get("password");
} }
iframe.allow = "autoplay"; iframe.allow = "autoplay";
var srcString = "../?datamode&label=chatOverlay&scene"+room+view+password; var srcString = "../?datamode&label=chatOverlay&scene"+room+view+password;
iframe.src = srcString; iframe.src = srcString;
iframe.style.width="0"; iframe.style.width="0";
iframe.style.height="0"; iframe.style.height="0";
iframe.style.border="0"; iframe.style.border="0";
document.body.appendChild(iframe); document.body.appendChild(iframe);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.log(e); console.log(e);
if ("gotChat" in e.data){ if ("gotChat" in e.data){
logData(e.data.gotChat.label,e.data.gotChat.msg); logData(e.data.gotChat.label,e.data.gotChat.msg);
} }
}); });
} }
function printValues(obj) { function printValues(obj) {
var out = ""; var out = "";
for (var key in obj) { for (var key in obj) {
if (typeof obj[key] === "object") { if (typeof obj[key] === "object") {
out += "<br />"; out += "<br />";
out += printValues(obj[key]); out += printValues(obj[key]);
} else { } else {
if (key.startsWith("_")) { if (key.startsWith("_")) {
} else { } else {
out += "<b>" + key + "</b>: " + obj[key] + "<br />"; out += "<b>" + key + "</b>: " + obj[key] + "<br />";
} }
} }
} }
return out; return out;
} }
function logData(type, data) { function logData(type, data) {
var span = document.createElement('span'); var span = document.createElement('span');
var entry = document.createElement('div'); var entry = document.createElement('div');
if (type){ if (type){
type = "<i>"+type.replace(/_/g, ' ')+"</i>"; type = "<i>"+type.replace(/_/g, ' ')+"</i>";
} }
entry.innerHTML = type + data; entry.innerHTML = type + data;
span.appendChild(entry); span.appendChild(entry);
document.body.prepend(span); document.body.prepend(span);
} }
</script> </script>
</head> </head>
<body onload="loadIframe();"> <body onload="loadIframe();">
</body> </body>
</html> </html>

View File

@ -1,123 +1,123 @@
<html> <html>
<head> <head>
<style> <style>
body { body {
margin:0; margin:0;
padding:0; padding:0;
height:100%; height:100%;
width:100%; width:100%;
border:0; border:0;
overflow:hidden; overflow:hidden;
} }
</style> </style>
</head> </head>
<body id="body"> <body id="body">
<button onclick='send({"abc1231":[0,0,50,50],abc1232:[50,0,50,50],abc1233:[0,50,50,50],abc1234:[50,50,50,50]});'>2x2</button> <button onclick='send({"abc1231":[0,0,50,50],abc1232:[50,0,50,50],abc1233:[0,50,50,50],abc1234:[50,50,50,50]});'>2x2</button>
<button onclick='send({"abc1231":[0,0,100,100],abc1232:[0, 0, 0, 0 ],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>1</button> <button onclick='send({"abc1231":[0,0,100,100],abc1232:[0, 0, 0, 0 ],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>1</button>
<button onclick='send({"abc1231":[0,0,50 ,100],abc1232:[50,0,100,100],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>2x1</button> <button onclick='send({"abc1231":[0,0,50 ,100],abc1232:[50,0,100,100],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>2x1</button>
<button onclick='send({"abc1231":[0,0,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button> <button onclick='send({"abc1231":[0,0,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
<script> <script>
// this app is mainly for demo purposes at this time. It has been depreciated. // this app is mainly for demo purposes at this time. It has been depreciated.
function updateURL(param, force=false) { function updateURL(param, force=false) {
var para = param.split('=')[0]; var para = param.split('=')[0];
if (!(urlParams.has(para)) || (force)){ if (!(urlParams.has(para)) || (force)){
if (history.pushState){ if (history.pushState){
var arr = window.location.href.split('?'); var arr = window.location.href.split('?');
var newurl; var newurl;
if (arr.length > 1 && arr[1] !== '') { if (arr.length > 1 && arr[1] !== '') {
newurl = window.location.href + '&' +param; newurl = window.location.href + '&' +param;
} else { } else {
newurl = window.location.href + '?' +param; newurl = window.location.href + '?' +param;
} }
window.history.pushState({path:newurl},'',newurl); window.history.pushState({path:newurl},'',newurl);
} }
} }
} }
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams || function (searchString) { w.URLSearchParams = w.URLSearchParams || function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} }
else { else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
function generateStreamID(){ function generateStreamID(){
var text = ""; var text = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"; var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
for (var i = 0; i < 7; i++){ for (var i = 0; i < 7; i++){
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
} }
return text; return text;
}; };
var roomID = "undefined"; var roomID = "undefined";
if (urlParams.has("room")){ if (urlParams.has("room")){
roomID = urlParams.get("room"); roomID = urlParams.get("room");
} else { } else {
roomID = generateStreamID(); roomID = generateStreamID();
updateURL("room="+roomID); updateURL("room="+roomID);
} }
var url = document.URL.substr(0,document.URL.lastIndexOf('/')); var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => { navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
/* clipboard successfully set */ /* clipboard successfully set */
}, () => { }, () => {
/* clipboard write failed */ /* clipboard write failed */
}); });
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID; document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
var socket = new WebSocket("wss://api.action.wtf:666"); // api.action.wtf has been deprecated. var socket = new WebSocket("wss://api.action.wtf:666"); // api.action.wtf has been deprecated.
socket.onclose = function (){ socket.onclose = function (){
setTimeout(function(){window.location.reload(true);},100); setTimeout(function(){window.location.reload(true);},100);
}; };
socket.onopen = function (){ socket.onopen = function (){
socket.send(JSON.stringify({"join":roomID})); socket.send(JSON.stringify({"join":roomID}));
} }
socket.addEventListener('message', function (event) { socket.addEventListener('message', function (event) {
if (event.data){ if (event.data){
var data = JSON.parse(event.data); var data = JSON.parse(event.data);
log(data); log(data);
} }
}); });
socket.onclose = function (){ socket.onclose = function (){
setTimeout(function(){window.location.reload(true);},100); setTimeout(function(){window.location.reload(true);},100);
}; };
var counter=0; var counter=0;
function send(scene){ function send(scene){
counter+=1; counter+=1;
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter})); socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,331 +1,331 @@
<html> <html>
<head><title>Dual Input</title> <head><title>Dual Input</title>
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
margin:0px; margin:0px;
min-height: 100px; min-height: 100px;
min-width: 100px; min-width: 100px;
max-height: 95%; max-height: 95%;
max-width: 99%%; max-width: 99%%;
float: left; float: left;
position: fixed; position: fixed;
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
position:relative; position:relative;
} }
.menu { .menu {
z-index: 10; z-index: 10;
float:right; float:right;
right: 20px; right: 20px;
color: #fff; color: #fff;
} }
.close { .close {
background-color: #d33; background-color: #d33;
color: #fff; color: #fff;
} }
.reload { .reload {
background-color: #0a0; background-color: #0a0;
color: #fff; color: #fff;
} }
.popup { .popup {
z-index: 9; z-index: 9;
background-color: #f1f1f1; background-color: #f1f1f1;
border: 1px solid #d3d3d3; border: 1px solid #d3d3d3;
text-align: center; text-align: center;
min-height: 100px; min-height: 100px;
min-width: 100px; min-width: 100px;
max-height: 95%; max-height: 95%;
max-width: 99%; max-width: 99%;
scale: 0.5; scale: 0.5;
} }
.popup { .popup {
position: absolute; position: absolute;
/*resize: both; !*enable this to css resize*! */ /*resize: both; !*enable this to css resize*! */
overflow: auto; overflow: auto;
} }
.popup-header { .popup-header {
cursor: move; cursor: move;
background-color: #2196f3; background-color: #2196f3;
} }
.popup .resizer-right { .popup .resizer-right {
width: 5px; width: 5px;
height: 100%; height: 100%;
background: transparent; background: transparent;
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
cursor: e-resize; cursor: e-resize;
} }
.popup .resizer-bottom { .popup .resizer-bottom {
width: 100%; width: 100%;
height: 5px; height: 5px;
background: transparent; background: transparent;
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
cursor: n-resize; cursor: n-resize;
} }
.popup .resizer-both { .popup .resizer-both {
width: 5px; width: 5px;
height: 5px; height: 5px;
background: transparent; background: transparent;
z-index: 10; z-index: 10;
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
cursor: nw-resize; cursor: nw-resize;
} }
/*NOSELECT*/ /*NOSELECT*/
.popup * { .popup * {
-webkit-touch-callout: none; /* iOS Safari */ -webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */ -khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */ -moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */ -ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */ supported by Chrome and Opera */
} }
</style> </style>
</head> </head>
<body> <body>
<input placeholder="Enter an VDO.Ninja Room Link" id="viewlink" /> <input placeholder="Enter an VDO.Ninja Room Link" id="viewlink" />
<button onclick="loadIframe();">Load URL</button>You can drag and resize the generated windows; multiple can be created. <button onclick="loadIframe();">Load URL</button>You can drag and resize the generated windows; multiple can be created.
<div id="container"></div> <div id="container"></div>
<script> <script>
var currentZIndex = 100; var currentZIndex = 100;
function initDragElement(popup){ function initDragElement(popup){
var pos1 = 0, var pos1 = 0,
pos2 = 0, pos2 = 0,
pos3 = 0, pos3 = 0,
pos4 = 0; pos4 = 0;
var elmnt = null; var elmnt = null;
var header = getHeader(popup); var header = getHeader(popup);
var iframe = getIFrame(popup); var iframe = getIFrame(popup);
popup.onmousedown = function() { popup.onmousedown = function() {
this.style.zIndex = "" + ++currentZIndex; this.style.zIndex = "" + ++currentZIndex;
}; };
if (header) { if (header) {
header.parentPopup = popup; header.parentPopup = popup;
header.onmousedown = dragMouseDown; header.onmousedown = dragMouseDown;
} }
function dragMouseDown(e) { function dragMouseDown(e) {
elmnt = this.parentPopup; elmnt = this.parentPopup;
elmnt.style.zIndex = "" + ++currentZIndex; elmnt.style.zIndex = "" + ++currentZIndex;
e = e || window.event; e = e || window.event;
// get the mouse cursor position at startup: // get the mouse cursor position at startup:
pos3 = e.clientX; pos3 = e.clientX;
pos4 = e.clientY; pos4 = e.clientY;
document.onmouseup = closeDragElement; document.onmouseup = closeDragElement;
// call a function whenever the cursor moves: // call a function whenever the cursor moves:
document.onmousemove = elementDrag; document.onmousemove = elementDrag;
} }
function elementDrag(e) { function elementDrag(e) {
if (!elmnt) { if (!elmnt) {
return; return;
} }
e = e || window.event; e = e || window.event;
// calculate the new cursor position: // calculate the new cursor position:
pos1 = pos3 - e.clientX; pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY; pos2 = pos4 - e.clientY;
pos3 = e.clientX; pos3 = e.clientX;
pos4 = e.clientY; pos4 = e.clientY;
// set the element's new position: // set the element's new position:
elmnt.style.top = elmnt.offsetTop - pos2 + "px"; elmnt.style.top = elmnt.offsetTop - pos2 + "px";
elmnt.style.left = elmnt.offsetLeft - pos1 + "px"; elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
iframe.style.top = elmnt.offsetTop - pos2 + "px"; iframe.style.top = elmnt.offsetTop - pos2 + "px";
iframe.style.left = elmnt.offsetLeft - pos1 + "px"; iframe.style.left = elmnt.offsetLeft - pos1 + "px";
} }
function closeDragElement() { function closeDragElement() {
/* stop moving when mouse button is released:*/ /* stop moving when mouse button is released:*/
document.onmouseup = null; document.onmouseup = null;
document.onmousemove = null; document.onmousemove = null;
} }
function getHeader(element) { function getHeader(element) {
var headerItems = element.getElementsByClassName("popup-header"); var headerItems = element.getElementsByClassName("popup-header");
if (headerItems.length === 1) { if (headerItems.length === 1) {
return headerItems[0]; return headerItems[0];
} }
return null; return null;
} }
function getIFrame(element) { function getIFrame(element) {
var headerItems = element.getElementsByTagName("iframe"); var headerItems = element.getElementsByTagName("iframe");
if (headerItems.length === 1) { if (headerItems.length === 1) {
return headerItems[0]; return headerItems[0];
} }
return null; return null;
} }
} }
function initResizeElement(p) { function initResizeElement(p) {
var iframe = getIFrame(p); var iframe = getIFrame(p);
var element = null; var element = null;
var startX, startY, startWidth, startHeight; var startX, startY, startWidth, startHeight;
var right = document.createElement("div"); var right = document.createElement("div");
right.className = "resizer-right"; right.className = "resizer-right";
p.appendChild(right); p.appendChild(right);
right.addEventListener("mousedown", initDrag, false); right.addEventListener("mousedown", initDrag, false);
right.parentPopup = p; right.parentPopup = p;
var bottom = document.createElement("div"); var bottom = document.createElement("div");
bottom.className = "resizer-bottom"; bottom.className = "resizer-bottom";
p.appendChild(bottom); p.appendChild(bottom);
bottom.addEventListener("mousedown", initDrag, false); bottom.addEventListener("mousedown", initDrag, false);
bottom.parentPopup = p; bottom.parentPopup = p;
var both = document.createElement("div"); var both = document.createElement("div");
both.className = "resizer-both"; both.className = "resizer-both";
p.appendChild(both); p.appendChild(both);
both.addEventListener("mousedown", initDrag, false); both.addEventListener("mousedown", initDrag, false);
both.parentPopup = p; both.parentPopup = p;
function initDrag(e) { function initDrag(e) {
element = this.parentPopup; element = this.parentPopup;
startX = e.clientX; startX = e.clientX;
startY = e.clientY; startY = e.clientY;
startWidth = parseInt( startWidth = parseInt(
document.defaultView.getComputedStyle(element).width, document.defaultView.getComputedStyle(element).width,
10 10
); );
startHeight = parseInt( startHeight = parseInt(
document.defaultView.getComputedStyle(element).height, document.defaultView.getComputedStyle(element).height,
10 10
); );
document.documentElement.addEventListener("mousemove", doDrag, false); document.documentElement.addEventListener("mousemove", doDrag, false);
document.documentElement.addEventListener("mouseup", stopDrag, false); document.documentElement.addEventListener("mouseup", stopDrag, false);
document.documentElement.addEventListener("click", stopDrag, false) document.documentElement.addEventListener("click", stopDrag, false)
} }
function doDrag(e) { function doDrag(e) {
if (e.buttons==0){ if (e.buttons==0){
stopDrag(e); stopDrag(e);
return false; return false;
} }
element.style.width = startWidth + e.clientX - startX + "px"; element.style.width = startWidth + e.clientX - startX + "px";
element.style.height = startHeight + e.clientY - startY + "px"; element.style.height = startHeight + e.clientY - startY + "px";
iframe.style.width = startWidth + e.clientX - startX + "px"; iframe.style.width = startWidth + e.clientX - startX + "px";
iframe.style.height = startHeight + e.clientY - startY + "px"; iframe.style.height = startHeight + e.clientY - startY + "px";
} }
function stopDrag(e) { function stopDrag(e) {
document.documentElement.removeEventListener("mousemove", doDrag, false); document.documentElement.removeEventListener("mousemove", doDrag, false);
document.documentElement.removeEventListener("mouseup", stopDrag, false); document.documentElement.removeEventListener("mouseup", stopDrag, false);
} }
function getIFrame(element) { function getIFrame(element) {
var headerItems = element.getElementsByTagName("iframe"); var headerItems = element.getElementsByTagName("iframe");
if (headerItems.length === 1) { if (headerItems.length === 1) {
return headerItems[0]; return headerItems[0];
} }
return null; return null;
} }
} }
function loadIframe(){ function loadIframe(){
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.className="popup"; iframeContainer.className="popup";
iframeContainer.style.zIndex = "" + ++currentZIndex; iframeContainer.style.zIndex = "" + ++currentZIndex;
iframeContainer.style.width="325px"; iframeContainer.style.width="325px";
iframeContainer.style.height="420px"; iframeContainer.style.height="420px";
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Move"; button.innerHTML = "Move";
button.className = "popup-header menu"; button.className = "popup-header menu";
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Close"; button.innerHTML = "Close";
button.className = "menu close"; button.className = "menu close";
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');iframe.parentNode.parentNode.removeChild(iframeContainer);} button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');iframe.parentNode.parentNode.removeChild(iframeContainer);}
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Reload"; button.innerHTML = "Reload";
button.className = "menu reload"; button.className = "menu reload";
button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');} button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow="autoplay"; iframe.allow="autoplay";
iframe.src = document.getElementById("viewlink").value || "https://vdo.ninja"; iframe.src = document.getElementById("viewlink").value || "https://vdo.ninja";
iframe.style.width="325px"; iframe.style.width="325px";
iframe.style.height="420px"; iframe.style.height="420px";
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
initDragElement(iframeContainer); initDragElement(iframeContainer);
initResizeElement(iframeContainer); initResizeElement(iframeContainer);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,76 +1,76 @@
<html> <html>
<head><title>Dual Input</title> <head><title>Dual Input</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
position:absolute; position:absolute;
display:block; display:block;
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container1" style="width:100%;height:100%;display:none;"></div> <div id="container1" style="width:100%;height:100%;display:none;"></div>
<div id="container2" style="width: calc(25vh*1.777);height: calc(25vh); display:none; float:left; position: fixed; top: 2%; left: 0%;"></div> <div id="container2" style="width: calc(25vh*1.777);height: calc(25vh); display:none; float:left; position: fixed; top: 2%; left: 0%;"></div>
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()"/> <input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()"/>
<script> <script>
function loadIframes(url=false){ function loadIframes(url=false){
var roomname = document.getElementById("viewlink").value; var roomname = document.getElementById("viewlink").value;
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink")); document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
document.getElementById("container1").style.display="inline-block"; document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block"; document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
var room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_front&webcam&autostart&vd=front&ad=1&exclude="+roomname+"_rear"; var room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_front&webcam&autostart&vd=front&ad=1&exclude="+roomname+"_rear";
var room2 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_rear&webcam&autostart&vd=back&ad=0&view&cleanoutput&nosettings&transparent"; var room2 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_rear&webcam&autostart&vd=back&ad=0&view&cleanoutput&nosettings&transparent";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = room1; iframe.src = room1;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container1").appendChild(iframeContainer); document.getElementById("container1").appendChild(iframeContainer);
setTimeout(function(){ setTimeout(function(){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room2; iframe.src = room2;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container2").appendChild(iframeContainer); document.getElementById("container2").appendChild(iframeContainer);
},3000); },3000);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,131 +1,131 @@
<html> <html>
<head> <head>
<title>IFRAME Example</title> <title>IFRAME Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:90% height:90%
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
</style> </style>
<script> <script>
function loadIframe(){ function loadIframe(){
document.getElementById("container").innerHTML = ""; document.getElementById("container").innerHTML = "";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view="; var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
var listOfStreamIDs = [ var listOfStreamIDs = [
"1234_pov", "1234_pov",
"2345_pov", "2345_pov",
"3456_pov", "3456_pov",
"4567_pov", "4567_pov",
"5678_pov" "5678_pov"
]; ];
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "List connected StreamIDs"; button.innerHTML = "List connected StreamIDs";
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');}; button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "HIDE ALL"; button.innerHTML = "HIDE ALL";
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');}; button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
for (var i=0;i<listOfStreamIDs.length;i++){ for (var i=0;i<listOfStreamIDs.length;i++){
if (i!==0){ if (i!==0){
iframesrc+=","; iframesrc+=",";
} }
iframesrc+=listOfStreamIDs[i]; iframesrc+=listOfStreamIDs[i];
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "SHOW "+listOfStreamIDs[i]; button.innerHTML = "SHOW "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i]; button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*'); iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
iframe.contentWindow.postMessage({"target":this.dataset.sid, "add":true, "settings":{"style":{"width":"100%", "height":"100%", "display":"block"}}}, '*'); iframe.contentWindow.postMessage({"target":this.dataset.sid, "add":true, "settings":{"style":{"width":"100%", "height":"100%", "display":"block"}}}, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
} }
iframe.src = iframesrc; iframe.src = iframesrc;
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "event: "+e.data.action+"<br />"; outputWindow.innerHTML = "event: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "streamID list:<br />"; outputWindow.innerHTML = "streamID list:<br />";
for (var key in e.data.streamIDs) { for (var key in e.data.streamIDs) {
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n"; outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
} }
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
}); });
} }
</script> </script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<button onclick="loadIframe();">CONNECT</button> <button onclick="loadIframe();">CONNECT</button>
</div> </div>
</body> </body>
</html> </html>

View File

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

View File

@ -1,5 +1,5 @@
<head> <head>
<link rel="stylesheet" href="../main.css?ver=40" /> <link rel="stylesheet" href="../../main.css?ver=40" />
<style> <style>
.container { .container {
max-width: 900px; max-width: 900px;
@ -58,7 +58,7 @@
<body style='color:white'> <body style='color:white'>
<div id="header"> <div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px"> <a id="logoname" href="" style="text-decoration: none; color: white; margin: 2px">
<span data-translate="logo-header"> <span data-translate="logo-header">
<font id="qos">V</font>DO.Ninja <font id="qos">V</font>DO.Ninja
</span> </span>

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
.tile { .tile {
max-width:200px !important; max-width:200px !important;
} }

View File

@ -1,451 +1,451 @@
<html> <html>
<head> <head>
<title>IFRAME Example</title> <title>IFRAME Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
padding:0; padding:0;
display:block; display:block;
width:1280px; width:1280px;
height:720px; height:720px;
background-color: #111; background-color: #111;
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
canvas{ canvas{
padding:10px; padding:10px;
cursor:pointer; cursor:pointer;
} }
.thing { .thing {
width: 100px; width: 100px;
height: 2em; height: 2em;
padding: 0.5em; padding: 0.5em;
margin: 0.5em; margin: 0.5em;
background: rgba(0,0,0,0.8); background: rgba(0,0,0,0.8);
color: white; color: white;
font-family: sans-serif; font-family: sans-serif;
cursor: grab; cursor: grab;
} }
.empty { .empty {
width: 100px; width: 100px;
height: 2em; height: 2em;
padding: 0.5em; padding: 0.5em;
margin: 0.5em; margin: 0.5em;
background: rgba(0,0,0,0.8); background: rgba(0,0,0,0.8);
color: white; color: white;
font-family: sans-serif; font-family: sans-serif;
user-select: none; user-select: none;
} }
.col { .col {
width: 130px; width: 130px;
height: 450px; height: 450px;
padding: 1em; padding: 1em;
border: 1px solid; border: 1px solid;
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
float: left; float: left;
} }
</style> </style>
<script> <script>
function allowDrop(ev) { function allowDrop(ev) {
ev.preventDefault(); ev.preventDefault();
} }
function swapNodes(n1, n2) { function swapNodes(n1, n2) {
var p1 = n1.parentNode; var p1 = n1.parentNode;
var p2 = n2.parentNode; var p2 = n2.parentNode;
var i1, i2; var i1, i2;
if ( !p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1) ) return; if ( !p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1) ) return;
for (var i = 0; i < p1.children.length; i++) { for (var i = 0; i < p1.children.length; i++) {
if (p1.children[i].isEqualNode(n1)) { if (p1.children[i].isEqualNode(n1)) {
i1 = i; i1 = i;
} }
} }
for (var i = 0; i < p2.children.length; i++) { for (var i = 0; i < p2.children.length; i++) {
if (p2.children[i].isEqualNode(n2)) { if (p2.children[i].isEqualNode(n2)) {
i2 = i; i2 = i;
} }
} }
if ( p1.isEqualNode(p2) && i1 < i2 ) { if ( p1.isEqualNode(p2) && i1 < i2 ) {
i2++; i2++;
} }
p1.insertBefore(n2, p1.children[i1]); p1.insertBefore(n2, p1.children[i1]);
p2.insertBefore(n1, p2.children[i2]); p2.insertBefore(n1, p2.children[i2]);
} }
function drag(ev) { function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id); ev.dataTransfer.setData("text", ev.target.id);
} }
function drop(ev) { function drop(ev) {
ev.preventDefault(); ev.preventDefault();
var data = ev.dataTransfer.getData("text"); var data = ev.dataTransfer.getData("text");
var origThing = document.getElementById(data); var origThing = document.getElementById(data);
console.log(origThing); console.log(origThing);
console.log(data); console.log(data);
console.log(ev); console.log(ev);
//var newThing = origThing.cloneNode(true); //var newThing = origThing.cloneNode(true);
if (ev.target.classList.contains("thing")){ if (ev.target.classList.contains("thing")){
//ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling); //ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
//elem.parentNode.insertBefore(elem, elem.parentNode.firstChild); //elem.parentNode.insertBefore(elem, elem.parentNode.firstChild);
swapNodes( ev.target, origThing); swapNodes( ev.target, origThing);
var slot = origThing.dataset.slot; var slot = origThing.dataset.slot;
origThing.dataset.slot = ev.target.dataset.slot; origThing.dataset.slot = ev.target.dataset.slot;
ev.target.dataset.slot = slot; ev.target.dataset.slot = slot;
} else if (ev.target.classList.contains("empty")){ } else if (ev.target.classList.contains("empty")){
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling); ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
origThing.dataset.slot = ev.target.dataset.slot; origThing.dataset.slot = ev.target.dataset.slot;
ev.target.style.display = "none"; ev.target.style.display = "none";
} }
origThing.style.backgroundColor = ev.target.style.backgroundColor; origThing.style.backgroundColor = ev.target.style.backgroundColor;
} }
function dropRemove(ev) { function dropRemove(ev) {
ev.preventDefault(); ev.preventDefault();
var data = ev.dataTransfer.getData("text"); var data = ev.dataTransfer.getData("text");
var origThing = document.getElementById(data); var origThing = document.getElementById(data);
if (origThing.dataset.slot){ if (origThing.dataset.slot){
document.querySelector(".empty[data-slot='"+origThing.dataset.slot+"']").style.display = "block"; document.querySelector(".empty[data-slot='"+origThing.dataset.slot+"']").style.display = "block";
delete origThing.dataset.slot; delete origThing.dataset.slot;
} }
origThing.style.backgroundColor = "#000"; origThing.style.backgroundColor = "#000";
if (ev.target.classList.contains("thing")){ if (ev.target.classList.contains("thing")){
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling); ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
} else { } else {
ev.target.appendChild(origThing); ev.target.appendChild(origThing);
} }
document.getElementById("col2").appendChild(document.getElementById("delete")); document.getElementById("col2").appendChild(document.getElementById("delete"));
} }
var streamIDs = []; var streamIDs = [];
function updateList(){ function updateList(){
//<div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)"> //<div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)">
// <div class="thing" draggable="true" ondragstart="drag(event)" id="thing4">THING 4</div> // <div class="thing" draggable="true" ondragstart="drag(event)" id="thing4">THING 4</div>
// <div class="thing" draggable="true" ondragstart="drag(event)" id="thing1">THING 1</div> // <div class="thing" draggable="true" ondragstart="drag(event)" id="thing1">THING 1</div>
//</div> //</div>
for (var i=0;i<streamIDs.length;i++){ for (var i=0;i<streamIDs.length;i++){
if (!document.getElementById("sid_"+streamIDs[i])){ if (!document.getElementById("sid_"+streamIDs[i])){
var thing = document.createElement("div"); var thing = document.createElement("div");
thing.draggable = true; thing.draggable = true;
thing.classList.add("thing"); thing.classList.add("thing");
thing.addEventListener("dragstart", drag); thing.addEventListener("dragstart", drag);
thing.dataset.sid = streamIDs[i]; thing.dataset.sid = streamIDs[i];
thing.id = "sid_"+streamIDs[i]; thing.id = "sid_"+streamIDs[i];
thing.innerText = streamIDs[i]; thing.innerText = streamIDs[i];
document.getElementById("col2").appendChild(thing); document.getElementById("col2").appendChild(thing);
} }
} }
document.getElementById("col2").appendChild(document.getElementById("delete")); document.getElementById("col2").appendChild(document.getElementById("delete"));
} }
function loadIframe(){ function loadIframe(){
document.getElementById("container").innerHTML = ""; document.getElementById("container").innerHTML = "";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
var promptRoom = prompt("Enter a room name to use"); var promptRoom = prompt("Enter a room name to use");
if (promptRoom){ if (promptRoom){
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom; var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
} else { } else {
promptRoom = "testroom123312"; promptRoom = "testroom123312";
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom; var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
} }
function activate(){ function activate(){
console.log(this.dataset.layout); console.log(this.dataset.layout);
var layout = JSON.parse(this.dataset.layout); var layout = JSON.parse(this.dataset.layout);
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*'); iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
for (var i=0;i<layout.length;i++){ for (var i=0;i<layout.length;i++){
var stream = document.querySelector(".thing[data-slot='"+(i+1)+"'"); var stream = document.querySelector(".thing[data-slot='"+(i+1)+"'");
if (!stream){continue;} if (!stream){continue;}
var x = layout[i].x|| 0; var x = layout[i].x|| 0;
var y = layout[i].y || 0; var y = layout[i].y || 0;
var w = layout[i].w || 0; var w = layout[i].w || 0;
var h = layout[i].h || 0; var h = layout[i].h || 0;
var cover = layout[i].cover || false; var cover = layout[i].cover || false;
if (!(w && h)){continue;} if (!(w && h)){continue;}
x = x + "%"; x = x + "%";
y = y + "%"; y = y + "%";
w = w + "%"; w = w + "%";
h = h + "%"; h = h + "%";
if (cover){ if (cover){
cover = "object-fit:cover;"; cover = "object-fit:cover;";
} else { } else {
cover = ""; cover = "";
} }
iframe.contentWindow.postMessage({"target":stream.dataset.sid, "add":true, "settings":{"style": "width:"+w+";height:"+h+";position:absolute;left:"+x+";top:"+y+";display:block;"+cover}}, '*'); iframe.contentWindow.postMessage({"target":stream.dataset.sid, "add":true, "settings":{"style": "width:"+w+";height:"+h+";position:absolute;left:"+x+";top:"+y+";display:block;"+cover}}, '*');
} }
} }
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Refresh list"; button.innerHTML = "Refresh list";
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');}; button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
button.style.display = "block"; button.style.display = "block";
document.getElementById("sources").appendChild(button); document.getElementById("sources").appendChild(button);
var a = document.createElement("a"); var a = document.createElement("a");
a.innerHTML = "Invite Guest Link"; a.innerHTML = "Invite Guest Link";
a.href = "https://vdo.ninja/?room="+promptRoom+"&broadcast"; a.href = "https://vdo.ninja/?room="+promptRoom+"&broadcast";
a.target = "_blank"; a.target = "_blank";
document.getElementById("sources").appendChild(a); document.getElementById("sources").appendChild(a);
var colors = [ var colors = [
"#00AAAA", "#00AAAA",
"#FF0000", "#FF0000",
"#0000FF", "#0000FF",
"#AA00AA", "#AA00AA",
"#00FF00", "#00FF00",
"#AAAA00" "#AAAA00"
]; ];
var slots = document.getElementById("col1").children; var slots = document.getElementById("col1").children;
for (var i=0;i<slots.length;i++){ for (var i=0;i<slots.length;i++){
slots[i].style.backgroundColor = colors[i]; slots[i].style.backgroundColor = colors[i];
} }
function drawLayout(layout){ function drawLayout(layout){
for (var i=0;i<layout.length;i++){ for (var i=0;i<layout.length;i++){
layout[i].i = i; layout[i].i = i;
} }
function compare( a, b ) { // sorts layout based on z-index. function compare( a, b ) { // sorts layout based on z-index.
var aa = a.z || 0; var aa = a.z || 0;
var bb = b.z || 0; var bb = b.z || 0;
if ( aa > bb ){ if ( aa > bb ){
return 1; return 1;
} }
if ( aa < bb ){ if ( aa < bb ){
return -1; return -1;
} }
return 0; return 0;
} }
layout.sort(compare); layout.sort(compare);
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
canvas.width="80"; canvas.width="80";
canvas.height="45"; canvas.height="45";
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
document.getElementById("container").appendChild(canvas); document.getElementById("container").appendChild(canvas);
ctx.beginPath(); ctx.beginPath();
ctx.rect(0, 0, 80, 45); ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000"; ctx.fillStyle = "#000";
ctx.fill(); ctx.fill();
for (var i=0;i<layout.length;i++){ for (var i=0;i<layout.length;i++){
ctx.fillStyle = colors[layout[i].i]; ctx.fillStyle = colors[layout[i].i];
ctx.lineWidth = 3; ctx.lineWidth = 3;
var x = layout[i].x*0.8 || 0; var x = layout[i].x*0.8 || 0;
var y = layout[i].y*0.45 || 0; var y = layout[i].y*0.45 || 0;
var w = layout[i].w*0.8 || 0; var w = layout[i].w*0.8 || 0;
var h = layout[i].h*0.45 || 0; var h = layout[i].h*0.45 || 0;
ctx.beginPath(); ctx.beginPath();
ctx.rect(x, y, w, h); ctx.rect(x, y, w, h);
ctx.fill(); ctx.fill();
} }
canvas.dataset.layout = JSON.stringify(layout); canvas.dataset.layout = JSON.stringify(layout);
canvas.onclick = activate; canvas.onclick = activate;
} }
var data = [ var data = [
{x:0, y:0, w:100, h:100} {x:0, y:0, w:100, h:100}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:0, w:0, h:0}, {x:0, y:0, w:0, h:0},
{x:0, y:0, w:100, h:100, cover:true} {x:0, y:0, w:100, h:100, cover:true}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:25, w:50, h:50, cover:true}, {x:0, y:25, w:50, h:50, cover:true},
{x:50, y:25, w:50, h:50, cover:true} {x:50, y:25, w:50, h:50, cover:true}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:70, y:70, w:30, h:30, z:1, cover:false}, {x:70, y:70, w:30, h:30, z:1, cover:false},
{x:0, y:0, w:100, h:100,z:0, cover:true} {x:0, y:0, w:100, h:100,z:0, cover:true}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:0, w:100, h:100,z:0, cover:true}, {x:0, y:0, w:100, h:100,z:0, cover:true},
{x:70, y:70, w:30, h:30, z:1, cover:false} {x:70, y:70, w:30, h:30, z:1, cover:false}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:0, w:20, h:20, z:1, cover:false}, {x:0, y:0, w:20, h:20, z:1, cover:false},
{x:0, y:0, w:100, h:100,z:0, cover:true} {x:0, y:0, w:100, h:100,z:0, cover:true}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:0, w:50, h:50}, {x:0, y:0, w:50, h:50},
{x:50, y:0, w:50, h:50}, {x:50, y:0, w:50, h:50},
{x:0, y:50, w:50, h:50}, {x:0, y:50, w:50, h:50},
{x:50, y:50, w:50, h:50} {x:50, y:50, w:50, h:50}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:16.667, w:66.667, h:66.667}, {x:0, y:16.667, w:66.667, h:66.667},
{x:66.667, y:0, w:33.333, h:33.333}, {x:66.667, y:0, w:33.333, h:33.333},
{x:66.667, y:33.333, w:33.333, h:33.333}, {x:66.667, y:33.333, w:33.333, h:33.333},
{x:66.667, y:66.667, w:33.333, h:33.333} {x:66.667, y:66.667, w:33.333, h:33.333}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:66.667, y:0, w:33.333, h:33.333}, {x:66.667, y:0, w:33.333, h:33.333},
{x:0, y:16.667, w:66.667, h:66.667}, {x:0, y:16.667, w:66.667, h:66.667},
{x:66.667, y:33.333, w:33.333, h:33.333}, {x:66.667, y:33.333, w:33.333, h:33.333},
{x:66.667, y:66.667, w:33.333, h:33.333} {x:66.667, y:66.667, w:33.333, h:33.333}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{x:0, y:0, w:0, h:0}, {x:0, y:0, w:0, h:0},
{}, {},
{x:0, y:0, w:100, h:100} {x:0, y:0, w:100, h:100}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{}, {},
{}, {},
{}, {},
{x:0, y:0, w:100, h:100} {x:0, y:0, w:100, h:100}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{}, {},
{}, {},
{x:70, y:70, w:30, h:30, z:1, cover:false}, {x:70, y:70, w:30, h:30, z:1, cover:false},
{x:0, y:0, w:100, h:100,z:0, cover:true} {x:0, y:0, w:100, h:100,z:0, cover:true}
]; ];
drawLayout(data); drawLayout(data);
var data = [ var data = [
{}, {},
{}, {},
{x:0, y:25, w:50, h:50, cover:true}, {x:0, y:25, w:50, h:50, cover:true},
{x:50, y:25, w:50, h:50, cover:true} {x:50, y:25, w:50, h:50, cover:true}
]; ];
drawLayout(data); drawLayout(data);
iframe.src = iframesrc; iframe.src = iframesrc;
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "event: "+e.data.action+"<br />"; outputWindow.innerHTML = "event: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
if (e.data.action === "new-view-connection"){ if (e.data.action === "new-view-connection"){
iframe.contentWindow.postMessage({"getStreamIDs":true}, '*'); iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');
} }
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
streamIDs = []; streamIDs = [];
for (var key in e.data.streamIDs){ for (var key in e.data.streamIDs){
streamIDs.push(key); streamIDs.push(key);
} }
updateList(); updateList();
console.log(streamIDs); console.log(streamIDs);
} }
}); });
} }
</script> </script>
</head> </head>
<body onload="loadIframe();"> <body onload="loadIframe();">
<div class="col" id="sources"> <div class="col" id="sources">
<div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)"> <div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)">
<div class="thing" draggable="false" id="delete" style="background-color:rgb(96 9 9);">REMOVE</div> <div class="thing" draggable="false" id="delete" style="background-color:rgb(96 9 9);">REMOVE</div>
</div> </div>
</div> </div>
<div class="col" id="col1" ondrop="drop(event)" ondragover="allowDrop(event)"> <div class="col" id="col1" ondrop="drop(event)" ondragover="allowDrop(event)">
<div class="empty" data-slot="1">SLOT 1</div> <div class="empty" data-slot="1">SLOT 1</div>
<div class="empty" data-slot="2">SLOT 2</div> <div class="empty" data-slot="2">SLOT 2</div>
<div class="empty" data-slot="3">SLOT 3</div> <div class="empty" data-slot="3">SLOT 3</div>
<div class="empty" data-slot="4">SLOT 4</div> <div class="empty" data-slot="4">SLOT 4</div>
</div> </div>
<div id="container"> <div id="container">
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,147 +1,147 @@
body{ body{
zoom: 75%; zoom: 75%;
} }
button[data-action-type='solo-chat'] { button[data-action-type='solo-chat'] {
display:none! important; display:none! important;
} }
button[data-action-type] { button[data-action-type] {
padding: 20px 10px; padding: 20px 10px;
} }
#controlButtons{ #controlButtons{
display:none! important; display:none! important;
} }
button[data-cluster='2'] { button[data-cluster='2'] {
display:none! important; display:none! important;
} }
button[data-action-type='solo-video'] { button[data-action-type='solo-video'] {
display:unset!important; display:unset!important;
visibility: visible; visibility: visible;
width:unset; width:unset;
height:unset; height:unset;
opacity: 1; opacity: 1;
} }
div > a.soloLink{ div > a.soloLink{
display:none! important; display:none! important;
} }
div.shift{ div.shift{
display:none! important; display:none! important;
} }
div.streamID{ div.streamID{
display:none! important; display:none! important;
} }
button[data-action-type='forward'] { button[data-action-type='forward'] {
display:none! important; display:none! important;
} }
button[data-action-type='direct-chat'] { button[data-action-type='direct-chat'] {
display:none! important; display:none! important;
} }
button[data-action-type='hangup'] { button[data-action-type='hangup'] {
display:none! important; display:none! important;
} }
button[data-action-type='solo-chat'] { button[data-action-type='solo-chat'] {
display:none! important; display:none! important;
} }
button[data-action-type='solo-chat'] { button[data-action-type='solo-chat'] {
display:none! important; display:none! important;
} }
button[data-cluster='1'] { button[data-cluster='1'] {
display:none! important; display:none! important;
} }
button[data-action-type='recorder-local'] { button[data-action-type='recorder-local'] {
display:none! important; display:none! important;
} }
span[data-action-type='ordering'] { span[data-action-type='ordering'] {
display:none! important; display:none! important;
} }
button[data-action-type='open-file-share'] { button[data-action-type='open-file-share'] {
display:none! important; display:none! important;
} }
button[data-action-type='add-channel']{ button[data-action-type='add-channel']{
display:none! important; display:none! important;
} }
button[data-action-type='toggle-remote-speaker']{ button[data-action-type='toggle-remote-speaker']{
display:none! important; display:none! important;
} }
button[data-action-type='toggle-remote-display']{ button[data-action-type='toggle-remote-display']{
display:none! important; display:none! important;
} }
button[data-action-type='hide-guest']{ button[data-action-type='hide-guest']{
display:none! important; display:none! important;
} }
button[data-action-type='create-timer']{ button[data-action-type='create-timer']{
display:none! important; display:none! important;
} }
button[data-action-type='change-url']{ button[data-action-type='change-url']{
display:none! important; display:none! important;
} }
button[data-action-type='change-params']{ button[data-action-type='change-params']{
display:none! important; display:none! important;
} }
span[data-action-type='change-quality']{ span[data-action-type='change-quality']{
display:none! important; display:none! important;
} }
span[data-action-type='sceneCluster2']{ span[data-action-type='sceneCluster2']{
display:none! important; display:none! important;
} }
span[data-action-type='sceneCluster1']{ span[data-action-type='sceneCluster1']{
display:none! important; display:none! important;
} }
.orderspan{ .orderspan{
display:none! important; display:none! important;
} }
#roomHeader{ #roomHeader{
display:none! important; display:none! important;
} }
.directorContainer { .directorContainer {
display:none!important; display:none!important;
} }
.hideDropMenu{ .hideDropMenu{
display:none!important; display:none!important;
} }
#header{ #header{
display:none!important; display:none!important;
} }
body { body {
background-color: #000; background-color: #000;
} }
button[class="pull-right"]{ button[class="pull-right"]{
display:none! important; display:none! important;
} }
div#guestFeeds { div#guestFeeds {
padding: 1px!important; padding: 1px!important;
margin: 1px!important; margin: 1px!important;
} }
div[class="vidcon directorMargins"] { div[class="vidcon directorMargins"] {
padding: 1px!important; padding: 1px!important;
margin: 1px!important; margin: 1px!important;
width: 260px; width: 260px;
} }
button[data-action-type]{ button[data-action-type]{
margin: 1px!important; margin: 1px!important;
} }

View File

@ -1,112 +1,112 @@
<html> <html>
<head><title>Dual Input</title> <head><title>Dual Input</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
} }
iframe { iframe {
width:100%; width:100%;
height:470px; height:470px;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
} }
</style> </style>
</head> </head>
<body> <body>
<script> <script>
(function(w) { (function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) { w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this; var self = this;
searchString = searchString.replace("??", "?"); searchString = searchString.replace("??", "?");
self.searchString = searchString; self.searchString = searchString;
self.get = function(name) { self.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlEdited = window.location.search.replace(/\?\?/g, "?"); var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&"); urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?"); urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){ if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited); warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString()); window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
} }
var urlParams = new URLSearchParams(urlEdited); var urlParams = new URLSearchParams(urlEdited);
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
var password = urlParams.get("passwords") || urlParams.get("password") || urlParams.get("pw") || urlParams.get("p") || null; var password = urlParams.get("passwords") || urlParams.get("password") || urlParams.get("pw") || urlParams.get("p") || null;
var rooms = ""; var rooms = "";
if (urlParams.has("rooms") || urlParams.has("room") || urlParams.has("r")){ if (urlParams.has("rooms") || urlParams.has("room") || urlParams.has("r")){
rooms = urlParams.get("rooms") || urlParams.get("room") || urlParams.get("r"); rooms = urlParams.get("rooms") || urlParams.get("room") || urlParams.get("r");
rooms = rooms.split(","); rooms = rooms.split(",");
if (password == null){ if (password == null){
password = prompt("Enter the password for the rooms; leave blank for none"); password = prompt("Enter the password for the rooms; leave blank for none");
} }
if (password){ if (password){
password = password.split(","); password = password.split(",");
} else { } else {
password = ""; password = "";
} }
for (var i = 0;i<rooms.length;i++){ for (var i = 0;i<rooms.length;i++){
var pass = ""; var pass = "";
if (password && (password.length>i)){ if (password && (password.length>i)){
pass = decodeURIComponent(password[i]); pass = decodeURIComponent(password[i]);
if (pass){ if (pass){
pass = "&password="+pass; pass = "&password="+pass;
} }
} else if (password[0]){ } else if (password[0]){
pass = decodeURIComponent(password[0]); pass = decodeURIComponent(password[0]);
if (pass){ if (pass){
pass = "&password="+pass; pass = "&password="+pass;
} }
} }
loadIframes("https://"+path+"/../?clean&hidecodirectors&director="+rooms[i]+pass); loadIframes("https://"+path+"/../?clean&hidecodirectors&director="+rooms[i]+pass);
} }
} else { } else {
document.write("To use, comma separate the room names. ie: https://vdo.ninja/examples/multi?rooms=xxxx,yyy,ccc"); document.write("To use, comma separate the room names. ie: https://vdo.ninja/examples/multi?rooms=xxxx,yyy,ccc");
} }
function loadIframes(url){ function loadIframes(url){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
var params = window.location.search || ""; var params = window.location.search || "";
if (params.startsWith("?")){ if (params.startsWith("?")){
params = params.slice(1); params = params.slice(1);
iframe.src = url + "&" + params iframe.src = url + "&" + params
} else { } else {
iframe.src = url + params iframe.src = url + params
} }
document.body.appendChild(iframe); document.body.appendChild(iframe);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,319 +1,319 @@
<html> <html>
<head> <head>
<title>IFRAME Example</title> <title>IFRAME Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:90% height:90%
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
</style> </style>
<script> <script>
function loadIframe(){ function loadIframe(){
document.getElementById("container").innerHTML = ""; document.getElementById("container").innerHTML = "";
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = "../?dir=teststeve123&password=1234"; iframe.src = "../?dir=teststeve123&password=1234";
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
var listOfStreamIDs = [ var listOfStreamIDs = [
"1234_pov" "1234_pov"
]; ];
for (var i=0;i<listOfStreamIDs.length;i++){ for (var i=0;i<listOfStreamIDs.length;i++){
var button = document.createElement("a"); var button = document.createElement("a");
button.innerHTML = "Invite "+listOfStreamIDs[i]; button.innerHTML = "Invite "+listOfStreamIDs[i];
button.target = "_blank"; button.target = "_blank";
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i]; button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "speaker true "+listOfStreamIDs[i]; button.innerHTML = "speaker true "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "speaker", action: "speaker",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "speaker false "+listOfStreamIDs[i]; button.innerHTML = "speaker false "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "speaker", action: "speaker",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "display true "+listOfStreamIDs[i]; button.innerHTML = "display true "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "display", action: "display",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "display false "+listOfStreamIDs[i]; button.innerHTML = "display false "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "display", action: "display",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "MUTE true "+listOfStreamIDs[i]; button.innerHTML = "MUTE true "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "mic", action: "mic",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "UN-MUTE "+listOfStreamIDs[i]; button.innerHTML = "UN-MUTE "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "mic", action: "mic",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "addScene 1 toggle "+listOfStreamIDs[i]; button.innerHTML = "addScene 1 toggle "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "addScene", action: "addScene",
value: 1, value: 1,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Scene 1 toggle "+listOfStreamIDs[i]; button.innerHTML = "Scene 1 toggle "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "addScene", action: "addScene",
value: "toggle", value: "toggle",
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "add Scene 1"+listOfStreamIDs[i]; button.innerHTML = "add Scene 1"+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "addScene", action: "addScene",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "remove Scene 1"+listOfStreamIDs[i]; button.innerHTML = "remove Scene 1"+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "addScene", action: "addScene",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "MUTE SCENE "+listOfStreamIDs[i]; button.innerHTML = "MUTE SCENE "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "muteScene", action: "muteScene",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "un-mute Scene "+listOfStreamIDs[i]; button.innerHTML = "un-mute Scene "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "muteScene", action: "muteScene",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
/////////////////// ///////////////////
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "soloChat "+listOfStreamIDs[i]; button.innerHTML = "soloChat "+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "soloChat", action: "soloChat",
value: true, value: true,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "soloChat off"+listOfStreamIDs[i]; button.innerHTML = "soloChat off"+listOfStreamIDs[i];
button.dataset.sid = listOfStreamIDs[i]; button.dataset.sid = listOfStreamIDs[i];
button.onclick = function(){ button.onclick = function(){
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
action: "soloChat", action: "soloChat",
value: false, value: false,
target: this.dataset.sid target: this.dataset.sid
}, '*'); }, '*');
}; // target can be a stream ID or * for all. }; // target can be a stream ID or * for all.
iframeContainer.appendChild(button); iframeContainer.appendChild(button);
} }
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if (typeof e.data !== "object"){return;} if (typeof e.data !== "object"){return;}
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "event: "+e.data.action+"<br />"; outputWindow.innerHTML = "event: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "streamID list:<br />"; outputWindow.innerHTML = "streamID list:<br />";
for (var key in e.data.streamIDs) { for (var key in e.data.streamIDs) {
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n"; outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
} }
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
}); });
} }
</script> </script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<button onclick="loadIframe();">Go to Directors Room</button> <button onclick="loadIframe();">Go to Directors Room</button>
<br /> <br />
The password for guests is 1234<br /> The password for guests is 1234<br />
<br /> <br />
<br /> <br />
Custom guest invites and toggles for add/removing from scene=1 are on the bottom. Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,401 +1,401 @@
<html> <html>
<head> <head>
<script type="text/javascript" src="./thirdparty/obs-websocket.min.js"></script> <script type="text/javascript" src="thirdparty/obs-websocket.min.js"></script>
<link rel="stylesheet" href="https://vdo.ninja/main.css" /> <link rel="stylesheet" href="https://vdo.ninja/main.css" />
<title>OBS Controller Demo using VDO.Ninja</title> <title>OBS Controller Demo using VDO.Ninja</title>
<style> <style>
.container { .container {
max-width: 80%; max-width: 80%;
width: fit-content; width: fit-content;
margin: 0 auto; margin: 0 auto;
} }
h1 { h1 {
margin-top: 1em; margin-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
padding: 10px; padding: 10px;
} }
.card { .card {
margin: 10px; margin: 10px;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%); box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
background-color: #ddd; background-color: #ddd;
color: black; color: black;
margin-bottom: 1.5em; margin-bottom: 1.5em;
} }
.card>div { .card>div {
padding: 10px; padding: 10px;
} }
.card h2 { .card h2 {
font-size: 1.5em; font-size: 1.5em;
padding: 10px; padding: 10px;
background-color: #457b9d; background-color: #457b9d;
color: white; color: white;
border-bottom: 2px solid #3b6a87; border-bottom: 2px solid #3b6a87;
} }
small { small {
font-style: italic; font-style: italic;
display: block; display: block;
margin-left: 1em; margin-left: 1em;
} }
span.warning { span.warning {
color: rgb(212, 191, 0); color: rgb(212, 191, 0);
} }
input { input {
padding: 10px; padding: 10px;
display: inline-block; display: inline-block;
flex-flow: unset; flex-flow: unset;
margin: 10px; margin: 10px;
} }
video { video {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
audio { audio {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
div#processing { div#processing {
display: none; display: none;
justify-content: center; justify-content: center;
place-items: center; place-items: center;
position: absolute; position: absolute;
inset: 0; inset: 0;
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
background: #141926; background: #141926;
flex-direction: column; flex-direction: column;
} }
button { button {
margin:5px; margin:5px;
border:solid black 2px; border:solid black 2px;
} }
body { body {
color:white; color:white;
display: inline-block; display: inline-block;
flex-flow: unset; flex-flow: unset;
} }
a { a {
color: #CEF!important; color: #CEF!important;
} }
#info{ #info{
margin: 20px; margin: 20px;
max-height: 50%; max-height: 50%;
overflow: auto; overflow: auto;
} }
#client { #client {
margin:10px; margin:10px;
display:block; display:block;
} }
label { label {
color:white; color:white;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>OBS remote (server)</h1> <h1>OBS remote (server)</h1>
<span id='setup'> <span id='setup'>
<label for="address">Websocket Address</label> <label for="address">Websocket Address</label>
<input name="address" id="address" placeholder="address (optional)" value="localhost:4444" /> <input name="address" id="address" placeholder="address (optional)" value="localhost:4444" />
<label for="address">Websocket Password</label> <label for="address">Websocket Password</label>
<input name="password" id="password" placeholder="password here (optional)" /> <input name="password" id="password" placeholder="password here (optional)" />
<br /> <br />
<label for="vdoroomname">Room name to use</label> <label for="vdoroomname">Room name to use</label>
<input name="vdoroomname" id="vdoroomname" placeholder="vdo room name to use (optional)" /> <input name="vdoroomname" id="vdoroomname" placeholder="vdo room name to use (optional)" />
<label for="vdopassword">Room password to use</label> <label for="vdopassword">Room password to use</label>
<input name="vdopassword" id="vdopassword" placeholder="vdo password to use (optional)" /> <input name="vdopassword" id="vdopassword" placeholder="vdo password to use (optional)" />
<br /> <br />
<button id="address_button">Connect</button> <button id="address_button">Connect</button>
<button id="address_button_2">Connect and share OBS Output</button> <button id="address_button_2">Connect and share OBS Output</button>
</span> </span>
<a id="client" target="_blank"></a> <a id="client" target="_blank"></a>
<div id="info"></div> <div id="info"></div>
<small>Code available on GitHub: <a target="_blank" href='https://github.com/steveseguin/remote_ninja'>https://github.com/steveseguin/remote_ninja</a></small> <small>Code available on GitHub: <a target="_blank" href='https://github.com/steveseguin/remote_ninja'>https://github.com/steveseguin/remote_ninja</a></small>
</div> </div>
<script> <script>
var hostname = "vdo.ninja"; // all that's supported as of this moment. var hostname = "vdo.ninja"; // all that's supported as of this moment.
const obs = new OBSWebSocket(); const obs = new OBSWebSocket();
var scenesData = {}; var scenesData = {};
scenesData.scenes = []; scenesData.scenes = [];
function sendToOBS(action, data={}){ function sendToOBS(action, data={}){
document.getElementById("info").innerHTML = action + "<br />"+document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = action + "<br />"+document.getElementById("info").innerHTML;
obs.sendCallback(action, data, sendCallback) obs.sendCallback(action, data, sendCallback)
} }
(function(w) { (function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) { w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this; var self = this;
searchString = searchString.replace("??", "?"); searchString = searchString.replace("??", "?");
self.searchString = searchString; self.searchString = searchString;
self.get = function(name) { self.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlEdited = window.location.search.replace(/\?\?/g, "?"); var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&"); urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?"); urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){ if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited); warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString()); window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
} }
var urlParams = new URLSearchParams(urlEdited); var urlParams = new URLSearchParams(urlEdited);
var roomname = Math.floor(Math.random() * 1000000); var roomname = Math.floor(Math.random() * 1000000);
var pwurl = Math.floor(Math.random() * 1000000); var pwurl = Math.floor(Math.random() * 1000000);
if (urlParams.get("password")){ if (urlParams.get("password")){
pwurl = urlParams.get("password"); pwurl = urlParams.get("password");
localStorage.setItem('password',pwurl) localStorage.setItem('password',pwurl)
} else if (localStorage.getItem('password')){ } else if (localStorage.getItem('password')){
pwurl = localStorage.getItem('password'); pwurl = localStorage.getItem('password');
} else { } else {
localStorage.setItem('password',pwurl) localStorage.setItem('password',pwurl)
} }
if (urlParams.get("room")){ if (urlParams.get("room")){
roomname = urlParams.get("room") roomname = urlParams.get("room")
localStorage.setItem('roomname',roomname) localStorage.setItem('roomname',roomname)
} else if (localStorage.getItem('roomname')){ } else if (localStorage.getItem('roomname')){
roomname = localStorage.getItem('roomname'); roomname = localStorage.getItem('roomname');
} else { } else {
localStorage.setItem('roomname',roomname) localStorage.setItem('roomname',roomname)
} }
document.getElementById('vdoroomname').value = roomname; document.getElementById('vdoroomname').value = roomname;
document.getElementById('vdopassword').value = pwurl; document.getElementById('vdopassword').value = pwurl;
if (localStorage.getItem('address')){ if (localStorage.getItem('address')){
document.getElementById('address').value = localStorage.getItem('address'); document.getElementById('address').value = localStorage.getItem('address');
} }
if (localStorage.getItem('wspass')){ if (localStorage.getItem('wspass')){
document.getElementById('password').value = localStorage.getItem('wspass'); document.getElementById('password').value = localStorage.getItem('wspass');
} }
var iframe = null; var iframe = null;
function createIFrame(visible=true){ function createIFrame(visible=true){
iframe = document.createElement("iframe"); iframe = document.createElement("iframe");
if (visible){ if (visible){
iframe.src = "https://"+hostname+"/?room="+roomname+"&push=mainOBSOutput&od=0&transparent&webcam&vd=obs&view&password="+pwurl+"&label=OBS_"+Math.floor(Math.random() * 1000000); iframe.src = "https://"+hostname+"/?room="+roomname+"&push=mainOBSOutput&od=0&transparent&webcam&vd=obs&view&password="+pwurl+"&label=OBS_"+Math.floor(Math.random() * 1000000);
iframe.style.minWidth = "720px"; iframe.style.minWidth = "720px";
iframe.style.minHeight = "480px"; iframe.style.minHeight = "480px";
iframe.style.maxWidth = "50%"; iframe.style.maxWidth = "50%";
iframe.style.maxHeight = "50%"; iframe.style.maxHeight = "50%";
iframe.style.display = "block"; iframe.style.display = "block";
iframe.style.margin = "auto"; iframe.style.margin = "auto";
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
} else { } else {
iframe.src = "https://"+hostname+"/?room="+roomname+"&push&autostart&vd=0&view&ad=0&transparent&cleanoutput&password="+pwurl+"&label=OBS_"+Math.floor(Math.random() * 1000000); iframe.src = "https://"+hostname+"/?room="+roomname+"&push&autostart&vd=0&view&ad=0&transparent&cleanoutput&password="+pwurl+"&label=OBS_"+Math.floor(Math.random() * 1000000);
iframe.style.opacity = 0; iframe.style.opacity = 0;
iframe.style.width = 0; iframe.style.width = 0;
iframe.style.height = 0; iframe.style.height = 0;
iframe.style.position = "absolulte"; iframe.style.position = "absolulte";
iframe.style.top = "-100px"; iframe.style.top = "-100px";
iframe.style.left = "-100px"; iframe.style.left = "-100px";
} }
document.getElementById("client").parentNode.insertBefore(iframe, document.getElementById("client").nextSibling); document.getElementById("client").parentNode.insertBefore(iframe, document.getElementById("client").nextSibling);
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.log(e); console.log(e);
if ("dataReceived" in e.data){ if ("dataReceived" in e.data){
if ("sendToOBS" in e.data.dataReceived){ if ("sendToOBS" in e.data.dataReceived){
if ("action" in e.data.dataReceived.sendToOBS){ if ("action" in e.data.dataReceived.sendToOBS){
if ("data" in e.data.dataReceived.sendToOBS){ if ("data" in e.data.dataReceived.sendToOBS){
sendToOBS(e.data.dataReceived.sendToOBS.action, e.data.dataReceived.sendToOBS.data); sendToOBS(e.data.dataReceived.sendToOBS.action, e.data.dataReceived.sendToOBS.data);
} }
} }
} }
} else if ("action" in e.data){ } else if ("action" in e.data){
if (e.data.action === "new-push-connection"){ if (e.data.action === "new-push-connection"){
console.log(e.data); console.log(e.data);
updateSceneList(); updateSceneList();
} }
} }
}); });
} }
function sendCallback(err, data){ function sendCallback(err, data){
console.log("CALLBACK TRIGGERED"); console.log("CALLBACK TRIGGERED");
var msg = {}; var msg = {};
msg.sentFromOBS = {}; msg.sentFromOBS = {};
msg.sentFromOBS.callbackData = data; msg.sentFromOBS.callbackData = data;
msg.sentFromOBS.callbackError = err; msg.sentFromOBS.callbackError = err;
try { try {
iframe.contentWindow.postMessage({"sendData":msg}, '*'); iframe.contentWindow.postMessage({"sendData":msg}, '*');
} catch(e){} } catch(e){}
} }
function sendRawData(data){ function sendRawData(data){
var msg = {}; var msg = {};
msg.sentFromOBS = {}; msg.sentFromOBS = {};
msg.sentFromOBS.rawData = data; msg.sentFromOBS.rawData = data;
try { try {
iframe.contentWindow.postMessage({"sendData":msg}, '*'); iframe.contentWindow.postMessage({"sendData":msg}, '*');
} catch(e){} } catch(e){}
} }
function heartBeat(){ function heartBeat(){
obs.send("GetStats"); obs.send("GetStats");
} }
function updateSceneList(){ function updateSceneList(){
var msg = {}; var msg = {};
msg.sentFromOBS = {}; msg.sentFromOBS = {};
msg.sentFromOBS.scenes = scenesData; msg.sentFromOBS.scenes = scenesData;
try { try {
iframe.contentWindow.postMessage({"sendData":msg}, '*'); iframe.contentWindow.postMessage({"sendData":msg}, '*');
} catch(e){} } catch(e){}
console.log(msg); console.log(msg);
obs.send("GetSourcesList"); obs.send("GetSourcesList");
obs.send('GetCurrentScene'); obs.send('GetCurrentScene');
obs.send("GetVideoInfo"); obs.send("GetVideoInfo");
obs.send("ListOutputs"); obs.send("ListOutputs");
} }
document.getElementById('address_button').addEventListener('click', e => { document.getElementById('address_button').addEventListener('click', e => {
connect(e, false); connect(e, false);
}); });
document.getElementById('address_button_2').addEventListener('click', e => { document.getElementById('address_button_2').addEventListener('click', e => {
connect(e, true); connect(e, true);
}); });
var heartBeatInterval = null; var heartBeatInterval = null;
function connect(e, camera){ function connect(e, camera){
const address = document.getElementById('address').value || "localhost:4444"; const address = document.getElementById('address').value || "localhost:4444";
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
roomname = document.getElementById('vdoroomname').value || Math.floor(Math.random() * 1000000); roomname = document.getElementById('vdoroomname').value || Math.floor(Math.random() * 1000000);
pwurl = document.getElementById('vdopassword').value || Math.floor(Math.random() * 1000000); pwurl = document.getElementById('vdopassword').value || Math.floor(Math.random() * 1000000);
localStorage.setItem('roomname',roomname) localStorage.setItem('roomname',roomname)
localStorage.setItem('password',pwurl) localStorage.setItem('password',pwurl)
createIFrame(camera); // connects to VDO.Ninja's IFRAME API createIFrame(camera); // connects to VDO.Ninja's IFRAME API
localStorage.setItem('address',address); localStorage.setItem('address',address);
if (password){ if (password){
localStorage.setItem('wspass',password); localStorage.setItem('wspass',password);
var ret = obs.connect({ var ret = obs.connect({
address: address, address: address,
password: password password: password
}); });
} else { } else {
var ret = obs.connect({ var ret = obs.connect({
address: address address: address
}); });
} }
ret.then(() => { ret.then(() => {
console.log(`Success!`); console.log(`Success!`);
return obs.send('GetSceneList'); return obs.send('GetSceneList');
}).then(data => { }).then(data => {
document.getElementById("setup").style.display = "none"; document.getElementById("setup").style.display = "none";
scenesData = data; scenesData = data;
updateSceneList(); updateSceneList();
var pathname = window.location.pathname.split("/"); var pathname = window.location.pathname.split("/");
pathname.pop(); pathname.pop();
pathname = pathname.join("/"); pathname = pathname.join("/");
var clientLink = window.location.protocol + "//" + window.location.host + pathname + "/interface.html?room="+roomname+"&password="+pwurl; var clientLink = window.location.protocol + "//" + window.location.host + pathname + "/interface.html?room="+roomname+"&password="+pwurl;
document.getElementById("client").href = clientLink; document.getElementById("client").href = clientLink;
document.getElementById("client").innerHTML = "<b><font style='color:#70c4ff;'>client link:</font></b> "+clientLink; document.getElementById("client").innerHTML = "<b><font style='color:#70c4ff;'>client link:</font></b> "+clientLink;
document.getElementById("info").innerHTML = "<br /><p style='color:#bdffbd;'>Connection to OBS websockets opened.</p>" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br /><p style='color:#bdffbd;'>Connection to OBS websockets opened.</p>" + document.getElementById("info").innerHTML;
try { try {
obs._socket.onmessage2 = obs._socket.onmessage; // hijacking the obs-websocket.js framework obs._socket.onmessage2 = obs._socket.onmessage; // hijacking the obs-websocket.js framework
obs._socket.onmessage = function(data){ obs._socket.onmessage = function(data){
console.log(data); console.log(data);
obs._socket.onmessage2(data); obs._socket.onmessage2(data);
if (data.type && data.data){ if (data.type && data.data){
if (data.type == "message"){ if (data.type == "message"){
sendRawData(data.data); sendRawData(data.data);
} }
} }
} }
} catch(e){console.error(e);} } catch(e){console.error(e);}
clearInterval(heartBeatInterval); clearInterval(heartBeatInterval);
heartBeatInterval = setInterval(function(){heartBeat();},3000); heartBeatInterval = setInterval(function(){heartBeat();},3000);
}).catch(err => { // Promise convention dicates you have a catch on every chain. }).catch(err => { // Promise convention dicates you have a catch on every chain.
console.log(err); console.log(err);
document.getElementById("info").innerHTML = "<br />Error trying to connect. Is SSL enabled on your domain?" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />Error trying to connect. Is SSL enabled on your domain?" + document.getElementById("info").innerHTML;
if ("error" in err){ if ("error" in err){
document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML;
} }
}); });
}; };
// We use the source visibility one and filter visibility web socket commands quite often in shows. // We use the source visibility one and filter visibility web socket commands quite often in shows.
obs.on('SwitchScenes', data => { obs.on('SwitchScenes', data => {
console.log(`New Active Scene: ${data.sceneName}`); console.log(`New Active Scene: ${data.sceneName}`);
scenesData.currentScene = data.sceneName scenesData.currentScene = data.sceneName
updateSceneList(); updateSceneList();
}); });
obs.on('ConnectionOpened', (data) => function(){ obs.on('ConnectionOpened', (data) => function(){
document.getElementById("setup").style.display = "none"; document.getElementById("setup").style.display = "none";
document.getElementById("info").innerHTML = "<br /><p style='color:#bdffbd;'>Connection to OBS websockets opened.</p>" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br /><p style='color:#bdffbd;'>Connection to OBS websockets opened.</p>" + document.getElementById("info").innerHTML;
}); });
obs.on('ConnectionClosed', (data) => function(){ obs.on('ConnectionClosed', (data) => function(){
document.getElementById("setup").style.display = "unset"; document.getElementById("setup").style.display = "unset";
document.getElementById("info").innerHTML = "<br />Connection to OBS websockets closed" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />Connection to OBS websockets closed" + document.getElementById("info").innerHTML;
}); });
obs.on('AuthenticationSuccess', (data) => function(){ obs.on('AuthenticationSuccess', (data) => function(){
document.getElementById("setup").style.display = "none"; document.getElementById("setup").style.display = "none";
document.getElementById("info").innerHTML = "<br />OBS websockets authenticated" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />OBS websockets authenticated" + document.getElementById("info").innerHTML;
}); });
obs.on('AuthenticationFailure', (data) => function(){ obs.on('AuthenticationFailure', (data) => function(){
document.getElementById("setup").style.display = "unset"; document.getElementById("setup").style.display = "unset";
document.getElementById("info").innerHTML = "<br />Authentication to OBS websockets failed" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />Authentication to OBS websockets failed" + document.getElementById("info").innerHTML;
}); });
obs.on('ScenesChanged', (data) => function(){ obs.on('ScenesChanged', (data) => function(){
scenesData = data; scenesData = data;
updateSceneList() updateSceneList()
}); });
// You must add this handler to avoid uncaught exceptions. // You must add this handler to avoid uncaught exceptions.
obs.on('error', err => { obs.on('error', err => {
document.getElementById("info").innerHTML = "<br />OBS websockets error" + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />OBS websockets error" + document.getElementById("info").innerHTML;
console.error('socket error:', err); console.error('socket error:', err);
if ("error" in err){ if ("error" in err){
document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML; document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML;
} }
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,474 +1,474 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script type="text/javascript" src="./thirdparty/obs-websocket.min.js"></script> <script type="text/javascript" src="thirdparty/obs-websocket.min.js"></script>
<link rel="stylesheet" href="https://vdo.ninja/main.css" /> <link rel="stylesheet" href="https://vdo.ninja/main.css" />
<style> <style>
.container { .container {
max-width: 80%; max-width: 80%;
width: fit-content; width: fit-content;
margin: 0 auto; margin: 0 auto;
} }
h1 { h1 {
margin-top: 1em; margin-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
padding: 10px; padding: 10px;
} }
.card { .card {
margin: 10px; margin: 10px;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%); box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
background-color: #ddd; background-color: #ddd;
color: black; color: black;
margin-bottom: 1.5em; margin-bottom: 1.5em;
} }
.card>div { .card>div {
padding: 10px; padding: 10px;
} }
.card h2 { .card h2 {
font-size: 1.5em; font-size: 1.5em;
padding: 10px; padding: 10px;
background-color: #457b9d; background-color: #457b9d;
color: white; color: white;
border-bottom: 2px solid #3b6a87; border-bottom: 2px solid #3b6a87;
} }
small { small {
font-style: italic; font-style: italic;
display: block; display: block;
margin-left: 1em; margin-left: 1em;
} }
span.warning { span.warning {
color: rgb(212, 191, 0); color: rgb(212, 191, 0);
} }
input { input {
padding: 10px; padding: 10px;
display: inline-block; display: inline-block;
flex-flow: unset; flex-flow: unset;
margin: 10px; margin: 10px;
} }
video { video {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
audio { audio {
max-width: 640px; max-width: 640px;
max-height: 360px; max-height: 360px;
padding: 20px; padding: 20px;
} }
div#processing { div#processing {
display: none; display: none;
justify-content: center; justify-content: center;
place-items: center; place-items: center;
position: absolute; position: absolute;
inset: 0; inset: 0;
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
background: #141926; background: #141926;
flex-direction: column; flex-direction: column;
} }
button { button {
margin:5px; margin:5px;
border:solid black 2px; border:solid black 2px;
} }
body { body {
color:white; color:white;
display: inline-block; display: inline-block;
flex-flow: unset; flex-flow: unset;
} }
a { a {
color: #225273!important; color: #225273!important;
} }
.hidden{ .hidden{
display:none !important; display:none !important;
} }
.stat { .stat {
background-color: black; background-color: black;
margin: 7px; margin: 7px;
padding: 5px; padding: 5px;
} }
.stat:nth-child(2n) { .stat:nth-child(2n) {
background-color: #333; background-color: #333;
} }
.stat:empty { .stat:empty {
display:none; display:none;
} }
</style> </style>
<title>OBS Controller Demo using VDO.NInja</title> <title>OBS Controller Demo using VDO.NInja</title>
</head> </head>
<div class="container"> <div class="container">
<h1>OBS remote (client)</h1> <h1>OBS remote (client)</h1>
<div id="info"> <div id="info">
<div class="card"> <div class="card">
<h2>Scenes</h2> <h2>Scenes</h2>
<div id="scene_list"> <div id="scene_list">
</div> </div>
</div> </div>
<div class="card hidden"> <div class="card hidden">
<h2>General</h2> <h2>General</h2>
<div> <div>
<button onclick="basicCommand(this);" data-command="GetVersion">GetVersion</button> <button onclick="basicCommand(this);" data-command="GetVersion">GetVersion</button>
<button onclick="basicCommand(this);" data-command="GetStats">GetStats</button> <button onclick="basicCommand(this);" data-command="GetStats">GetStats</button>
<button onclick="basicCommand(this);" data-command="GetVideoInfo">GetVideoInfo</button> <button onclick="basicCommand(this);" data-command="GetVideoInfo">GetVideoInfo</button>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Output</h2> <h2>Output</h2>
<div id="outputs"> <div id="outputs">
<button onclick="basicCommand(this);" data-command="ListOutputs">ListOutputs</button> <button onclick="basicCommand(this);" data-command="ListOutputs">ListOutputs</button>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Active Sources</h2> <h2>Active Sources</h2>
<div id="active_source_list"> <div id="active_source_list">
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>All Sources</h2> <h2>All Sources</h2>
<div id="source_list"> <div id="source_list">
</div> </div>
</div> </div>
<div class="card hidden"> <div class="card hidden">
<h2>Source debug</h2> <h2>Source debug</h2>
<div> <div>
<button onclick="basicCommand(this);" data-command="GetMediaSourcesList">GetMediaSourcesList</button> <button onclick="basicCommand(this);" data-command="GetMediaSourcesList">GetMediaSourcesList</button>
<button onclick="basicCommand(this);" data-command="GetSourcesList">GetSourcesList</button> <button onclick="basicCommand(this);" data-command="GetSourcesList">GetSourcesList</button>
<button onclick="basicCommand(this);" data-command="GetSourceActive">GetSourceActive</button> <button onclick="basicCommand(this);" data-command="GetSourceActive">GetSourceActive</button>
<button onclick="basicCommand(this);" data-command="GetAudioActive">GetAudioActive</button> <button onclick="basicCommand(this);" data-command="GetAudioActive">GetAudioActive</button>
</div> </div>
</div> </div>
<div id="audio" class="card hidden"> <div id="audio" class="card hidden">
<h2>Audio</h2> <h2>Audio</h2>
<div> <div>
<button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="GetVolume">GetVolume</button> <button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="GetVolume">GetVolume</button>
<input type='range' min="0" max="100" value="100" onchange="basicCommand(this, {source:selectedSource, volume:parseInt(this.value)/100});" data-command="SetVolume" /> <input type='range' min="0" max="100" value="100" onchange="basicCommand(this, {source:selectedSource, volume:parseInt(this.value)/100});" data-command="SetVolume" />
<button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="ToggleMute">ToggleMute</button> <button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="ToggleMute">ToggleMute</button>
</div> </div>
</div> </div>
<div class="card" id='commands'> <div class="card" id='commands'>
<h2>Custom Commands</h2> <h2>Custom Commands</h2>
<input id="newCommand" placeholder="enter a custom command" type='text' /><button id="goCreate" onclick="createCommand()">Create</button> <input id="newCommand" placeholder="enter a custom command" type='text' /><button id="goCreate" onclick="createCommand()">Create</button>
<br /> <br />
A list of possible commands <a href="https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md#requests">available here:</a><br /> A list of possible commands <a href="https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md#requests">available here:</a><br />
</div> </div>
</div> </div>
<div style="width:100%;display:block;margin:0;padding:0;"> <div style="width:100%;display:block;margin:0;padding:0;">
<div id='OBSstats' style="width:49%;display:inline-block;vertical-align: top;"> <div id='OBSstats' style="width:49%;display:inline-block;vertical-align: top;">
<div id="stat-current-profile" class='stat'></div> <div id="stat-current-profile" class='stat'></div>
<div id="stat-current-scene" class='stat'></div> <div id="stat-current-scene" class='stat'></div>
<div id="stat-streaming" class='stat'></div> <div id="stat-streaming" class='stat'></div>
<div id="stat-memory-usage" class='stat'></div> <div id="stat-memory-usage" class='stat'></div>
<div id="stat-recording" class='stat'></div> <div id="stat-recording" class='stat'></div>
<div id="stat-recording-paused" class='stat'></div> <div id="stat-recording-paused" class='stat'></div>
<div id="stat-average-frame-time" class='stat'></div> <div id="stat-average-frame-time" class='stat'></div>
<div id="stat-cpu-usage" class='stat'></div> <div id="stat-cpu-usage" class='stat'></div>
<div id="stat-fps" class='stat'></div> <div id="stat-fps" class='stat'></div>
<div id="stat-free-disk-space" class='stat'></div> <div id="stat-free-disk-space" class='stat'></div>
</div> </div>
<div id='OBSsettings' style="width:49%;display:inline-block;vertical-align: top;"> <div id='OBSsettings' style="width:49%;display:inline-block;vertical-align: top;">
<div id="setting-baseHeight" class='stat'></div> <div id="setting-baseHeight" class='stat'></div>
<div id="setting-baseWidth" class='stat'></div> <div id="setting-baseWidth" class='stat'></div>
<div id="setting-outputHeight" class='stat'></div> <div id="setting-outputHeight" class='stat'></div>
<div id="setting-outputWidth" class='stat'></div> <div id="setting-outputWidth" class='stat'></div>
<div id="setting-scaleType" class='stat'></div> <div id="setting-scaleType" class='stat'></div>
<div id="setting-fps" class='stat'></div> <div id="setting-fps" class='stat'></div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function basicCommand(ele, data={}){ function basicCommand(ele, data={}){
sendToOBS(ele.dataset.command, data); sendToOBS(ele.dataset.command, data);
} }
function createCommand(){ function createCommand(){
var command = document.getElementById("newCommand").value; var command = document.getElementById("newCommand").value;
document.getElementById("newCommand").value = ""; document.getElementById("newCommand").value = "";
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = command; button.innerText = command;
button.dataset.command = command; button.dataset.command = command;
button.onclick = function(){basicCommand(this);}; button.onclick = function(){basicCommand(this);};
document.getElementById("commands").appendChild(button); document.getElementById("commands").appendChild(button);
} }
(function(w) { (function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) { w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this; var self = this;
searchString = searchString.replace("??", "?"); searchString = searchString.replace("??", "?");
self.searchString = searchString; self.searchString = searchString;
self.get = function(name) { self.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlEdited = window.location.search.replace(/\?\?/g, "?"); var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&"); urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?"); urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){ if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited); warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString()); window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
} }
var urlParams = new URLSearchParams(urlEdited); var urlParams = new URLSearchParams(urlEdited);
if (urlParams.get("password")){ if (urlParams.get("password")){
var password = urlParams.get("password"); var password = urlParams.get("password");
localStorage.setItem('password',password) localStorage.setItem('password',password)
} else if (localStorage.getItem('password')){ } else if (localStorage.getItem('password')){
password = localStorage.getItem('password'); password = localStorage.getItem('password');
} }
if (urlParams.get("room")){ if (urlParams.get("room")){
var roomname = urlParams.get("room") var roomname = urlParams.get("room")
localStorage.setItem('roomname',roomname) localStorage.setItem('roomname',roomname)
} else if (localStorage.getItem('roomname')){ } else if (localStorage.getItem('roomname')){
roomname = localStorage.getItem('roomname'); roomname = localStorage.getItem('roomname');
} }
const obs = new OBSWebSocket(); const obs = new OBSWebSocket();
var scenesData = {}; var scenesData = {};
var selectedSource = null; var selectedSource = null;
scenesData.scenes = []; scenesData.scenes = [];
function sendToOBS(action, data){ function sendToOBS(action, data){
var msg = {}; var msg = {};
msg.sendToOBS = {}; msg.sendToOBS = {};
msg.sendToOBS.action = action; msg.sendToOBS.action = action;
msg.sendToOBS.data = data; msg.sendToOBS.data = data;
iframe.contentWindow.postMessage({"sendData":msg}, '*'); iframe.contentWindow.postMessage({"sendData":msg}, '*');
} }
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.src = "https://vdo.ninja/?room="+roomname+"&password="+password+"&push&novideo=mainOBSOutput&noaudio=mainOBSOutput&autostart&vd=0&ad=0&transparent&cleanoutput&label=CLIENT_"+Math.floor(Math.random() * 1000000); iframe.src = "https://vdo.ninja/?room="+roomname+"&password="+password+"&push&novideo=mainOBSOutput&noaudio=mainOBSOutput&autostart&vd=0&ad=0&transparent&cleanoutput&label=CLIENT_"+Math.floor(Math.random() * 1000000);
// iframe.style.opacity = 0; // iframe.style.opacity = 0;
iframe.style.width = "160px"; iframe.style.width = "160px";
iframe.style.height = "90px"; iframe.style.height = "90px";
iframe.style.position = "fixed"; iframe.style.position = "fixed";
iframe.style.top = "10px"; iframe.style.top = "10px";
iframe.style.right = "170px"; iframe.style.right = "170px";
document.body.appendChild(iframe) document.body.appendChild(iframe)
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("dataReceived" in e.data){ if ("dataReceived" in e.data){
if ("sentFromOBS" in e.data.dataReceived){ if ("sentFromOBS" in e.data.dataReceived){
processIncoming(e.data.dataReceived.sentFromOBS); processIncoming(e.data.dataReceived.sentFromOBS);
} }
} }
}); });
function changeScene(scene){ function changeScene(scene){
sendToOBS('SetCurrentScene', { sendToOBS('SetCurrentScene', {
'scene-name': scene 'scene-name': scene
}); });
} }
function processIncoming(data){ function processIncoming(data){
if ("scenes" in data){ if ("scenes" in data){
scenesData = data.scenes; scenesData = data.scenes;
updateSceneList(); updateSceneList();
} }
if ("callbackData" in data){ if ("callbackData" in data){
console.log(data.callbackData); console.log(data.callbackData);
} else if ("callbackError" in data){ } else if ("callbackError" in data){
console.log(data.callbackError); console.log(data.callbackError);
} }
if ("rawData" in data){ if ("rawData" in data){
var data = JSON.parse(data.rawData); var data = JSON.parse(data.rawData);
console.log(data); console.log(data);
if ("stats" in data){ if ("stats" in data){
var i = "stats"; var i = "stats";
for (var j in data[i]){ for (var j in data[i]){
if (document.getElementById("stat-"+j)){ if (document.getElementById("stat-"+j)){
if (typeof data[i][j] == "number"){ if (typeof data[i][j] == "number"){
data[i][j] = parseInt(data[i][j]*100)/100.0; data[i][j] = parseInt(data[i][j]*100)/100.0;
} }
if (data[i][j]===true){ if (data[i][j]===true){
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#CFC'>" + data[i][j]+"</font>"; document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#CFC'>" + data[i][j]+"</font>";
} else if (data[i][j] === false){ } else if (data[i][j] === false){
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#FCC'>" + data[i][j]+"</font>"; document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#FCC'>" + data[i][j]+"</font>";
} else { } else {
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#DDD'>" + data[i][j]+"</font>"; document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#DDD'>" + data[i][j]+"</font>";
} }
} }
} }
} else if ("baseHeight" in data){ } else if ("baseHeight" in data){
for (var i in data){ for (var i in data){
if (document.getElementById("setting-"+i)){ if (document.getElementById("setting-"+i)){
if (data[i]===true){ if (data[i]===true){
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#CFC'>" + data[i]+"</font>"; document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#CFC'>" + data[i]+"</font>";
} else if (data[i] === false){ } else if (data[i] === false){
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#FCC'>" + data[i]+"</font>"; document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#FCC'>" + data[i]+"</font>";
} else { } else {
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#DDD'>" + data[i]+"</font>"; document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#DDD'>" + data[i]+"</font>";
} }
} }
} }
} else if ("sources" in data){ } else if ("sources" in data){
if ("name" in data){ if ("name" in data){
document.getElementById("active_source_list").innerHTML = ""; document.getElementById("active_source_list").innerHTML = "";
for (var i =0;i<data.sources.length;i++){ for (var i =0;i<data.sources.length;i++){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = data.sources[i].name; button.innerText = data.sources[i].name;
button.dataset.name = data.sources[i].name; button.dataset.name = data.sources[i].name;
button.dataset.type = "source" button.dataset.type = "source"
button.onclick = function(){ button.onclick = function(){
console.log("CLICKED: " + this.innerText); console.log("CLICKED: " + this.innerText);
selectedSource = this.dataset.name; selectedSource = this.dataset.name;
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
var sources = document.querySelectorAll("[data-name"); var sources = document.querySelectorAll("[data-name");
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.remove("pressed"); sources[k].classList.remove("pressed");
} }
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']"); var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
console.log(sources); console.log(sources);
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
document.getElementById("audio").classList.remove("hidden"); document.getElementById("audio").classList.remove("hidden");
sources[k].classList.add("pressed"); sources[k].classList.add("pressed");
} }
}; };
document.getElementById("active_source_list").appendChild(button); document.getElementById("active_source_list").appendChild(button);
} }
if (selectedSource){ if (selectedSource){
var sources = document.querySelectorAll("[data-name"); var sources = document.querySelectorAll("[data-name");
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.remove("pressed"); sources[k].classList.remove("pressed");
} }
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']"); var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
console.log(sources); console.log(sources);
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.add("pressed"); sources[k].classList.add("pressed");
document.getElementById("audio").classList.remove("hidden"); document.getElementById("audio").classList.remove("hidden");
} }
} else { } else {
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
} }
} else { } else {
document.getElementById("source_list").innerHTML = ""; document.getElementById("source_list").innerHTML = "";
for (var i =0;i<data.sources.length;i++){ for (var i =0;i<data.sources.length;i++){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = data.sources[i].name; button.innerText = data.sources[i].name;
button.dataset.name = data.sources[i].name; button.dataset.name = data.sources[i].name;
button.dataset.type = "source" button.dataset.type = "source"
button.onclick = function(){ button.onclick = function(){
console.log("CLICKED: " + this.innerText); console.log("CLICKED: " + this.innerText);
selectedSource = this.dataset.name; selectedSource = this.dataset.name;
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
var sources = document.querySelectorAll("[data-name"); var sources = document.querySelectorAll("[data-name");
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.remove("pressed"); sources[k].classList.remove("pressed");
} }
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']"); var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
console.log(sources); console.log(sources);
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.add("pressed"); sources[k].classList.add("pressed");
document.getElementById("audio").classList.remove("hidden"); document.getElementById("audio").classList.remove("hidden");
} }
}; };
document.getElementById("source_list").appendChild(button); document.getElementById("source_list").appendChild(button);
} }
if (selectedSource){ if (selectedSource){
var sources = document.querySelectorAll("[data-name"); var sources = document.querySelectorAll("[data-name");
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.remove("pressed"); sources[k].classList.remove("pressed");
} }
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']"); var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
console.log(sources); console.log(sources);
for (var k = 0 ; k<sources.length; k++){ for (var k = 0 ; k<sources.length; k++){
sources[k].classList.add("pressed"); sources[k].classList.add("pressed");
document.getElementById("audio").classList.remove("hidden"); document.getElementById("audio").classList.remove("hidden");
} }
} else { } else {
document.getElementById("audio").classList.add("hidden"); document.getElementById("audio").classList.add("hidden");
} }
} }
<!-- congestion: 0 --> <!-- congestion: 0 -->
<!-- droppedFrames: 0 --> <!-- droppedFrames: 0 -->
<!-- flags: {audio: true, encoded: true, multiTrack: true, rawValue: 31, service: true, …} --> <!-- flags: {audio: true, encoded: true, multiTrack: true, rawValue: 31, service: true, …} -->
<!-- height: 1080 --> <!-- height: 1080 -->
<!-- name: "simple_stream" --> <!-- name: "simple_stream" -->
<!-- reconnecting: false --> <!-- reconnecting: false -->
<!-- settings: {bind_ip: 'default', dyn_bitrate: false, low_latency_mode_enabled: false, new_socket_loop_enabled: false} --> <!-- settings: {bind_ip: 'default', dyn_bitrate: false, low_latency_mode_enabled: false, new_socket_loop_enabled: false} -->
<!-- totalBytes: 351121 --> <!-- totalBytes: 351121 -->
<!-- totalFrames: 30 --> <!-- totalFrames: 30 -->
<!-- type: "rtmp_output" --> <!-- type: "rtmp_output" -->
<!-- width: 1920 --> <!-- width: 1920 -->
} else if ("outputs" in data){ } else if ("outputs" in data){
document.getElementById("outputs").innerHTML = ""; document.getElementById("outputs").innerHTML = "";
for (var i =0;i<data.outputs.length;i++){ for (var i =0;i<data.outputs.length;i++){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = data.outputs[i].name; button.innerText = data.outputs[i].name;
button.dataset.output = data.outputs[i].name; button.dataset.output = data.outputs[i].name;
button.onclick = function(){ button.onclick = function(){
console.log("CLICKED: " + this.innerText); console.log("CLICKED: " + this.innerText);
var outputName = this.dataset.output; var outputName = this.dataset.output;
if (this.classList.contains("pressed")){ if (this.classList.contains("pressed")){
this.classList.remove("pressed"); this.classList.remove("pressed");
sendToOBS("StopOutput",{outputName:outputName}); sendToOBS("StopOutput",{outputName:outputName});
} else { } else {
this.classList.add("pressed"); this.classList.add("pressed");
sendToOBS("StartOutput",{outputName:outputName}); sendToOBS("StartOutput",{outputName:outputName});
} }
}; };
document.getElementById("outputs").appendChild(button); document.getElementById("outputs").appendChild(button);
} }
} }
} }
} }
function updateSceneList(){ function updateSceneList(){
var scenes = scenesData.scenes; var scenes = scenesData.scenes;
document.getElementById("scene_list").innerHTML = ""; document.getElementById("scene_list").innerHTML = "";
scenes.forEach(scene => { scenes.forEach(scene => {
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = scene.name; button.innerText = scene.name;
button.onclick = function(){ button.onclick = function(){
console.log("CLICKED"); console.log("CLICKED");
changeScene(this.innerText); changeScene(this.innerText);
}; // "speaker" also works in the same way. }; // "speaker" also works in the same way.
document.getElementById("scene_list").appendChild(button); document.getElementById("scene_list").appendChild(button);
if (scene.name === scenesData.currentScene) { if (scene.name === scenesData.currentScene) {
button.classList.add("pressed"); button.classList.add("pressed");
} }
}); });
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,146 +1,146 @@
<html> <html>
<head><title>overlay + Video</title> <head><title>overlay + Video</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" /> <meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
color:white; color:white;
} }
iframe { iframe {
width:100vw; width:100vw;
height:100vh; height:100vh;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
position:fixed; position:fixed;
top:0; top:0;
left:0 left:0
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
} }
h1{ h1{
color: white; color: white;
font-family: verdana; font-family: verdana;
margin: 10px; margin: 10px;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="clean"> <div id="clean">
<h1>Apply an Overlay to VDO.Ninja</h1> <h1>Apply an Overlay to VDO.Ninja</h1>
<input placeholder="Enter a VDON URL here" id="viewlink" type="text" /> <input placeholder="Enter a VDON URL here" id="viewlink" type="text" />
<input placeholder="Enter the Overlay page here" id="overlay" type="text" /> <input placeholder="Enter the Overlay page here" id="overlay" type="text" />
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>(Leave blank and press start to see a default sample result) <button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>(Leave blank and press start to see a default sample result)
</div> </div>
<script> <script>
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Announce the new orientation number // Announce the new orientation number
// alert(window.orientation); // alert(window.orientation);
}, false); }, false);
function removeStorage(cname){ function removeStorage(cname){
localStorage.removeItem(cname); localStorage.removeItem(cname);
} }
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date(); var now = new Date();
var item = { var item = {
value: cvalue, value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000), expiry: now.getTime() + (hours * 60 * 60 * 1000),
}; };
try{ try{
localStorage.setItem(cname, JSON.stringify(item)); localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);} }catch(e){errorlog(e);}
} }
function getStorage(cname) { function getStorage(cname) {
try { try {
var itemStr = localStorage.getItem(cname); var itemStr = localStorage.getItem(cname);
} catch(e){ } catch(e){
errorlog(e); errorlog(e);
return; return;
} }
if (!itemStr) { if (!itemStr) {
return ""; return "";
} }
var item = JSON.parse(itemStr); var item = JSON.parse(itemStr);
var now = new Date(); var now = new Date();
if (now.getTime() > item.expiry) { if (now.getTime() > item.expiry) {
localStorage.removeItem(cname); localStorage.removeItem(cname);
return ""; return "";
} }
return item.value; return item.value;
} }
if (getStorage("overlayChatLink")){ if (getStorage("overlayChatLink")){
document.getElementById("overlay").value = getStorage("overlayChatLink"); document.getElementById("overlay").value = getStorage("overlayChatLink");
} }
if (getStorage("vdoNinjaoverlayURL")){ if (getStorage("vdoNinjaoverlayURL")){
document.getElementById("viewlink").value = getStorage("vdoNinjaoverlayURL"); document.getElementById("viewlink").value = getStorage("vdoNinjaoverlayURL");
} }
function loadIframes(url=false){ function loadIframes(url=false){
var roomname = document.getElementById("viewlink").value; var roomname = document.getElementById("viewlink").value;
var overlay = document.getElementById("overlay").value; var overlay = document.getElementById("overlay").value;
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean")); document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
if (!roomname){ if (!roomname){
var room1 = "../"; var room1 = "../";
} else if (roomname.startsWith("https://")){ } else if (roomname.startsWith("https://")){
var room1 = roomname; var room1 = roomname;
} else { } else {
var room1 = "https://"+roomname; var room1 = "https://"+roomname;
} }
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = room1; iframe.src = room1;
document.body.appendChild(iframe); document.body.appendChild(iframe);
if (!overlay){ if (!overlay){
var room2 = "./test_overlay"; var room2 = "./test_overlay";
} else if (overlay.startsWith("https://")){ } else if (overlay.startsWith("https://")){
var room2 = overlay; var room2 = overlay;
} else { } else {
var room2 = "https://"+overlay; var room2 = "https://"+overlay;
} }
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = room2; iframe.src = room2;
iframe.style.pointerEvents = "none"; iframe.style.pointerEvents = "none";
iframe.style.backgroundColor = "#0000"; iframe.style.backgroundColor = "#0000";
iframe.style.width = "25vw"; iframe.style.width = "25vw";
iframe.style.height = "25vh"; iframe.style.height = "25vh";
iframe.style.overflow = "hidden"; iframe.style.overflow = "hidden";
document.body.appendChild(iframe); document.body.appendChild(iframe);
if (roomname && overlay){ if (roomname && overlay){
setStorage("overlayChatLink", room2); setStorage("overlayChatLink", room2);
setStorage("vdoNinjaoverlayURL", room1); setStorage("vdoNinjaoverlayURL", room1);
} }
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,69 +1,69 @@
<html> <html>
<body> <body>
<div id="results" style="overflow:scroll;max-height:300px;"> <div id="results" style="overflow:scroll;max-height:300px;">
starting... starting...
</div> </div>
<script> // https://jsfiddle.net/steveseguin/0t3ayvk8/31/ <script> // https://jsfiddle.net/steveseguin/0t3ayvk8/31/
var connectionID = Math.random()*100000001; var connectionID = Math.random()*100000001;
function RecvDataWindow(){ function RecvDataWindow(){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.src = "https://vdo.ninja/beta/?view="+connectionID+"&cleanoutput"; iframe.src = "https://vdo.ninja/beta/?view="+connectionID+"&cleanoutput";
iframe.style.width = "0px"; iframe.style.width = "0px";
iframe.style.height = "0px"; iframe.style.height = "0px";
iframe.style.position = "fixed"; iframe.style.position = "fixed";
iframe.style.left = "-100px"; iframe.style.left = "-100px";
iframe.style.top = "-100px"; iframe.style.top = "-100px";
iframe.id = "frame1" iframe.id = "frame1"
document.body.appendChild(iframe); document.body.appendChild(iframe);
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("dataReceived" in e.data){ // raw data if ("dataReceived" in e.data){ // raw data
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />"; document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
console.log(e.data); console.log(e.data);
try { try {
iframe.contentWindow.postMessage({"sendData":"pong!!", "UUID":e.data.UUID}, '*'); iframe.contentWindow.postMessage({"sendData":"pong!!", "UUID":e.data.UUID}, '*');
} catch(E){} } catch(E){}
} }
}); });
} }
function SendDataWindow(){ function SendDataWindow(){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.src = "https://vdo.ninja/beta/?push="+connectionID+"&vd=0&ad=0&autostart&cleanoutput"; iframe.src = "https://vdo.ninja/beta/?push="+connectionID+"&vd=0&ad=0&autostart&cleanoutput";
iframe.style.width = "0px"; iframe.style.width = "0px";
iframe.style.height = "0px"; iframe.style.height = "0px";
iframe.style.position = "fixed"; iframe.style.position = "fixed";
iframe.style.left = "-100px"; iframe.style.left = "-100px";
iframe.style.top = "-100px"; iframe.style.top = "-100px";
iframe.id = "frame2"; iframe.id = "frame2";
document.body.appendChild(iframe); document.body.appendChild(iframe);
setInterval(function(){ setInterval(function(){
try { try {
console.log("."); console.log(".");
document.getElementById("frame2").contentWindow.postMessage({"sendData":"ping!!"}, '*'); document.getElementById("frame2").contentWindow.postMessage({"sendData":"ping!!"}, '*');
} catch(E){} } catch(E){}
}, 1000); }, 1000);
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("dataReceived" in e.data){ // raw data if ("dataReceived" in e.data){ // raw data
console.log(e.data); console.log(e.data);
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />"; document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
} }
}); });
} }
SendDataWindow(); SendDataWindow();
RecvDataWindow(); RecvDataWindow();
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,136 +1,136 @@
<html> <html>
<head><title>PowerPoint Remote Controller</title> <head><title>PowerPoint Remote Controller</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
color:white; color:white;
font-family: tahoma, arial; font-family: tahoma, arial;
} }
a { a {
color:white color:white
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
} }
div{ div{
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
text-align: center; text-align: center;
} }
button{ button{
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
width:49%; width:49%;
height:100%; height:100%;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container1" style="width:100%;height:89%;display:none;"></div> <div id="container1" style="width:100%;height:89%;display:none;"></div>
<div id="container2" style="width:100%;height:10%;display:none;"> <div id="container2" style="width:100%;height:10%;display:none;">
<button onclick="prevSlide()" style='background-color:red'>Previous Slide</button> <button onclick="prevSlide()" style='background-color:red'>Previous Slide</button>
<button onclick="nextSlide()" style='background-color:green'>Next Slide</button> <button onclick="nextSlide()" style='background-color:green'>Next Slide</button>
</div> </div>
<div> <div>
<h2>PowerPoint Remote Control interface</h2> <h2>PowerPoint Remote Control interface</h2>
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()" /><br> <input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()" /><br>
<br> <br>
This app is a custom remote client for VDO.Ninja's PowerPoint remote control feature. This app is a custom remote client for VDO.Ninja's PowerPoint remote control feature.
<br><br> <br><br>
For this to work, the remote VDO.Ninja peer will need <b> &midiin </b> added to their URL, a virtual MIDI loopback device installed, PowerPoint running as an application, and the AutoHotKey script <a href='https://github.com/steveseguin/powerpoint_remote'>found here</a> running, with the MIDI loopback device selected as a MIDI Input device. For this to work, the remote VDO.Ninja peer will need <b> &midiin </b> added to their URL, a virtual MIDI loopback device installed, PowerPoint running as an application, and the AutoHotKey script <a href='https://github.com/steveseguin/powerpoint_remote'>found here</a> running, with the MIDI loopback device selected as a MIDI Input device.
</div> </div>
<script> <script>
var iframe; var iframe;
function nextSlide(){ function nextSlide(){
if (iframe){ if (iframe){
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 11]}}, '*'); iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 11]}}, '*');
/// OR AS OF V22.12 YOU CAN DO: /// OR AS OF V22.12 YOU CAN DO:
//iframe.contentWindow.postMessage({"nextSlide":true}, '*'); //iframe.contentWindow.postMessage({"nextSlide":true}, '*');
} }
} }
function prevSlide(){ function prevSlide(){
if (iframe){ if (iframe){
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 10]}}, '*'); iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 10]}}, '*');
/// OR AS OF V22.12 YOU CAN DO: /// OR AS OF V22.12 YOU CAN DO:
//iframe.contentWindow.postMessage({"prevSlide":true}, '*'); //iframe.contentWindow.postMessage({"prevSlide":true}, '*');
} }
} }
function customCommand(){ // just an example of what you can do to make a custom action. function customCommand(){ // just an example of what you can do to make a custom action.
if (iframe){ if (iframe){
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 12]}}, '*'); // You'll need to have autohotkey be updated to respond to this though. iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 12]}}, '*'); // You'll need to have autohotkey be updated to respond to this though.
} }
} }
var urlEdited = window.location.search.replace(/\?\?/g, "?"); var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&"); urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?"); urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){ if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited); warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString()); window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
} }
var urlParams = new URLSearchParams(urlEdited); var urlParams = new URLSearchParams(urlEdited);
var room = false; var room = false;
if (urlParams.has("room") || urlParams.has("r")){ if (urlParams.has("room") || urlParams.has("r")){
room = urlParams.get("room") || urlParams.get("r") || false; room = urlParams.get("room") || urlParams.get("r") || false;
} }
if (room){ if (room){
loadIframes(room); loadIframes(room);
} }
function loadIframes(roomname=false){ function loadIframes(roomname=false){
if (!roomname){ if (!roomname){
roomname = document.getElementById("viewlink").value; roomname = document.getElementById("viewlink").value;
} }
document.getElementById("viewlink").parentNode.parentNode.removeChild(document.getElementById("viewlink").parentNode); document.getElementById("viewlink").parentNode.parentNode.removeChild(document.getElementById("viewlink").parentNode);
document.getElementById("container1").style.display="inline-block"; document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block"; document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
var room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_controller&webcam&autostart&minipreview"; var room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_controller&webcam&autostart&minipreview";
iframe = document.createElement("iframe"); iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = room1; iframe.src = room1;
document.getElementById("container1").appendChild(iframe); document.getElementById("container1").appendChild(iframe);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,23 +1,23 @@
p2p is a sample of how to use vdo.ninja as a data transport tunneling service p2p is a sample of how to use vdo.ninja as a data transport tunneling service
twitch is an example of how to have a twitch live chat side-by-side with VDO.NInja on the same screen twitch is an example of how to have a twitch live chat side-by-side with VDO.NInja on the same screen
dual is an example of how to have two VDO.Ninja windows (or any windows really) open on the same page; Picture-in-Picture style dual is an example of how to have two VDO.Ninja windows (or any windows really) open on the same page; Picture-in-Picture style
sensors is an example of how to transmit sensor and video data from a phone to a computer, drawing it to canvas: youtube video for this exists sensors is an example of how to transmit sensor and video data from a phone to a computer, drawing it to canvas: youtube video for this exists
midi demonstrates the MIDI API for VDO.Ninja midi demonstrates the MIDI API for VDO.Ninja
draggable demonstrates how to drag multiple windows around, if you wanted to create a custom layout of elements. (experimental) draggable demonstrates how to drag multiple windows around, if you wanted to create a custom layout of elements. (experimental)
chat.html is an example of a chat-only interface for VDO.NInja; maybe dockable into OBS even chat.html is an example of a chat-only interface for VDO.NInja; maybe dockable into OBS even
iframe.outbound-stats.html demostrates how to get stats from VDO.Ninja using the IFRAME API iframe.outbound-stats.html demostrates how to get stats from VDO.Ninja using the IFRAME API
changepass lets you create passwords and related HASH values for VDO.NInja rooms changepass lets you create passwords and related HASH values for VDO.NInja rooms
webhid demonstrates how to interface with a USB device, like a streamdeck (mouse/keyboard not supported) webhid demonstrates how to interface with a USB device, like a streamdeck (mouse/keyboard not supported)
zoom.html is a tool for letting you publish into VDO.Ninja, but then full-screen the window once setup, allowing for window-capturing into zoom. zoom.html is a tool for letting you publish into VDO.Ninja, but then full-screen the window once setup, allowing for window-capturing into zoom.
obs_remote is also hosted on github elsewhere, but it's an example of how to remotely control OBS using VDO.Ninja's tunneling abilities obs_remote is also hosted on github elsewhere, but it's an example of how to remotely control OBS using VDO.Ninja's tunneling abilities

View File

@ -1,477 +1,477 @@
<html> <html>
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
<link href="./nes.min.css" rel="stylesheet" /> <link href="nes.min.css" rel="stylesheet" />
<style> <style>
html, body, pre, code, kbd, samp { html, body, pre, code, kbd, samp {
font-family:"Press Start 2P"; font-family:"Press Start 2P";
} }
body{ body{
margin:1%; margin:1%;
border:0; border:0;
background-image: linear-gradient(to left, #e1c5d5, #ddc5da, #d8c6e0, #d1c7e5, #c8c9e9, #c1cded, #bad2f0, #b4d6f2, #b1ddf3, #b1e4f3, #b4eaf0, #bbf0ed); background-image: linear-gradient(to left, #e1c5d5, #ddc5da, #d8c6e0, #d1c7e5, #c8c9e9, #c1cded, #bad2f0, #b4d6f2, #b1ddf3, #b1e4f3, #b4eaf0, #bbf0ed);
width:100%; width:100%;
height:100%; height:100%;
} }
button { button {
margin:10px 3px; margin:10px 3px;
} }
button, input, optgroup, select, textarea { button, input, optgroup, select, textarea {
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
line-height: inherit; line-height: inherit;
padding: 8px 12px; padding: 8px 12px;
} }
button:active{ button:active{
background-color:#BBB; background-color:#BBB;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="header"></div> <div id="header"></div>
<div id="target_self"></div> <div id="target_self"></div>
<div id="guest_1_container"></div> <div id="guest_1_container"></div>
<script> <script>
function generateStreamID(){ function generateStreamID(){
var text = ""; var text = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"; var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
for (var i = 0; i < 10; i++){ for (var i = 0; i < 10; i++){
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
} }
return text; return text;
}; };
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams || function (searchString) { w.URLSearchParams = w.URLSearchParams || function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} }
else { else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) { window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
console.error(errorMsg); console.error(errorMsg);
console.error(lineNumber); console.error(lineNumber);
console.error("Unhandeled Error occured"); //or any message console.error("Unhandeled Error occured"); //or any message
return false; return false;
}; };
window.onbeforeunload = function() { window.onbeforeunload = function() {
return "Dude, are you sure you want to leave? Think of the kittens!"; // prevents accidental page reloads. return "Dude, are you sure you want to leave? Think of the kittens!"; // prevents accidental page reloads.
} }
var WID = "testVDON"; var WID = "testVDON";
if (urlParams.has("api")){ if (urlParams.has("api")){
WID = urlParams.get("api"); WID = urlParams.get("api");
} else if (urlParams.has("osc")){ } else if (urlParams.has("osc")){
WID = urlParams.get("osc"); WID = urlParams.get("osc");
} else if (urlParams.has("id")){ } else if (urlParams.has("id")){
WID = urlParams.get("id"); WID = urlParams.get("id");
} else if (urlParams.has("ID")){ } else if (urlParams.has("ID")){
WID = urlParams.get("ID"); WID = urlParams.get("ID");
} else if (urlParams.has("wid")){ } else if (urlParams.has("wid")){
WID = urlParams.get("wid"); WID = urlParams.get("wid");
} else { } else {
WID = generateStreamID(10); WID = generateStreamID(10);
var href = window.location.href; var href = window.location.href;
var arr = href.split('?'); var arr = href.split('?');
var newurl; var newurl;
if (arr.length > 1 && arr[1] !== '') { if (arr.length > 1 && arr[1] !== '') {
newurl = href + '&api=' + WID; newurl = href + '&api=' + WID;
} else { } else {
newurl = href + '?api=' + WID; newurl = href + '?api=' + WID;
} }
window.history.pushState({path: newurl.toString()}, '', newurl.toString()); window.history.pushState({path: newurl.toString()}, '', newurl.toString());
} }
var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
var header = document.getElementById("header"); var header = document.getElementById("header");
header.innerHTML += "Your Ninja Link: <a href='https://"+path+"/?api="+WID+"' target='_blank'>https://"+path+"/?api="+WID+"</a><br /><br />"; header.innerHTML += "Your Ninja Link: <a href='https://"+path+"/?api="+WID+"' target='_blank'>https://"+path+"/?api="+WID+"</a><br /><br />";
header.innerHTML += "<small>You can append your own VDO.Ninja parameters to this link, treating it like a normal VDO.Ninja link.</small>"; header.innerHTML += "<small>You can append your own VDO.Ninja parameters to this link, treating it like a normal VDO.Ninja link.</small>";
header.innerHTML += "<br /><br /><small>Code and documentation hosted at <a href='https://github.com/steveseguin/Companion-Ninja'>https://github.com/steveseguin/Companion-Ninja</a></small> <svg width='32' height='32' viewBox='0 0 1024 1024' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z' transform='scale(64)' fill='#1B1F23'/></svg>"; header.innerHTML += "<br /><br /><small>Code and documentation hosted at <a href='https://github.com/steveseguin/Companion-Ninja'>https://github.com/steveseguin/Companion-Ninja</a></small> <svg width='32' height='32' viewBox='0 0 1024 1024' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z' transform='scale(64)' fill='#1B1F23'/></svg>";
var socket = null; var socket = null;
var connecting = false; var connecting = false;
var failedCount = 0; var failedCount = 0;
function connect(){ function connect(){
clearTimeout(connecting); clearTimeout(connecting);
if (socket){ if (socket){
if (socket.readyState === socket.OPEN){return;} if (socket.readyState === socket.OPEN){return;}
try{ try{
socket.close(); socket.close();
} catch(e){} } catch(e){}
} }
socket = new WebSocket("wss://api.vdo.ninja:443"); socket = new WebSocket("wss://api.vdo.ninja:443");
socket.onclose = function (){ socket.onclose = function (){
failedCount+=1; failedCount+=1;
clearTimeout(connecting); clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*(failedCount-1)); connecting = setTimeout(function(){connect();},100*(failedCount-1));
}; };
socket.onerror = function (){ socket.onerror = function (){
failedCount+=1; failedCount+=1;
clearTimeout(connecting); clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*failedCount); connecting = setTimeout(function(){connect();},100*failedCount);
}; };
socket.onopen = function (){ socket.onopen = function (){
failedCount = 0; failedCount = 0;
try{ try{
socket.send(JSON.stringify({"join":WID})); socket.send(JSON.stringify({"join":WID}));
} catch(e){ } catch(e){
connecting = setTimeout(function(){connect();},1); connecting = setTimeout(function(){connect();},1);
} }
} }
} }
connect(); connect();
function sendGuestCommand(target, action, value=null){ function sendGuestCommand(target, action, value=null){
sendMessage(JSON.stringify({"target":target, "action":action, "value":value})); sendMessage(JSON.stringify({"target":target, "action":action, "value":value}));
} }
function sendMessage(msg){ function sendMessage(msg){
if (socket.readyState !== socket.OPEN){ if (socket.readyState !== socket.OPEN){
console.log("not connected; msg didn't send"); console.log("not connected; msg didn't send");
connect(); connect();
return; return;
} }
try { try {
socket.send(msg); socket.send(msg);
} catch(e){ } catch(e){
connecting = setTimeout(function(){connect();},100); connecting = setTimeout(function(){connect();},100);
} }
} }
function log(msg){ function log(msg){
console.log(msg); console.log(msg);
} }
function ajax(data) { function ajax(data) {
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) { if (this.readyState == 4 && this.status == 200) {
log("AJAX MESSAGE SENT SUCCESSFULL"); log("AJAX MESSAGE SENT SUCCESSFULL");
} }
}; };
var action = false var action = false
if ("action" in data){ if ("action" in data){
action=data['action']; action=data['action'];
} }
var value = "null" var value = "null"
if ("value" in data){ if ("value" in data){
value=data['value']; value=data['value'];
} }
var apiid = false var apiid = false
if ("apiid" in data){ if ("apiid" in data){
apiid=data['apiid']; apiid=data['apiid'];
} }
var target = "null"; var target = "null";
if ("target" in data){ if ("target" in data){
target=data['target']; target=data['target'];
} }
if (!action || !apiid){ if (!action || !apiid){
alert("no action or api ID provided; request won't work"); alert("no action or api ID provided; request won't work");
} else { } else {
var URL = "https://api.vdo.ninja/"+apiid+"/"+action+"/"+target+"/"+value; var URL = "https://api.vdo.ninja/"+apiid+"/"+action+"/"+target+"/"+value;
xhttp.open("GET", URL, true); xhttp.open("GET", URL, true);
xhttp.send(); xhttp.send();
} }
} }
function loadSelfCommands(){ function loadSelfCommands(){
var commands = {} var commands = {}
commands.speaker = function(value){sendMessage(JSON.stringify({"action":"speaker","value":value}))}; // "speaker" also works in the same way commands.speaker = function(value){sendMessage(JSON.stringify({"action":"speaker","value":value}))}; // "speaker" also works in the same way
commands.mic = function(value){sendMessage(JSON.stringify({"action":"mic","value":value}))}; commands.mic = function(value){sendMessage(JSON.stringify({"action":"mic","value":value}))};
commands.camera = function(value){sendMessage(JSON.stringify({"action":"camera","value":value}))}; commands.camera = function(value){sendMessage(JSON.stringify({"action":"camera","value":value}))};
commands.bitrate = function(value){sendMessage(JSON.stringify({"action":"bitrate","value":value}))}; commands.bitrate = function(value){sendMessage(JSON.stringify({"action":"bitrate","value":value}))};
commands.volume = function(value){sendMessage(JSON.stringify({"action":"volume","value":value}))}; commands.volume = function(value){sendMessage(JSON.stringify({"action":"volume","value":value}))};
commands.record = function(value){sendMessage(JSON.stringify({"action":"record","value":value}))}; commands.record = function(value){sendMessage(JSON.stringify({"action":"record","value":value}))};
commands.sayHello = function(value){sendMessage(JSON.stringify({"action":"sendChat","value":"Hello"}))}; commands.sayHello = function(value){sendMessage(JSON.stringify({"action":"sendChat","value":"Hello"}))};
var target_self = document.getElementById("target_self"); var target_self = document.getElementById("target_self");
setTimeout(function(){ setTimeout(function(){
var hr = document.createElement("hr"); var hr = document.createElement("hr");
target_self.appendChild(hr); target_self.appendChild(hr);
var h3 = document.createElement("h3"); var h3 = document.createElement("h3");
h3.innerText = "These are Websocket-based requests"; h3.innerText = "These are Websocket-based requests";
target_self.appendChild(h3); target_self.appendChild(h3);
},0); },0);
for (var k in commands) { for (var k in commands) {
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TRUE"; button.innerHTML = k + ":<br />TRUE";
button.onclick = function(){commands[k](true);} button.onclick = function(){commands[k](true);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />FALSE"; button.innerHTML = k + ":<br />FALSE";
button.onclick = function(){commands[k](false);} button.onclick = function(){commands[k](false);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){ if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TOGGLE"; button.innerHTML = k + ":<br />TOGGLE";
button.onclick = function(){commands[k]("toggle");} button.onclick = function(){commands[k]("toggle");}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
} }
log(k); log(k);
} // list available commands to console } // list available commands to console
commands.reload = function(){sendMessage(JSON.stringify({"action":"reload","value":true}))}; commands.reload = function(){sendMessage(JSON.stringify({"action":"reload","value":true}))};
commands.hangup = function(){sendMessage(JSON.stringify({"action":"hangup","value":true}))}; commands.hangup = function(){sendMessage(JSON.stringify({"action":"hangup","value":true}))};
k = "reload"; k = "reload";
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TRUE"; button.innerHTML = k + ":<br />TRUE";
button.onclick = function(){commands[k](true);} button.onclick = function(){commands[k](true);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
k = "hangup"; k = "hangup";
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TRUE"; button.innerHTML = k + ":<br />TRUE";
button.onclick = function(){commands[k](true);} button.onclick = function(){commands[k](true);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
setTimeout(function(){ setTimeout(function(){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Rainbow<br />Puke"; button.innerHTML = "Rainbow<br />Puke";
button.onclick = function(){sendMessage(JSON.stringify({"action":"forceKeyframe"}))} button.onclick = function(){sendMessage(JSON.stringify({"action":"forceKeyframe"}))}
target_self.appendChild(button); target_self.appendChild(button);
},0); },0);
var commands2 = {} var commands2 = {}
commands2.speaker = function(value){ajax({"action":"speaker","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.speaker = function(value){ajax({"action":"speaker","value":value,"apiid":WID})}; // "speaker" also works in the same way
commands2.mic = function(value){ajax({"action":"mic","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.mic = function(value){ajax({"action":"mic","value":value,"apiid":WID})}; // "speaker" also works in the same way
commands2.camera = function(value){ajax({"action":"camera","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.camera = function(value){ajax({"action":"camera","value":value,"apiid":WID})}; // "speaker" also works in the same way
commands2.bitrate = function(value){ajax({"action":"bitrate","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.bitrate = function(value){ajax({"action":"bitrate","value":value,"apiid":WID})}; // "speaker" also works in the same way
commands2.volume = function(value){ajax({"action":"volume","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.volume = function(value){ajax({"action":"volume","value":value,"apiid":WID})}; // "speaker" also works in the same way
commands2.record = function(value){ajax({"action":"record","value":value,"apiid":WID})}; // "speaker" also works in the same way commands2.record = function(value){ajax({"action":"record","value":value,"apiid":WID})}; // "speaker" also works in the same way
setTimeout(function(){ setTimeout(function(){
var hr = document.createElement("hr"); var hr = document.createElement("hr");
target_self.appendChild(hr); target_self.appendChild(hr);
var h3 = document.createElement("h3"); var h3 = document.createElement("h3");
h3.innerText = "These are HTTP-based GET requests"; h3.innerText = "These are HTTP-based GET requests";
target_self.appendChild(h3); target_self.appendChild(h3);
},0); },0);
for (var k in commands2) { for (var k in commands2) {
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TRUE"; button.innerHTML = k + ":<br />TRUE";
button.onclick = function(){commands2[k](true);} button.onclick = function(){commands2[k](true);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />FALSE"; button.innerHTML = k + ":<br />FALSE";
button.onclick = function(){commands2[k](false);} button.onclick = function(){commands2[k](false);}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){ if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
setTimeout(function(k){ setTimeout(function(k){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = k + ":<br />TOGGLE"; button.innerHTML = k + ":<br />TOGGLE";
button.onclick = function(){commands2[k]("toggle");} button.onclick = function(){commands2[k]("toggle");}
target_self.appendChild(button); target_self.appendChild(button);
},0,k); },0,k);
} }
log(k); log(k);
} }
setTimeout(function(WID){ setTimeout(function(WID){
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "Rainbow<br />Puke" button.innerHTML = "Rainbow<br />Puke"
button.onclick = function(){ajax({"action":"forceKeyframe","apiid":WID})} button.onclick = function(){ajax({"action":"forceKeyframe","apiid":WID})}
target_self.appendChild(button); target_self.appendChild(button);
},0,WID); },0,WID);
return commands; return commands;
} }
function loadGuestCommands(guestid){ function loadGuestCommands(guestid){
var container = document.createElement("div"); var container = document.createElement("div");
container.id = "guest_"+guestid+"_container"; container.id = "guest_"+guestid+"_container";
document.body.appendChild(container); document.body.appendChild(container);
var hr = document.createElement("hr"); var hr = document.createElement("hr");
container.appendChild(hr); container.appendChild(hr);
var h3 = document.createElement("h3"); var h3 = document.createElement("h3");
h3.innerText = "These target guest "+guestid+ " (if a director)"; h3.innerText = "These target guest "+guestid+ " (if a director)";
container.appendChild(h3); container.appendChild(h3);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "transfer popup"; button.innerHTML = "transfer popup";
button.onclick = function(){sendGuestCommand(guestid, "forward");}; button.onclick = function(){sendGuestCommand(guestid, "forward");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "transfer to 'room321'"; button.innerHTML = "transfer to 'room321'";
button.onclick = function(){sendGuestCommand(guestid, "forward", 'room321');}; button.onclick = function(){sendGuestCommand(guestid, "forward", 'room321');};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 1"; button.innerHTML = "scene 1";
button.onclick = function(){sendGuestCommand(guestid, "addScene");}; /// SCENE 1 or specify a custom scene name as a value button.onclick = function(){sendGuestCommand(guestid, "addScene");}; /// SCENE 1 or specify a custom scene name as a value
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "mute in scene"; button.innerHTML = "mute in scene";
button.onclick = function(){sendGuestCommand(guestid, "muteScene");}; button.onclick = function(){sendGuestCommand(guestid, "muteScene");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "mute everywhere"; button.innerHTML = "mute everywhere";
button.onclick = function(){sendGuestCommand(guestid, "mic");}; button.onclick = function(){sendGuestCommand(guestid, "mic");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "hang up"; button.innerHTML = "hang up";
button.onclick = function(){sendGuestCommand(guestid, "hangup");}; button.onclick = function(){sendGuestCommand(guestid, "hangup");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "solo chat"; button.innerHTML = "solo chat";
button.onclick = function(){sendGuestCommand(guestid, "soloChat");}; button.onclick = function(){sendGuestCommand(guestid, "soloChat");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "remote speaker"; button.innerHTML = "remote speaker";
button.onclick = function(){sendGuestCommand(guestid, "speaker");}; button.onclick = function(){sendGuestCommand(guestid, "speaker");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "remote display"; button.innerHTML = "remote display";
button.onclick = function(){sendGuestCommand(guestid, "display");}; button.onclick = function(){sendGuestCommand(guestid, "display");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "rainbow puke fix"; button.innerHTML = "rainbow puke fix";
button.onclick = function(){sendGuestCommand(guestid, "forceKeyframe");}; button.onclick = function(){sendGuestCommand(guestid, "forceKeyframe");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "highlight"; button.innerHTML = "highlight";
button.onclick = function(){sendGuestCommand(guestid, "soloVideo");}; button.onclick = function(){sendGuestCommand(guestid, "soloVideo");};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 2"; button.innerHTML = "scene 2";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 2);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 2);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 3"; button.innerHTML = "scene 3";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 3);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 3);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 4"; button.innerHTML = "scene 4";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 4);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 4);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 5"; button.innerHTML = "scene 5";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 5);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 5);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 6"; button.innerHTML = "scene 6";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 6);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 6);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = " scene 7"; button.innerHTML = " scene 7";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 7);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 7);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 8"; button.innerHTML = "scene 8";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 8);}; button.onclick = function(){sendGuestCommand(guestid, "addScene", 8);};
container.appendChild(button); container.appendChild(button);
var button = document.createElement("button"); var button = document.createElement("button");
button.innerHTML = "scene 'test'"; button.innerHTML = "scene 'test'";
button.onclick = function(){sendGuestCommand(guestid, "addScene", 'test');}; // specifying a custom scene; it needs to be active for this to work.. button.onclick = function(){sendGuestCommand(guestid, "addScene", 'test');}; // specifying a custom scene; it needs to be active for this to work..
container.appendChild(button); container.appendChild(button);
var input = document.createElement("label"); var input = document.createElement("label");
input.innerHTML = "mic volume:"; input.innerHTML = "mic volume:";
container.appendChild(input); container.appendChild(input);
var input = document.createElement("input"); var input = document.createElement("input");
input.type = "range"; input.type = "range";
input.title = "volume"; input.title = "volume";
input.min = 0; input.min = 0;
input.max = 200; input.max = 200;
input.value = 100; input.value = 100;
input.onchange = function(){sendGuestCommand(guestid, "volume", this.value);}; input.onchange = function(){sendGuestCommand(guestid, "volume", this.value);};
container.appendChild(input); container.appendChild(input);
} }
loadSelfCommands(); loadSelfCommands();
loadGuestCommands(1); loadGuestCommands(1);
loadGuestCommands(2); loadGuestCommands(2);
loadGuestCommands(3); loadGuestCommands(3);
loadGuestCommands(4); loadGuestCommands(4);
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,135 +1,135 @@
<html> <html>
<head><title>Rotated Scene</title> <head><title>Rotated Scene</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
} }
iframe { iframe {
width:100%; width:100%;
height:470px; height:470px;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width: 100vh; width: 100vh;
height: 100vw; height: 100vw;
transform: rotate(90deg); transform: rotate(90deg);
transform-origin: 0 0; transform-origin: 0 0;
left: 100vw; left: 100vw;
position: relative; position: relative;
top: 0; top: 0;
} }
</style> </style>
</head> </head>
<body> <body>
<script> <script>
function removeStorage(cname){ function removeStorage(cname){
localStorage.removeItem(cname); localStorage.removeItem(cname);
} }
function clearStorage(){ function clearStorage(){
localStorage.clear(); localStorage.clear();
if (!session.cleanOutput){ if (!session.cleanOutput){
warnUser("The local storage and saved settings have been cleared", 1000); warnUser("The local storage and saved settings have been cleared", 1000);
} }
} }
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date(); var now = new Date();
var item = { var item = {
value: cvalue, value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000), expiry: now.getTime() + (hours * 60 * 60 * 1000),
}; };
try{ try{
localStorage.setItem(cname, JSON.stringify(item)); localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);} }catch(e){errorlog(e);}
} }
function getStorage(cname) { function getStorage(cname) {
try { try {
var itemStr = localStorage.getItem(cname); var itemStr = localStorage.getItem(cname);
} catch(e){ } catch(e){
errorlog(e); errorlog(e);
return; return;
} }
if (!itemStr) { if (!itemStr) {
return ""; return "";
} }
var item = JSON.parse(itemStr); var item = JSON.parse(itemStr);
var now = new Date(); var now = new Date();
if (now.getTime() > item.expiry) { if (now.getTime() > item.expiry) {
localStorage.removeItem(cname); localStorage.removeItem(cname);
return ""; return "";
} }
return item.value; return item.value;
} }
(function(w) { (function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) { w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this; var self = this;
searchString = searchString.replace("??", "?"); searchString = searchString.replace("??", "?");
self.searchString = searchString; self.searchString = searchString;
self.get = function(name) { self.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString); var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlEdited = window.location.search.replace(/\?\?/g, "?"); var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&"); urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?"); urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){ if (urlEdited !== window.location.search){
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString()); window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
} }
var urlParams = new URLSearchParams(urlEdited); var urlParams = new URLSearchParams(urlEdited);
var rotate = parseInt(urlParams.get("rotate")) || "90"; var rotate = parseInt(urlParams.get("rotate")) || "90";
var sdfasd = decodeURIComponent(urlParams.get("link") || "") || getStorage("savedRotateLink") || ""; var sdfasd = decodeURIComponent(urlParams.get("link") || "") || getStorage("savedRotateLink") || "";
var linktoload = sdfasd || prompt("What URL would you like to load? (rotated)"); var linktoload = sdfasd || prompt("What URL would you like to load? (rotated)");
setStorage("savedRotateLink", linktoload, 99999); setStorage("savedRotateLink", linktoload, 99999);
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = linktoload; iframe.src = linktoload;
if (rotate=="180"){ if (rotate=="180"){
iframe.style.transform = "rotate(180deg)"; iframe.style.transform = "rotate(180deg)";
iframe.style.width = "100vw"; iframe.style.width = "100vw";
iframe.style.height = "100vh"; iframe.style.height = "100vh";
iframe.style.transformOrigin = "0 0;"; iframe.style.transformOrigin = "0 0;";
iframe.style.position = "rotate(180deg)"; iframe.style.position = "rotate(180deg)";
iframe.style.left = "100vw"; iframe.style.left = "100vw";
iframe.style.top = "100vh"; iframe.style.top = "100vh";
} else if (rotate=="270"){ } else if (rotate=="270"){
iframe.style.transform = "rotate(270deg)"; iframe.style.transform = "rotate(270deg)";
iframe.style.left = "0"; iframe.style.left = "0";
iframe.style.top = "100vh"; iframe.style.top = "100vh";
} }
document.body.appendChild(iframe); document.body.appendChild(iframe);
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,266 +1,266 @@
<html> <html>
<head><title>Video with sensor overlayed data</title> <head><title>Video with sensor overlayed data</title>
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:100%; height:100%;
} }
#container { #container {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:100%; height:100%;
position:absolute; position:absolute;
top:0; top:0;
left:0; left:0;
} }
#overlay{ #overlay{
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
text-align:right; text-align:right;
position:absolute; position:absolute;
top:100px; top:100px;
right:0; right:0;
z-index: 10; z-index: 10;
color: white; color: white;
font-size:300%; font-size:300%;
} }
#canvas{ #canvas{
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:20%; width:20%;
text-align:right; text-align:right;
height:100px; height:100px;
position:absolute; position:absolute;
top:0; top:0;
right:0; right:0;
z-index: 5; z-index: 5;
} }
</style> </style>
</head> </head>
<body id="main"> <body id="main">
<div id="overlay"></div> <div id="overlay"></div>
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<div id="container"></div> <div id="container"></div>
<script> <script>
function getColor(value) { function getColor(value) {
var hue = (Math.abs(value*100+50)).toString(10); var hue = (Math.abs(value*100+50)).toString(10);
return ["hsl(", hue, ",100%,50%)"].join(""); return ["hsl(", hue, ",100%,50%)"].join("");
} }
var canvas = document.getElementById("canvas"); var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d"); var context = canvas.getContext("2d");
var height = context.canvas.height; var height = context.canvas.height;
var width = context.canvas.width; var width = context.canvas.width;
canvas.history_accel = []; canvas.history_accel = [];
canvas.history_speed = []; canvas.history_speed = [];
var canvasNew = true var canvasNew = true
function plotData(speed, accel) { function plotData(speed, accel) {
if (isNaN(speed)) { if (isNaN(speed)) {
speed = 0; speed = 0;
} }
if (isNaN(accel)) { if (isNaN(accel)) {
accel = 0; accel = 0;
} }
canvas.history_accel.push(accel); canvas.history_accel.push(accel);
canvas.history_speed.push(speed); canvas.history_speed.push(speed);
canvas.history_accel = canvas.history_accel.slice(-1 * canvas.width); canvas.history_accel = canvas.history_accel.slice(-1 * canvas.width);
canvas.history_speed = canvas.history_speed.slice(-1 * canvas.width); canvas.history_speed = canvas.history_speed.slice(-1 * canvas.width);
var maxSpeed = Math.max(...canvas.history_speed); var maxSpeed = Math.max(...canvas.history_speed);
var interval = 10; var interval = 10;
var target = canvas.target || (interval*1.5); var target = canvas.target || (interval*1.5);
if (target && (maxSpeed > target)){ if (target && (maxSpeed > target)){
canvas.target = maxSpeed*1.5; // set it higher than it needs to be, so it doens't jump around a lot canvas.target = maxSpeed*1.5; // set it higher than it needs to be, so it doens't jump around a lot
var yScale = height / canvas.target; var yScale = height / canvas.target;
context.clearRect(0, 0, width, height); context.clearRect(0, 0, width, height);
var w = 1; var w = 1;
var x = width - w; var x = width - w;
for (var i = 0; i<canvas.history_speed.length;i++){ for (var i = 0; i<canvas.history_speed.length;i++){
var accel = canvas.history_accel[i]; var accel = canvas.history_accel[i];
var speed = canvas.history_speed[i]; var speed = canvas.history_speed[i];
var val = (10-accel)/10; var val = (10-accel)/10;
if (val>1){val=1;} if (val>1){val=1;}
else if (val<0){val=0;} else if (val<0){val=0;}
var color = getColor(val); var color = getColor(val);
var y = height - speed * yScale; var y = height - speed * yScale;
context.fillStyle = color; context.fillStyle = color;
context.fillRect(x, y, w, height); context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5"; context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4); context.fillRect(x, y-2, w, 4);
if (y-5>0){ if (y-5>0){
context.fillStyle = "#FFF3"; context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1); context.fillRect(x, y+2, w, 1);
} }
var imageData = context.getImageData(w, 0, x, height); var imageData = context.getImageData(w, 0, x, height);
context.putImageData(imageData, 0, 0); context.putImageData(imageData, 0, 0);
context.clearRect(x, 0, w, height); context.clearRect(x, 0, w, height);
} }
for (var tt = interval; tt<canvas.target;tt+=interval){ for (var tt = interval; tt<canvas.target;tt+=interval){
var y = parseInt(height - tt * yScale); var y = parseInt(height - tt * yScale);
context.fillStyle = "#0555"; context.fillStyle = "#0555";
context.fillRect(0, y, width, 1); context.fillRect(0, y, width, 1);
} }
return; return;
} }
var val = (10-accel)/10; var val = (10-accel)/10;
if (val>1){val=1;} if (val>1){val=1;}
else if (val<0){val=0;} else if (val<0){val=0;}
var color = getColor(val); var color = getColor(val);
var yScale = height / target; var yScale = height / target;
var w = 1; var w = 1;
var x = width - w; var x = width - w;
var y = height - speed * yScale; var y = height - speed * yScale;
context.fillStyle = color; context.fillStyle = color;
context.fillRect(x, y, w, height); context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5"; context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4); context.fillRect(x, y-2, w, 4);
if (y-5>0){ if (y-5>0){
context.fillStyle = "#FFF3"; context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1); context.fillRect(x, y+2, w, 1);
} }
context.fillStyle = "#0555"; context.fillStyle = "#0555";
if (canvasNew){ if (canvasNew){
canvasNew = false; canvasNew = false;
for (var tt = interval; tt<target;tt+=interval){ for (var tt = interval; tt<target;tt+=interval){
var y = parseInt(height - tt * yScale); var y = parseInt(height - tt * yScale);
context.fillRect(0, y, width, 1); context.fillRect(0, y, width, 1);
} }
} else { } else {
for (var tt = interval; tt<target;tt+=interval){ for (var tt = interval; tt<target;tt+=interval){
var y = parseInt(height - tt * yScale); var y = parseInt(height - tt * yScale);
context.fillRect(x, y, w, 1); context.fillRect(x, y, w, 1);
} }
} }
var imageData = context.getImageData(w, 0, x, height); var imageData = context.getImageData(w, 0, x, height);
context.putImageData(imageData, 0, 0); context.putImageData(imageData, 0, 0);
context.clearRect(x, 0, w, height); context.clearRect(x, 0, w, height);
} }
function loadIframe(url=false){ function loadIframe(url=false){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
if (url){ if (url){
var iframesrc = url; var iframesrc = url;
} else { } else {
var iframesrc = document.getElementById("viewlink").value; var iframesrc = document.getElementById("viewlink").value;
} }
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
if (iframesrc==""){ if (iframesrc==""){
iframesrc="./"; iframesrc="./";
} }
iframe.src = iframesrc; iframe.src = iframesrc;
document.getElementById("container").appendChild(iframe); document.getElementById("container").appendChild(iframe);
var outputWindow = document.getElementById("overlay"); var outputWindow = document.getElementById("overlay");
var sensors = {}; var sensors = {};
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("sensors" in e.data){ if ("sensors" in e.data){
//console.log(e.data.sensors); //console.log(e.data.sensors);
var speed = 0; var speed = 0;
if (e.data.sensors.pos){ if (e.data.sensors.pos){
speed = e.data.sensors.pos.speed; speed = e.data.sensors.pos.speed;
// e.data.sensors.pos.alt // e.data.sensors.pos.alt
// e.data.sensors.pos.t // e.data.sensors.pos.t
} }
var accel = 0; var accel = 0;
if (e.data.sensors.lin){ if (e.data.sensors.lin){
accel += Math.pow(e.data.sensors.lin.x, 2); accel += Math.pow(e.data.sensors.lin.x, 2);
accel += Math.pow(e.data.sensors.lin.y, 2); accel += Math.pow(e.data.sensors.lin.y, 2);
accel += Math.pow(e.data.sensors.lin.z, 2); accel += Math.pow(e.data.sensors.lin.z, 2);
} }
if (accel){ if (accel){
accel = Math.pow(accel,0.5); accel = Math.pow(accel,0.5);
} }
if (isNaN(accel)){ if (isNaN(accel)){
accel = 0; accel = 0;
} }
plotData(speed, accel); plotData(speed, accel);
outputWindow.innerHTML = ""; outputWindow.innerHTML = "";
speed = parseInt(speed*100)/100; speed = parseInt(speed*100)/100;
outputWindow.innerHTML += "speed: "+speed+"m/s<br />"; outputWindow.innerHTML += "speed: "+speed+"m/s<br />";
accel = parseInt(accel*100)/100; accel = parseInt(accel*100)/100;
outputWindow.innerHTML += "acceleration: " + accel + "m/s^2<br />"; outputWindow.innerHTML += "acceleration: " + accel + "m/s^2<br />";
//for (var key in e.data.sensors.lin) { //for (var key in e.data.sensors.lin) {
// outputWindow.innerHTML += key + " lin: " + e.data.sensors.lin[key] + "<br />"; // outputWindow.innerHTML += key + " lin: " + e.data.sensors.lin[key] + "<br />";
//} //}
//for (var key in e.data.sensors.acc) { //for (var key in e.data.sensors.acc) {
// outputWindow.innerHTML += key + " acc: " + e.data.sensors.acc[key] + "<br />"; // outputWindow.innerHTML += key + " acc: " + e.data.sensors.acc[key] + "<br />";
//} //}
//for (var key in e.data.sensors.mag) { //for (var key in e.data.sensors.mag) {
// outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />"; // outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
//} //}
//for (var key in e.data.sensors.ori) { //for (var key in e.data.sensors.ori) {
// outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />"; // outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
//} //}
} }
}); });
} }
loadIframe("../"+window.location.search); loadIframe("../"+window.location.search);
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,280 +1,280 @@
<html> <html>
<head><title>Sensor and video stream access example</title> <head><title>Sensor and video stream access example</title>
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: rgb(222,242,253); background-color: rgb(222,242,253);
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
margin:10px; margin:10px;
width:640px; width:640px;
height:320px; height:320px;
} }
#viewlink { #viewlink {
width:400px; width:400px;
} }
#container { #container {
display:block; display:block;
padding:0px; padding:0px;
} }
input{ input{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
button{ button{
padding:5px; padding:5px;
margin:5px; margin:5px;
} }
canvas{ canvas{
width:100%; width:100%;
display:block; display:block;
margin:0; margin:0;
padding:0; padding:0;
} }
</style> </style>
</head> </head>
<body> <body>
<canvas id="canvas" style="display:none;max-height:70vh;max-width:calc(70vh*1.777);width:100%;height:100%;" width="1920" height="1080" ></canvas> <canvas id="canvas" style="display:none;max-height:70vh;max-width:calc(70vh*1.777);width:100%;height:100%;" width="1920" height="1080" ></canvas>
<input placeholder="Enter a VDO.Ninja View URL here" id="viewlink" style="display:block;" onchange="loadIframe();"/> <input placeholder="Enter a VDO.Ninja View URL here" id="viewlink" style="display:block;" onchange="loadIframe();"/>
<label for="hori">FOA-Horizontal</label> <label for="hori">FOA-Horizontal</label>
<input type="range" id="hori" name="hori" value="63" title="63" min="40" max="80" title="67" onchange="updateHor(this);"> <input type="range" id="hori" name="hori" value="63" title="63" min="40" max="80" title="67" onchange="updateHor(this);">
<label for="vert">FOA-Vertical</label> <label for="vert">FOA-Vertical</label>
<input type="range" id="vert" name="vert" value="50" title="50" min="30" max="70" onchange="updateVer(this);"> <input type="range" id="vert" name="vert" value="50" title="50" min="30" max="70" onchange="updateVer(this);">
<label for="draw">Draw Delay</label> <label for="draw">Draw Delay</label>
<input type="range" id="draw" name="draw" value="110" title="110" min="0" max="500" style="width:500px" onchange="updateDelay(this);"><br /><br /> <input type="range" id="draw" name="draw" value="110" title="110" min="0" max="500" style="width:500px" onchange="updateDelay(this);"><br /><br />
Add &sensor to the push link to send data; see: <a target="_blank" href="https://docs.vdo.ninja/source-settings/sensor">https://docs.vdo.ninja/source-settings/sensor</a> Add &sensor to the push link to send data; see: <a target="_blank" href="https://docs.vdo.ninja/source-settings/sensor">https://docs.vdo.ninja/source-settings/sensor</a>
<div id="container"> <div id="container">
</div> </div>
<script> <script>
// https://www.camerafv5.com/devices/manufacturers/google/pixel_4a_sunfish_1/ ; pixel 4a specs // https://www.camerafv5.com/devices/manufacturers/google/pixel_4a_sunfish_1/ ; pixel 4a specs
var horFOA = 49.6; var horFOA = 49.6;
var verFOA = 63.3; var verFOA = 63.3;
var drawDelay = 110; var drawDelay = 110;
function updateHor(hor){ function updateHor(hor){
horFOA = parseInt(hor.value); horFOA = parseInt(hor.value);
hor.title = horFOA; hor.title = horFOA;
} }
function updateVer(ver){ function updateVer(ver){
verFOA = parseInt(ver.value); verFOA = parseInt(ver.value);
ver.title = verFOA; ver.title = verFOA;
} }
function updateDelay(time){ function updateDelay(time){
drawDelay = parseInt(time.value); drawDelay = parseInt(time.value);
time.title = drawDelay; time.title = drawDelay;
} }
function loadIframe(url=false){ // this is pretty important if you want to avoid camera permission popup problems. You can also call it automatically via: <body onload=>loadIframe();"> , but don't call it before the page loads. function loadIframe(url=false){ // this is pretty important if you want to avoid camera permission popup problems. You can also call it automatically via: <body onload=>loadIframe();"> , but don't call it before the page loads.
var canvas = document.getElementById("canvas"); var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled= false; ctx.imageSmoothingEnabled= false;
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
if (url){ if (url){
var iframesrc = url; var iframesrc = url;
} else { } else {
var iframesrc = document.getElementById("viewlink").value; var iframesrc = document.getElementById("viewlink").value;
} }
console.log(iframesrc); console.log(iframesrc);
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink")); document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
document.getElementById("canvas").style.display="block"; document.getElementById("canvas").style.display="block";
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
if (iframesrc==""){ if (iframesrc==""){
iframesrc="./"; iframesrc="./";
} }
iframe.src = iframesrc; iframe.src = iframesrc;
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer); document.getElementById("container").appendChild(iframeContainer);
var videos = iframe.contentWindow.document.querySelectorAll("video"); var videos = iframe.contentWindow.document.querySelectorAll("video");
var sensors = {}; var sensors = {};
function drawFrame(vid){ function drawFrame(vid){
try { try {
if (sensors.mag){ // androids may not support this. if (sensors.mag){ // androids may not support this.
var angle = 1.5 * Math.PI - Math.atan2(sensors.mag.y,sensors.mag.x); var angle = 1.5 * Math.PI - Math.atan2(sensors.mag.y,sensors.mag.x);
var startPixel = (angle / ( 2 * Math.PI)) * 1920; var startPixel = (angle / ( 2 * Math.PI)) * 1920;
var endPixel = (verFOA/360) * 1920 + startPixel; var endPixel = (verFOA/360) * 1920 + startPixel;
} else if (sensors.ori){ } else if (sensors.ori){
var angle = sensors.ori.a; var angle = sensors.ori.a;
var frontToBack = sensors.ori.b; var frontToBack = sensors.ori.b;
var leftToRight = sensors.ori.g; var leftToRight = sensors.ori.g;
var startPixel = Math.floor((angle / 360) * 1920); var startPixel = Math.floor((angle / 360) * 1920);
var width = Math.floor((verFOA/360) * 1920); var width = Math.floor((verFOA/360) * 1920);
var height = vid.videoHeight*(width/vid.videoWidth); var height = vid.videoHeight*(width/vid.videoWidth);
var h_offset = Math.floor(((frontToBack+(verFOA/2))/180 * 1080)-540); var h_offset = Math.floor(((frontToBack+(verFOA/2))/180 * 1080)-540);
var w_offset = Math.floor((leftToRight+horFOA)/180 * 1920); var w_offset = Math.floor((leftToRight+horFOA)/180 * 1920);
} }
setTimeout(function(a1,a2,a3,a4,a5){ setTimeout(function(a1,a2,a3,a4,a5){
ctx.filter = 'blur(4px)'; ctx.filter = 'blur(4px)';
ctx.drawImage(a1,a2,a3,a4,a5); ctx.drawImage(a1,a2,a3,a4,a5);
ctx.filter = "none"; ctx.filter = "none";
ctx.drawImage(a1,a2,a3,a4,a5); ctx.drawImage(a1,a2,a3,a4,a5);
}, drawDelay, vid, startPixel-w_offset, h_offset, width, height); }, drawDelay, vid, startPixel-w_offset, h_offset, width, height);
} catch(e){console.error(e);} } catch(e){console.error(e);}
}; };
setInterval(function(){ setInterval(function(){
if (videos.length){ if (videos.length){
if ("UUID" in sensors){ if ("UUID" in sensors){
if (videos[0].id !== "videosource_"+sensors.UUID){ if (videos[0].id !== "videosource_"+sensors.UUID){
videos = iframe.contentWindow.document.querySelectorAll("video#videosource_"+sensors.UUID); videos = iframe.contentWindow.document.querySelectorAll("video#videosource_"+sensors.UUID);
} }
if (videos.length){ if (videos.length){
drawFrame(videos[0]); drawFrame(videos[0]);
} }
} }
} }
},100); },100);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("stats" in e.data){ if ("stats" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
//console.log(e.data.stats); //console.log(e.data.stats);
var out = "<br />total_inbound_connections:"+e.data.stats.total_inbound_connections; var out = "<br />total_inbound_connections:"+e.data.stats.total_inbound_connections;
out += "<br />total_outbound_connections:"+e.data.stats.total_outbound_connections; out += "<br />total_outbound_connections:"+e.data.stats.total_outbound_connections;
for (var streamID in e.data.stats.inbound_stats){ for (var streamID in e.data.stats.inbound_stats){
out += "<br /><br /><b>streamID:</b> "+streamID+"<br />"; out += "<br /><br /><b>streamID:</b> "+streamID+"<br />";
out += printValues(e.data.stats.inbound_stats[streamID]); out += printValues(e.data.stats.inbound_stats[streamID]);
} }
outputWindow.innerHTML = out; outputWindow.innerHTML = out;
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("gotChat" in e.data){ if ("gotChat" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = e.data.gotChat.msg; outputWindow.innerHTML = e.data.gotChat.msg;
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("action" in e.data){ if ("action" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "child-page-action: "+e.data.action+"<br />"; outputWindow.innerHTML = "child-page-action: "+e.data.action+"<br />";
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
console.log(e.data.action); console.log(e.data.action);
if (e.data.action == "new-view-connection"){ if (e.data.action == "new-view-connection"){
setTimeout(function(){ setTimeout(function(){
videos = iframe.contentWindow.document.querySelectorAll("video"); videos = iframe.contentWindow.document.querySelectorAll("video");
console.log(videos); console.log(videos);
},500); },500);
} }
} }
if ("streamIDs" in e.data){ if ("streamIDs" in e.data){
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.innerHTML = "child-page-action: streamIDs<br />"; outputWindow.innerHTML = "child-page-action: streamIDs<br />";
for (var key in e.data.streamIDs) { for (var key in e.data.streamIDs) {
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n"; outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
} }
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
} }
if ("loudness" in e.data){ if ("loudness" in e.data){
//console.log(e.data); //console.log(e.data);
if (document.getElementById("loudness")){ if (document.getElementById("loudness")){
outputWindow = document.getElementById("loudness"); outputWindow = document.getElementById("loudness");
} else { } else {
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
outputWindow.id = "loudness"; outputWindow.id = "loudness";
} }
outputWindow.innerHTML = "child-page-action: loudness<br />"; outputWindow.innerHTML = "child-page-action: loudness<br />";
for (var key in e.data.loudness) { for (var key in e.data.loudness) {
outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n";
} }
outputWindow.style.border="1px black"; outputWindow.style.border="1px black";
} }
if ("sensors" in e.data){ if ("sensors" in e.data){
sensors = e.data.sensors; sensors = e.data.sensors;
if (document.getElementById("sensors")){ if (document.getElementById("sensors")){
outputWindow = document.getElementById("sensors"); outputWindow = document.getElementById("sensors");
} else { } else {
var outputWindow = document.createElement("div"); var outputWindow = document.createElement("div");
outputWindow.style.border="1px dotted black"; outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow); iframeContainer.appendChild(outputWindow);
outputWindow.id = "sensors"; outputWindow.id = "sensors";
console.log(sensors); console.log(sensors);
} }
outputWindow.innerHTML = "child-page-action: sensors<br /><br />"; outputWindow.innerHTML = "child-page-action: sensors<br /><br />";
for (var key in e.data.sensors.lin) { for (var key in e.data.sensors.lin) {
outputWindow.innerHTML += key + " linear: " + e.data.sensors.lin[key] + "<br />"; outputWindow.innerHTML += key + " linear: " + e.data.sensors.lin[key] + "<br />";
} }
for (var key in e.data.sensors.acc) { for (var key in e.data.sensors.acc) {
outputWindow.innerHTML += key + " acceleration: " + e.data.sensors.acc[key] + "<br />"; outputWindow.innerHTML += key + " acceleration: " + e.data.sensors.acc[key] + "<br />";
} }
for (var key in e.data.sensors.gyro) { for (var key in e.data.sensors.gyro) {
outputWindow.innerHTML += key + " gyro: " + e.data.sensors.gyro[key] + "<br />"; outputWindow.innerHTML += key + " gyro: " + e.data.sensors.gyro[key] + "<br />";
} }
for (var key in e.data.sensors.mag) { for (var key in e.data.sensors.mag) {
outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />"; outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
} }
for (var key in e.data.sensors.ori) { for (var key in e.data.sensors.ori) {
outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />"; outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
} }
outputWindow.style.border="1px black"; outputWindow.style.border="1px black";
} }
}); });
} }
function printValues( obj) { function printValues( obj) {
var out = ""; var out = "";
for (var key in obj) { for (var key in obj) {
if (typeof obj[key] === "object") { if (typeof obj[key] === "object") {
out +="<br />"; out +="<br />";
out += printValues(obj[key]); out += printValues(obj[key]);
} else { } else {
out +="<b>"+key+"</b>: "+obj[key]+"<br />"; out +="<b>"+key+"</b>: "+obj[key]+"<br />";
} }
} }
return out; return out;
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,185 +1,185 @@
<html> <html>
<head><title>SocialStream + Video</title> <head><title>SocialStream + Video</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" /> <meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
position:absolute; position:absolute;
display:block; display:block;
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
} }
h1{ h1{
color: white; color: white;
font-family: verdana; font-family: verdana;
margin: 10px; margin: 10px;
} }
#container2{ #container2{
width:100vw; width:100vw;
height:100vh; height:100vh;
position: fixed; position: fixed;
top: 0; top: 0;
left:0; left:0;
display:none; display:none;
z-index:2; z-index:2;
} }
#container1{ #container1{
width:100vw; width:100vw;
height:100vh; height:100vh;
position: fixed; position: fixed;
top: 0; top: 0;
left:0; left:0;
display:none; display:none;
} }
iframe{ iframe{
width:100vw; width:100vw;
height:100vh; height:100vh;
} }
@media screen and (orientation:portrait) { @media screen and (orientation:portrait) {
#container2{ #container2{
} }
#container1{ #container1{
} }
iframe{ iframe{
} }
} }
@media screen and (orientation:landscape) { @media screen and (orientation:landscape) {
#container2{ #container2{
} }
#container1{ #container1{
} }
iframe{ iframe{
} }
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container2"></div> <div id="container2"></div>
<div id="container1" ></div> <div id="container1" ></div>
<div id="selectChatSource"> <div id="selectChatSource">
<h1>Which social integration are you adding?</h1> <h1>Which social integration are you adding?</h1>
</div> </div>
<div id="clean"> <div id="clean">
<h1>Use VDO.Ninja and SocialStream chat at the same time</h1> <h1>Use VDO.Ninja and SocialStream chat at the same time</h1>
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" /> <input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
<input placeholder="Enter the SocialStream URL" id="social" type="text" /> <input placeholder="Enter the SocialStream URL" id="social" type="text" />
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button> <button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
</div> </div>
<script> <script>
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Announce the new orientation number // Announce the new orientation number
// alert(window.orientation); // alert(window.orientation);
}, false); }, false);
function removeStorage(cname){ function removeStorage(cname){
localStorage.removeItem(cname); localStorage.removeItem(cname);
} }
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date(); var now = new Date();
var item = { var item = {
value: cvalue, value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000), expiry: now.getTime() + (hours * 60 * 60 * 1000),
}; };
try{ try{
localStorage.setItem(cname, JSON.stringify(item)); localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);} }catch(e){errorlog(e);}
} }
function getStorage(cname) { function getStorage(cname) {
try { try {
var itemStr = localStorage.getItem(cname); var itemStr = localStorage.getItem(cname);
} catch(e){ } catch(e){
errorlog(e); errorlog(e);
return; return;
} }
if (!itemStr) { if (!itemStr) {
return ""; return "";
} }
var item = JSON.parse(itemStr); var item = JSON.parse(itemStr);
var now = new Date(); var now = new Date();
if (now.getTime() > item.expiry) { if (now.getTime() > item.expiry) {
localStorage.removeItem(cname); localStorage.removeItem(cname);
return ""; return "";
} }
return item.value; return item.value;
} }
if (getStorage("SocialStreamChatLink")){ if (getStorage("SocialStreamChatLink")){
document.getElementById("social").value = getStorage("SocialStreamChatLink"); document.getElementById("social").value = getStorage("SocialStreamChatLink");
} }
if (getStorage("vdoNinjaSocialStreamURL")){ if (getStorage("vdoNinjaSocialStreamURL")){
document.getElementById("viewlink").value = getStorage("vdoNinjaSocialStreamURL"); document.getElementById("viewlink").value = getStorage("vdoNinjaSocialStreamURL");
} }
function loadIframes(url=false){ function loadIframes(url=false){
var roomname = document.getElementById("viewlink").value; var roomname = document.getElementById("viewlink").value;
var room2 = document.getElementById("social").value; var room2 = document.getElementById("social").value;
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean")); document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
document.getElementById("container1").style.display="inline-block"; document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block"; document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
path = path.replace("/examples",""); path = path.replace("/examples","");
if (roomname.startsWith("https://")){ if (roomname.startsWith("https://")){
var room1 = roomname; var room1 = roomname;
} else { } else {
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader"; var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader";
} }
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room1; iframe.src = room1;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container1").appendChild(iframeContainer); document.getElementById("container1").appendChild(iframeContainer);
setStorage("SocialStreamChatLink", room2); setStorage("SocialStreamChatLink", room2);
setStorage("vdoNinjaSocialStreamURL", room1); setStorage("vdoNinjaSocialStreamURL", room1);
setTimeout(function(){ setTimeout(function(){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room2; iframe.src = room2;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container2").appendChild(iframeContainer); document.getElementById("container2").appendChild(iframeContainer);
},3000); },3000);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,172 +1,172 @@
<html> <html>
<head> <head>
<meta charset="utf8" /> <meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>VDON Chat Overlay</title> <title>VDON Chat Overlay</title>
<style> <style>
@font-face { @font-face {
font-family: 'Cousine'; font-family: 'Cousine';
src: url('fonts/Cousine-Bold.ttf') format('truetype'); src: url('fonts/Cousine-Bold.ttf') format('truetype');
} }
body { body {
margin:0; margin:0;
padding:0 10px; padding:0 10px;
height:100%; height:100%;
border: 0; border: 0;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
position:absolute; position:absolute;
bottom:0; bottom:0;
overflow:hidden; overflow:hidden;
max-width:100%; max-width:100%;
} }
ul { ul {
margin:0; margin:0;
background-color: #0000; background-color: #0000;
color: white; color: white;
font-family: Cousine, monospace; font-family: Cousine, monospace;
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1em; line-height: 1.1em;
letter-spacing: 0.0em; letter-spacing: 0.0em;
text-transform: uppercase; text-transform: uppercase;
padding: 0em; padding: 0em;
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1); text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
max-width:100%; max-width:100%;
} }
ul li { ul li {
background-color: black; background-color: black;
padding: 8px 8px 0px 8px; padding: 8px 8px 0px 8px;
margin:0; margin:0;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
max-width:100%; max-width:100%;
} }
a { a {
color:white; color:white;
font-size:1.2em; font-size:1.2em;
text-transform: none; text-transform: none;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
hyphens: auto; hyphens: auto;
} }
</style> </style>
<script> <script>
(function (w) { (function (w) {
w.URLSearchParams = w.URLSearchParams =
w.URLSearchParams || w.URLSearchParams ||
function (searchString) { function (searchString) {
var self = this; var self = this;
self.searchString = searchString; self.searchString = searchString;
self.get = function (name) { self.get = function (name) {
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec( var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
self.searchString self.searchString
); );
if (results == null) { if (results == null) {
return null; return null;
} else { } else {
return decodeURI(results[1]) || 0; return decodeURI(results[1]) || 0;
} }
}; };
}; };
})(window); })(window);
var urlParams = new URLSearchParams(window.location.search); var urlParams = new URLSearchParams(window.location.search);
function loadIframe() { function loadIframe() {
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
var view= ""; var view= "";
if (urlParams.has("view")) { if (urlParams.has("view")) {
view = "&view="+(urlParams.get("view") || ""); view = "&view="+(urlParams.get("view") || "");
} }
var room=""; var room="";
if (urlParams.has("room")) { if (urlParams.has("room")) {
room = "&room="+urlParams.get("room"); room = "&room="+urlParams.get("room");
} }
var password=""; var password="";
if (urlParams.has("password")) { if (urlParams.has("password")) {
password = "&password="+urlParams.get("password"); password = "&password="+urlParams.get("password");
} }
iframe.allow = "autoplay"; iframe.allow = "autoplay";
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password; var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
iframe.src = srcString; iframe.src = srcString;
iframe.style.width="0"; iframe.style.width="0";
iframe.style.height="0"; iframe.style.height="0";
iframe.style.border="0"; iframe.style.border="0";
document.body.appendChild(iframe); document.body.appendChild(iframe);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.log(e); console.log(e);
if ("gotChat" in e.data){ if ("gotChat" in e.data){
logData(e.data.gotChat.label,e.data.gotChat.msg); logData(e.data.gotChat.label,e.data.gotChat.msg);
} }
}); });
} }
function printValues(obj) { function printValues(obj) {
var out = ""; var out = "";
for (var key in obj) { for (var key in obj) {
if (typeof obj[key] === "object") { if (typeof obj[key] === "object") {
out += "<br />"; out += "<br />";
out += printValues(obj[key]); out += printValues(obj[key]);
} else { } else {
if (key.startsWith("_")) { if (key.startsWith("_")) {
} else { } else {
out += "<b>" + key + "</b>: " + obj[key] + "<br />"; out += "<b>" + key + "</b>: " + obj[key] + "<br />";
} }
} }
} }
return out; return out;
} }
function logData(type, data) { function logData(type, data) {
var log = document.body.getElementsByTagName("ul")[0]; var log = document.body.getElementsByTagName("ul")[0];
var entry = document.createElement('li'); var entry = document.createElement('li');
if (type){ if (type){
type = "<i>"+type+"</i>"; type = "<i>"+type+"</i>";
} }
entry.innerHTML = type + data; entry.innerHTML = type + data;
//setTimeout(function(entry){ // hide message after 60 seconds //setTimeout(function(entry){ // hide message after 60 seconds
// entry.innerHTML=""; // entry.innerHTML="";
// entry.remove(); // entry.remove();
// },60000,entry); // },60000,entry);
log.appendChild(entry); log.appendChild(entry);
} }
</script> </script>
</head> </head>
<body onload="loadIframe();"> <body onload="loadIframe();">
<ul></ul> <ul></ul>
</body> </body>
</html> </html>

View File

@ -1,289 +1,289 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style> <style>
html { html {
border:0; border:0;
margin:0; margin:0;
outline:0; outline:0;
overflow: hidden; overflow: hidden;
} }
video { video {
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=), none; cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=), none;
user-select: none; user-select: none;
} }
body { body {
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
background-color: -webkit-linear-gradient(to top, #363644, 50%, #151b29); /* Chrome 10-25, Safari 5.1-6 */ background-color: -webkit-linear-gradient(to top, #363644, 50%, #151b29); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to top, #363644, 50%, #151b29); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ background: linear-gradient(to top, #363644, 50%, #151b29); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
font-size: 2em; font-size: 2em;
font-family: Helvetica, Arial, sans-serif; font-family: Helvetica, Arial, sans-serif;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
border:0; border:0;
outline:0; outline:0;
} }
button.glyphicon-button:focus, button.glyphicon-button:focus,
button.glyphicon-button:active:focus, button.glyphicon-button:active:focus,
button.glyphicon-button.active:focus, button.glyphicon-button.active:focus,
button.glyphicon-button.focus, button.glyphicon-button.focus,
button.glyphicon-button:active.focus, button.glyphicon-button:active.focus,
button.glyphicon-button.active.focus { button.glyphicon-button.active.focus {
outline: none !important; outline: none !important;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width: 100vw; width: 100vw;
height: calc(100vh - 100px); height: calc(100vh - 100px);
transform: rotate(0deg); transform: rotate(0deg);
transform-origin: 0 0; transform-origin: 0 0;
left: 0; left: 0;
position: absolute; position: absolute;
top: 100px; top: 100px;
} }
.gobutton { .gobutton {
font-size:min(30px, 2vw); font-size:min(30px, 2vw);
font-weight: bold; font-weight: bold;
border: none; border: none;
background: #6aab23; background: #6aab23;
display: flex; display: flex;
border-radius: 0px; border-radius: 0px;
border-top-right-radius: 10px; border-top-right-radius: 10px;
border-bottom-right-radius: 10px; border-bottom-right-radius: 10px;
box-shadow: 0 12px 15px -10px #5ca70b, 0 2px 0px #6aab23; box-shadow: 0 12px 15px -10px #5ca70b, 0 2px 0px #6aab23;
color: white; color: white;
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
align-items: center; align-items: center;
padding: 0 min(1vw, 10px); padding: 0 min(1vw, 10px);
margin: min(1vw, 10px) 0 ; margin: min(1vw, 10px) 0 ;
} }
.details{ .details{
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
border: none; border: none;
background: #555; background: #555;
display: flex; display: flex;
border-radius: 0px; border-radius: 0px;
border-top-right-radius: 10px; border-top-right-radius: 10px;
border-bottom-right-radius: 10px; border-bottom-right-radius: 10px;
box-shadow: 0 12px 15px -10px #444, 0 2px 0px #555; box-shadow: 0 12px 15px -10px #444, 0 2px 0px #555;
color: white; color: white;
box-sizing: border-box; box-sizing: border-box;
align-items: center; align-items: center;
padding: 0 min(1vw, 10px); padding: 0 min(1vw, 10px);
} }
#header{ #header{
width:100%; width:100%;
background-color: #101520; background-color: #101520;
} }
.changeText { .changeText {
font-size: max(1vw, 10px) font-size: max(1vw, 10px)
align-self: center; align-self: center;
width: 100%; width: 100%;
padding: min(1vw, 10px); padding: min(1vw, 10px);
font-weight: bold; font-weight: bold;
background: white; background: white;
border: 4px solid white; border: 4px solid white;
box-shadow: 0px 30px 40px -32px #6aab23, 0 2px 0px #6aab23; box-shadow: 0px 30px 40px -32px #6aab23, 0 2px 0px #6aab23;
border-top-left-radius: 10px; border-top-left-radius: 10px;
border-bottom-left-radius: 10px; border-bottom-left-radius: 10px;
transition: all 0.2s linear; transition: all 0.2s linear;
box-sizing: border-box; box-sizing: border-box;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
margin: min(1vw, 10px) 0; margin: min(1vw, 10px) 0;
} }
.changeText:focus { .changeText:focus {
outline: none; outline: none;
} }
select.changetext{ select.changetext{
padding: .1vw; padding: .1vw;
} }
.container{ .container{
width:100%; width:100%;
top:0; top:0;
position:absolute; position:absolute;
left:0; left:0;
margin: auto auto; margin: auto auto;
height: 70px; height: 70px;
} }
label { label {
font: white; font: white;
font-size: 1vw; font-size: 1vw;
color: white; color: white;
} }
input[type='checkbox'] { input[type='checkbox'] {
-webkit-appearance:none; -webkit-appearance:none;
width:30px; width:30px;
height:30px; height:30px;
background:white; background:white;
border-radius:5px; border-radius:5px;
border:2px solid #555; border:2px solid #555;
cursor: pointer; cursor: pointer;
} }
input[type='checkbox']:checked { input[type='checkbox']:checked {
background: #1A1; background: #1A1;
} }
#audioOutput, #lastUrls { #audioOutput, #lastUrls {
font-size: calc(16px + 0.3vw); font-size: calc(16px + 0.3vw);
width: 730px; width: 730px;
height: 100%; height: 100%;
flex: 20; flex: 20;
border-radius: 10px; border-radius: 10px;
padding: min(1vw, 10px); padding: min(1vw, 10px);
background: #eaeaea; background: #eaeaea;
cursor:pointer; cursor:pointer;
} }
label[for="audioOutput"] { label[for="audioOutput"] {
font-size: min(30px, 2vw); font-size: min(30px, 2vw);
color: #FE53BB; color: #FE53BB;
text-shadow: 0px 0px 30px #fe53bb; text-shadow: 0px 0px 30px #fe53bb;
padding-right: 10px; padding-right: 10px;
} }
label[for="changeText"] { label[for="changeText"] {
font-size: min(30px, 2.5vw); font-size: min(30px, 2.5vw);
color: #00F6FF; color: #00F6FF;
text-shadow: 0px 0px 30px #00f6ff; text-shadow: 0px 0px 30px #00f6ff;
padding-right: 10px; padding-right: 10px;
margin:auto auto; margin:auto auto;
} }
label[for="lastUrls"] { label[for="lastUrls"] {
font-size: min(min(30px, 2vw), 2vw); font-size: min(min(30px, 2vw), 2vw);
color: #1a1; color: #1a1;
text-shadow: 0px 0px 30px #1a1; text-shadow: 0px 0px 30px #1a1;
padding-right: 10px; padding-right: 10px;
cursor: pointer; cursor: pointer;
} }
div#audioOutputContainer, #history { div#audioOutputContainer, #history {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: center; justify-content: center;
margin: 4em; margin: 4em;
} }
#messageDiv { #messageDiv {
font-size: .7em; font-size: .7em;
color: #DDD; color: #DDD;
transition: all 0.5s linear; transition: all 0.5s linear;
font-style: italic; font-style: italic;
opacity: 0; opacity: 0;
text-align: center; text-align: center;
margin: 10px 0; margin: 10px 0;
} }
div.urlInput { div.urlInput {
padding: 0 0 4vh 0; padding: 0 0 4vh 0;
} }
label[for="audioOutput"], label[for="lastUrls"] { label[for="audioOutput"], label[for="lastUrls"] {
font-size: min(30px, 2vw); font-size: min(30px, 2vw);
} }
#warning4mac, #electronVersion { #warning4mac, #electronVersion {
background: #8500f7; background: #8500f7;
box-shadow: 0px 0px 50px 10px #8500f7ab, inset 0px 0px 10px 2px #8d08ffba; box-shadow: 0px 0px 50px 10px #8500f7ab, inset 0px 0px 10px 2px #8d08ffba;
border: 2px solid #8500f7; border: 2px solid #8500f7;
border-radius: 10px; border-radius: 10px;
width: 90%; width: 90%;
padding:min(1vw, 10px); padding:min(1vw, 10px);
margin:0 auto; margin:0 auto;
color:white; color:white;
font-size: 1.40px; font-size: 1.40px;
margin-bottom: 20px; margin-bottom: 20px;
} }
#warning4mac a, #electronVersion a { #warning4mac a, #electronVersion a {
color:white; color:white;
} }
ul#lastUrls { ul#lastUrls {
list-style: none; list-style: none;
background: #101520; background: #101520;
color: white; color: white;
padding: min(1vw, 10px); padding: min(1vw, 10px);
} }
ul#lastUrls li { ul#lastUrls li {
padding: 5px 0px; padding: 5px 0px;
} }
ul#lastUrls li:nth-child(even) { ul#lastUrls li:nth-child(even) {
background-color: #182031; background-color: #182031;
} }
.inputCombo { .inputCombo {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-grow: 1; flex-grow: 1;
} }
#version{ #version{
margin: 0 auto; margin: 0 auto;
font-size: 30%; font-size: 30%;
display: inline-block; display: inline-block;
color: #000A; color: #000A;
} }
h3 { h3 {
color: #b0e3ff; color: #b0e3ff;
} }
.hidden{ .hidden{
display:none; display:none;
opacity:0; opacity:0;
visibility:none; visibility:none;
width:0; width:0;
height:0 height:0
} }
.hidebutton{ .hidebutton{
font-size:min(30px, 2vw); font-size:min(30px, 2vw);
font-weight: bold; font-weight: bold;
border: none; border: none;
background: #ab236a; background: #ab236a;
display: flex; display: flex;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 12px 15px -10px #a70b5c, 0 2px 0px #ab236a; box-shadow: 0 12px 15px -10px #a70b5c, 0 2px 0px #ab236a;
color: white; color: white;
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
align-items: center; align-items: center;
padding: 0 min(1vw, 10px); padding: 0 min(1vw, 10px);
margin: min(1vw, 5px) 0; margin: min(1vw, 5px) 0;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container" id="container"> <div class="container" id="container">
</div> </div>
<script> <script>
location.href = './teleprompter.html'; location.href = 'teleprompter.html';
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,22 +1,22 @@
<html> <html>
<head> <head>
<style> <style>
body { body {
background-color:#0000; background-color:#0000;
object-fit: contain; object-fit: contain;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
img { img {
object-fit: contain; object-fit: contain;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
</style> </style>
</head> </head>
<body> <body>
<img src='../media/vdoNinja_logo_full.png'> <img src='../media/vdoNinja_logo_full.png'>
</body> </body>
</html> </html>

View File

@ -1,358 +1,358 @@
<html> <html>
<head><title>Twitch + Video</title> <head><title>Twitch + Video</title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#000; background-color:#000;
width:100vw; width:100vw;
height:100vh; height:100vh;
color:white; color:white;
overscroll-behavior: contain; overscroll-behavior: contain;
overflow: hidden; overflow: hidden;
display:block; display:block;
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
} }
input{ input{
padding:10px 2px; padding:10px 2px;
width:calc(100vw - 60px); width:calc(100vw - 60px);
font-size: min(4vw, 20px); font-size: min(4vw, 20px);
margin:10px 0 30px 0; margin:10px 0 30px 0;
z-index: 1000; z-index: 1000;
color:black; color:black;
display:block; display:block;
} }
#clean{ #clean{
max-width:100%; max-width:100%;
width: 90vw; width: 90vw;
max-width: 100%; max-width: 100%;
display: inline-block; display: inline-block;
} }
h1{ h1{
color: white; color: white;
font-family: verdana; font-family: verdana;
margin: 10px 3px 30px 3px; margin: 10px 3px 30px 3px;
color:white; color:white;
} }
button { button {
font-size: min(10vw, 28px); font-size: min(10vw, 28px);
color:black; color:black;
display:inline-block; display:inline-block;
} }
#controlbar { #controlbar {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background-color: #0000; background-color: #0000;
height: max(7vh, 50px); height: max(7vh, 50px);
width: 100%; width: 100%;
display: none; display: none;
justify-content: center; justify-content: center;
} }
#container2{ #container2{
background-color:#000; background-color:#000;
} }
#container1{ #container1{
transition: all ease 0.4ms; transition: all ease 0.4ms;
} }
#controlbar button{ #controlbar button{
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
height: 100%; height: 100%;
background-color: black; background-color: black;
color: white; color: white;
box-shadow: inset 0 0 20px 7px #FFF7; box-shadow: inset 0 0 20px 7px #FFF7;
font-size: 1.0em; font-size: 1.0em;
border-radius: 19px; border-radius: 19px;
max-width: 20%; max-width: 20%;
margin: 0; margin: 0;
padding: 0 2px; padding: 0 2px;
} }
.pressed { .pressed {
background-color: #A00!important; background-color: #A00!important;
} }
.loading { .loading {
width: 100%!important; height:100%!important; position: absolute; top: 0; right:unset;left:0;opacity:0%; animation: fadeIn 3s; width: 100%!important; height:100%!important; position: absolute; top: 0; right:unset;left:0;opacity:0%; animation: fadeIn 3s;
} }
.fullwindow { .fullwindow {
height: calc(100% - max(7vh, 50px)) !important; width:100%!important; position: absolute; top: max(7vh, 50px)!important; right:unset!important;left:0!important; height: calc(100% - max(7vh, 50px)) !important; width:100%!important; position: absolute; top: max(7vh, 50px)!important; right:unset!important;left:0!important;
} }
@keyframes fadeIn { @keyframes fadeIn {
0% { opacity: 0; } 0% { opacity: 0; }
10% { opacity: 0; } 10% { opacity: 0; }
50% { opacity: 0.2; } 50% { opacity: 0.2; }
100% { opacity: 1; } 100% { opacity: 1; }
} }
@media screen and (orientation:portrait) { @media screen and (orientation:portrait) {
#container2{ #container2{
width:100%;height:100%;display:none; width:100%;height:100%;display:none;
} }
#container1{ #container1{
width: 50vw;height: 50vh; display:none; top: 0; right: 0%; position: absolute; width: 50vw;height: 50vh; display:none; top: 0; right: 0%; position: absolute;
} }
} }
@media screen and (orientation:landscape) { @media screen and (orientation:landscape) {
#container2{ #container2{
width:60vw;height:100%;display:none; width:60vw;height:100%;display:none;
z-index:5; z-index:5;
} }
#container1{ #container1{
width: 50vw; height:100%; max-height: calc(100vh - 100px); display:none; position: fixed; top: 0; right: -10vw; width: 50vw; height:100%; max-height: calc(100vh - 100px); display:none; position: fixed; top: 0; right: -10vw;
} }
} }
.hide{ .hide{
width: 1px!important; width: 1px!important;
height: 1px!important; height: 1px!important;
opacity: 0.5; opacity: 0.5;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container2" ></div> <div id="container2" ></div>
<div id="container1" class="loading"></div> <div id="container1" class="loading"></div>
<div id="controlbar" > <div id="controlbar" >
<button id="hidepreview">Hide Preview</button> <button id="hidepreview">Hide Preview</button>
<button id="mutemic">Mute Mic</button> <button id="mutemic">Mute Mic</button>
<button id="mutevideo">Mute Video</button> <button id="mutevideo">Mute Video</button>
<button id="togglesettings">Settings</button> <button id="togglesettings">Settings</button>
<button id="hangup">Hangup</button> <button id="hangup">Hangup</button>
</div> </div>
<div id="clean"> <div id="clean">
<h1>Use VDO.Ninja and Twitch chat at the same time</h1> <h1>Use VDO.Ninja and Twitch chat at the same time</h1>
VDO.Ninja Stream ID or URL: VDO.Ninja Stream ID or URL:
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" /> <input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
Twitch Username or URL: Twitch Username or URL:
<input placeholder="Enter the Twitch channel name" id="twitch" type="text" /> <input placeholder="Enter the Twitch channel name" id="twitch" type="text" />
<button onclick="loadIframes()" style="background-color: #d1fed1;; padding:10px;margin:10px;">START</button> <button onclick="loadIframes()" style="background-color: #d1fed1;; padding:10px;margin:10px;">START</button>
<button onclick="clearInput()" style="background-color: #f4cccc;margin:10px 0px 10px 10vh;padding:10px;">CLEAR</button> <button onclick="clearInput()" style="background-color: #f4cccc;margin:10px 0px 10px 10vh;padding:10px;">CLEAR</button>
<br /><br /><br /> <br /><br /><br />
<p> <p>
This app lets you publish video/audio via VDO.Ninja at the same time as viewing your Twitch chat.<br /><br />If you have feature requests or suggestions, please report them at https://discord.vdo.ninja in the #feature-request channel. This app lets you publish video/audio via VDO.Ninja at the same time as viewing your Twitch chat.<br /><br />If you have feature requests or suggestions, please report them at https://discord.vdo.ninja in the #feature-request channel.
</p> </p>
</div> </div>
<script> <script>
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Announce the new orientation number // Announce the new orientation number
// alert(window.orientation); // alert(window.orientation);
}, false); }, false);
function removeStorage(cname){ function removeStorage(cname){
localStorage.removeItem(cname); localStorage.removeItem(cname);
} }
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date(); var now = new Date();
var item = { var item = {
value: cvalue, value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000), expiry: now.getTime() + (hours * 60 * 60 * 1000),
}; };
try{ try{
localStorage.setItem(cname, JSON.stringify(item)); localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);} }catch(e){errorlog(e);}
} }
function getStorage(cname) { function getStorage(cname) {
try { try {
var itemStr = localStorage.getItem(cname); var itemStr = localStorage.getItem(cname);
} catch(e){ } catch(e){
errorlog(e); errorlog(e);
return; return;
} }
if (!itemStr) { if (!itemStr) {
return ""; return "";
} }
var item = JSON.parse(itemStr); var item = JSON.parse(itemStr);
var now = new Date(); var now = new Date();
if (now.getTime() > item.expiry) { if (now.getTime() > item.expiry) {
localStorage.removeItem(cname); localStorage.removeItem(cname);
return ""; return "";
} }
return item.value; return item.value;
} }
if (getStorage("twitchChatLink")){ if (getStorage("twitchChatLink")){
document.getElementById("twitch").value = getStorage("twitchChatLink"); document.getElementById("twitch").value = getStorage("twitchChatLink");
} }
if (getStorage("vdoNinjaTwitchURL")){ if (getStorage("vdoNinjaTwitchURL")){
document.getElementById("viewlink").value = getStorage("vdoNinjaTwitchURL"); document.getElementById("viewlink").value = getStorage("vdoNinjaTwitchURL");
} }
function clearInput(){ function clearInput(){
var confirmit = confirm("Are you sure you want to clear the input fields and local storage?"); var confirmit = confirm("Are you sure you want to clear the input fields and local storage?");
if (confirmit){ if (confirmit){
removeStorage("twitchChatLink"); removeStorage("twitchChatLink");
removeStorage("vdoNinjaTwitchURL"); removeStorage("vdoNinjaTwitchURL");
document.getElementById("viewlink").value = ""; document.getElementById("viewlink").value = "";
document.getElementById("twitch").value = ""; document.getElementById("twitch").value = "";
} }
} }
var iframe = null; var iframe = null;
function sendSelfCommand(action, value=null){ function sendSelfCommand(action, value=null){
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*'); iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
} }
var injectCSS = ` var injectCSS = `
#controlButtons{ #controlButtons{
display:none!important; display:none!important;
} }
`; `;
injectCSS = encodeURIComponent(btoa(injectCSS)); injectCSS = encodeURIComponent(btoa(injectCSS));
function loadIframes(url=false){ function loadIframes(url=false){
var roomname = document.getElementById("viewlink").value; var roomname = document.getElementById("viewlink").value;
var twitch = document.getElementById("twitch").value; var twitch = document.getElementById("twitch").value;
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean")); document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
document.getElementById("container1").style.display="inline-block"; document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block"; document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
path = path.replace("/examples",""); path = path.replace("/examples","");
if (roomname.startsWith("https://")){ if (roomname.startsWith("https://")){
var room1 = roomname; var room1 = roomname;
} else { } else {
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader&fullscreen&cleanish&b64css="+injectCSS; var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader&fullscreen&cleanish&b64css="+injectCSS;
} }
var room2 = twitch.startsWith("https://") ? twitch : `https://www.twitch.tv/embed/${twitch}/chat?darkpopout&parent=${location.hostname}`; var room2 = twitch.startsWith("https://") ? twitch : `https://www.twitch.tv/embed/${twitch}/chat?darkpopout&parent=${location.hostname}`;
iframe = document.createElement("iframe"); iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room1; iframe.src = room1;
document.getElementById("container1").appendChild(iframe); document.getElementById("container1").appendChild(iframe);
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead. /// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.warn(e.data); console.warn(e.data);
if ("action" in e.data){ if ("action" in e.data){
if (e.data.action === "seeding-started"){ if (e.data.action === "seeding-started"){
document.getElementById("controlbar").style.display="inline-flex"; document.getElementById("controlbar").style.display="inline-flex";
document.getElementById("container1").classList.remove("loading"); document.getElementById("container1").classList.remove("loading");
} }
if (e.data.action === "settings-menu-state"){ if (e.data.action === "settings-menu-state"){
if (e.data.value==true){ if (e.data.value==true){
togglesettings.dataset.value = "true"; togglesettings.dataset.value = "true";
togglesettings.classList.add("pressed"); togglesettings.classList.add("pressed");
document.getElementById("container1").classList.add("fullwindow"); document.getElementById("container1").classList.add("fullwindow");
} else { } else {
togglesettings.dataset.value = "false"; togglesettings.dataset.value = "false";
togglesettings.classList.remove("pressed"); togglesettings.classList.remove("pressed");
document.getElementById("container1").classList.remove("fullwindow"); document.getElementById("container1").classList.remove("fullwindow");
} }
} }
} }
}); });
setStorage("twitchChatLink", room2); setStorage("twitchChatLink", room2);
setStorage("vdoNinjaTwitchURL", room1); setStorage("vdoNinjaTwitchURL", room1);
setTimeout(function(){ setTimeout(function(){
var iframe2 = document.createElement("iframe"); var iframe2 = document.createElement("iframe");
iframe2.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe2.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe2.src = room2; iframe2.src = room2;
document.getElementById("container2").appendChild(iframe2); document.getElementById("container2").appendChild(iframe2);
},1000); },1000);
} }
var hangup = document.getElementById("hangup"); var hangup = document.getElementById("hangup");
hangup.onclick = function(){ hangup.onclick = function(){
iframe.contentWindow.postMessage({"hangup":true}, '*'); iframe.contentWindow.postMessage({"hangup":true}, '*');
} }
var togglesettings = document.getElementById("togglesettings"); var togglesettings = document.getElementById("togglesettings");
togglesettings.onclick = function(){ togglesettings.onclick = function(){
iframe.contentWindow.postMessage({"toggleSettings":"toggle"}, '*'); iframe.contentWindow.postMessage({"toggleSettings":"toggle"}, '*');
} }
var mutemic = document.getElementById("mutemic"); var mutemic = document.getElementById("mutemic");
mutemic.onclick = function(){ mutemic.onclick = function(){
if (this.dataset.value!=="false"){ if (this.dataset.value!=="false"){
this.dataset.value = "false"; this.dataset.value = "false";
this.classList.add("pressed"); this.classList.add("pressed");
this.innerText = "Un-Mute Mic"; this.innerText = "Un-Mute Mic";
sendSelfCommand("mic",false); sendSelfCommand("mic",false);
} else { } else {
this.classList.remove("pressed"); this.classList.remove("pressed");
this.innerText = "Mute Mic"; this.innerText = "Mute Mic";
this.dataset.value = "true"; this.dataset.value = "true";
sendSelfCommand("mic",true); sendSelfCommand("mic",true);
} }
} }
var mutevideo = document.getElementById("mutevideo"); var mutevideo = document.getElementById("mutevideo");
mutevideo.onclick = function(){ mutevideo.onclick = function(){
if (this.dataset.value!=="false"){ if (this.dataset.value!=="false"){
this.dataset.value = "false"; this.dataset.value = "false";
this.classList.add("pressed"); this.classList.add("pressed");
this.innerText = "Un-Mute camera"; this.innerText = "Un-Mute camera";
sendSelfCommand("camera",false); sendSelfCommand("camera",false);
} else { } else {
this.classList.remove("pressed"); this.classList.remove("pressed");
this.innerText = "Mute Camera"; this.innerText = "Mute Camera";
this.dataset.value = "true"; this.dataset.value = "true";
sendSelfCommand("camera",true); sendSelfCommand("camera",true);
} }
} }
var hidepreview = document.getElementById("hidepreview"); var hidepreview = document.getElementById("hidepreview");
hidepreview.onclick = function(){ hidepreview.onclick = function(){
if (this.dataset.value!=="false"){ if (this.dataset.value!=="false"){
this.dataset.value = "false"; this.dataset.value = "false";
this.classList.add("pressed"); this.classList.add("pressed");
this.innerText = "Show Preview"; this.innerText = "Show Preview";
document.getElementById("container1").classList.add("hide"); document.getElementById("container1").classList.add("hide");
document.getElementById("container2").classList.add("fullwindow"); document.getElementById("container2").classList.add("fullwindow");
} else { } else {
this.classList.remove("pressed"); this.classList.remove("pressed");
this.innerText = "Hide Preview"; this.innerText = "Hide Preview";
this.dataset.value = "true"; this.dataset.value = "true";
document.getElementById("container1").classList.remove("hide"); document.getElementById("container1").classList.remove("hide");
document.getElementById("container2").classList.remove("fullwindow"); document.getElementById("container2").classList.remove("fullwindow");
} }
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,121 +1,121 @@
<html> <html>
<head><title>Video with sensor overlayed data</title> <head><title>Video with sensor overlayed data</title>
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color: #0000; background-color: #0000;
} }
iframe { iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:100%; height:100%;
} }
#container { #container {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
display:block; display:block;
width:100%; width:100%;
height:100%; height:100%;
position:absolute; position:absolute;
top:0; top:0;
left:0; left:0;
} }
#overlay{ #overlay{
top: 0; top: 0;
border: 0; border: 0;
margin: auto; margin: auto;
padding: 5% 0; padding: 5% 0;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
z-index: 10; z-index: 10;
color: white; color: white;
font-size: 10vh; font-size: 10vh;
width: 100%; width: 100%;
background-color: #000000a3; background-color: #000000a3;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
font-family: monospace; font-family: monospace;
text-shadow: 2px 2px black; text-shadow: 2px 2px black;
} }
.hidden { .hidden {
display:none; display:none;
} }
</style> </style>
</head> </head>
<body id="main"> <body id="main">
<div id="overlay" class="hidden"></div> <div id="overlay" class="hidden"></div>
<div id="container"></div> <div id="container"></div>
<script> <script>
function loadIframe(url=false){ function loadIframe(url=false){
if (url){ if (url){
var iframesrc = url; var iframesrc = url;
} else { } else {
var iframesrc = document.getElementById("viewlink").value; var iframesrc = document.getElementById("viewlink").value;
} }
if (iframesrc==""){ if (iframesrc==""){
iframesrc="../"; iframesrc="../";
} }
var params = window.location.search || ""; var params = window.location.search || "";
if (params.startsWith("?")){ if (params.startsWith("?")){
params = params.slice(1); params = params.slice(1);
iframesrc = iframesrc + "&" + params iframesrc = iframesrc + "&" + params
} else { } else {
iframesrc = iframesrc + params iframesrc = iframesrc + params
} }
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;"; iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.src = iframesrc; iframe.src = iframesrc;
document.getElementById("container").appendChild(iframe); document.getElementById("container").appendChild(iframe);
var outputWindow = document.getElementById("overlay"); var outputWindow = document.getElementById("overlay");
//////////// LISTEN FOR EVENTS //////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod]; var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
var waiting = null; var waiting = null;
eventer(messageEvent, function (e) { eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.warn(e.data); console.warn(e.data);
if ("action" in e.data){ if ("action" in e.data){
if (e.data.action == "joining-room"){ if (e.data.action == "joining-room"){
outputWindow.innerHTML = "JOINING ROOM"; outputWindow.innerHTML = "JOINING ROOM";
waiting = setTimeout(function(){ waiting = setTimeout(function(){
outputWindow.innerHTML = "Waiting for the director to join"; outputWindow.innerHTML = "Waiting for the director to join";
outputWindow.classList.remove("hidden"); outputWindow.classList.remove("hidden");
},1000); },1000);
} else if (e.data.action == "director-connected"){ } else if (e.data.action == "director-connected"){
clearTimeout(waiting); clearTimeout(waiting);
outputWindow.innerHTML = ""; outputWindow.innerHTML = "";
outputWindow.classList.add("hidden"); outputWindow.classList.add("hidden");
} }
} }
}); });
} }
loadIframe("../"+window.location.search); loadIframe("../"+window.location.search);
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,133 +1,133 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>WebHID Demo</title> <title>WebHID Demo</title>
<style> <style>
body { body {
text-align: center; text-align: center;
} }
.button { .button {
background-color: black; background-color: black;
border: none; border: none;
color: #00FF00; color: #00FF00;
padding: 15px 32px; padding: 15px 32px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
display: inline-block; display: inline-block;
margin: 4px 2px; margin: 4px 2px;
cursor: pointer; cursor: pointer;
font-size: 24px; font-size: 24px;
} }
#connected { #connected {
font-size: 24px; font-size: 24px;
max-height:700px; max-height:700px;
overflow-y:scroll overflow-y:scroll
} }
#disconnectButton { #disconnectButton {
font-size: 24px; font-size: 24px;
} }
</style> </style>
</head> </head>
<body> <body>
<h1>STREAMDECK DEMO</h1> <h1>STREAMDECK DEMO</h1>
<img src="../media/streamdeck.png" /><br /> <img src="../media/streamdeck.png" /><br />
<input class="button" type="button" id="connectButton" value="Connect" /> <input class="button" type="button" id="connectButton" value="Connect" />
<input class="button" type="button" id="disconnectButton" style="display:none" value="Disconnect" /> <input class="button" type="button" id="disconnectButton" style="display:none" value="Disconnect" />
<div id="connected" style> <div id="connected" style>
</div> </div>
<script> <script>
const connectButton = document.getElementById("connectButton"); const connectButton = document.getElementById("connectButton");
const disconnectButton = document.getElementById("disconnectButton"); const disconnectButton = document.getElementById("disconnectButton");
const connect = document.getElementById("connect"); const connect = document.getElementById("connect");
const deviceButtonPressed = document.getElementById("deviceButtonPressed"); const deviceButtonPressed = document.getElementById("deviceButtonPressed");
var lastState = false; var lastState = false;
//productId: 0x0060, //productId: 0x0060,
//class: models_1.StreamDeckOriginal, //class: models_1.StreamDeckOriginal,
//productId: 0x0063, //productId: 0x0063,
//class: models_1.StreamDeckMini, //class: models_1.StreamDeckMini,
//productId: 0x006c, //productId: 0x006c,
//class: models_1.StreamDeckXL, //class: models_1.StreamDeckXL,
//productId: 0x006d, //productId: 0x006d,
//class: models_1.StreamDeckOriginalV2, //class: models_1.StreamDeckOriginalV2,
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
let devices = await navigator.hid.getDevices(); let devices = await navigator.hid.getDevices();
devices.forEach(device => { devices.forEach(device => {
console.log(`HID: ${device.productName}`); console.log(`HID: ${device.productName}`);
}); });
}); });
function handleInputReport(e) { function handleInputReport(e) {
console.log(e.device.productName + ": got input report " + e.reportId); console.log(e.device.productName + ": got input report " + e.reportId);
console.log(new Uint8Array(e.data.buffer)); console.log(new Uint8Array(e.data.buffer));
var data = new Uint8Array(e.data.buffer); var data = new Uint8Array(e.data.buffer);
if (lastState!==false){ if (lastState!==false){
for (var i=0;i<data.length;i++){ for (var i=0;i<data.length;i++){
if (parseInt(data[i])!=data[i]){continue;} if (parseInt(data[i])!=data[i]){continue;}
if (lastState[i]!==data[i]){ if (lastState[i]!==data[i]){
if (data[i]){ if (data[i]){
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML; document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
} else { } else {
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Released"+document.getElementById("connected").innerHTML; document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Released"+document.getElementById("connected").innerHTML;
} }
} else { } else {
if (data[i]){ if (data[i]){
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML; document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
} }
} }
} }
} }
lastState = data; lastState = data;
} }
let device; let device;
connectButton.onclick = async () => { connectButton.onclick = async () => {
navigator.hid.requestDevice({ navigator.hid.requestDevice({
filters: [{ vendorId: 0x0fd9}] // elgato? filters: [{ vendorId: 0x0fd9}] // elgato?
}).then((devices)=>{ }).then((devices)=>{
console.log(devices); console.log(devices);
device = devices[0]; device = devices[0];
console.log(`HID connected: ${device.productName}`); console.log(`HID connected: ${device.productName}`);
document.getElementById("connected").innerHTML = "<br />Connected" +document.getElementById("connected").innerHTML; document.getElementById("connected").innerHTML = "<br />Connected" +document.getElementById("connected").innerHTML;
document.getElementById("disconnectButton").style.display = "inline-block"; document.getElementById("disconnectButton").style.display = "inline-block";
device.addEventListener("inputreport", handleInputReport); device.addEventListener("inputreport", handleInputReport);
//device.sendReport(outputReportId, outputReport).then(() => { //device.sendReport(outputReportId, outputReport).then(() => {
// console.log("Sent output report " + outputReportId); // console.log("Sent output report " + outputReportId);
//}); //});
if (!device.opened){ if (!device.opened){
device.open().then(()=>{ device.open().then(()=>{
window.addEventListener("onbeforeunload", async () => { window.addEventListener("onbeforeunload", async () => {
await device.close(); await device.close();
}); });
}).catch(function(err){console.error(err);}); }).catch(function(err){console.error(err);});
} }
}).catch(function(err){console.error(err);}); }).catch(function(err){console.error(err);});
}; };
disconnectButton.onclick = async () => { disconnectButton.onclick = async () => {
await device.close(); await device.close();
//connected.style.display = "none"; //connected.style.display = "none";
//connectButton.style.display = "initial"; //connectButton.style.display = "initial";
disconnectButton.style.display = "none"; disconnectButton.style.display = "none";
}; };
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,129 +1,129 @@
<html> <html>
<head><title>YouTube Chat + VDON</title> <head><title>YouTube Chat + VDON</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" /> <meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style> <style>
body{ body{
padding:0; padding:0;
margin:0; margin:0;
background-color:#003; background-color:#003;
width:100%; width:100%;
height:100%; height:100%;
color:white; color:white;
font-family: arial; font-family: arial;
} }
iframe { iframe {
width:100%; width:100%;
height:100%; height:100%;
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
position:absolute; position:absolute;
display:block; display:block;
} }
input{ input{
padding:10px; padding:10px;
width:80%; width:80%;
font-size:1.2em; font-size:1.2em;
z-index: 1000; z-index: 1000;
color:black; color:black;
} }
@media screen and (orientation:portrait) { @media screen and (orientation:portrait) {
#container2{ #container2{
width:100%;height:100%;display:none; width:100%;height:100%;display:none;
} }
#container1{ #container1{
width: 50vw;height: 50vh; display:none; float:left; position: fixed; top: 0; right: 0%; width: 50vw;height: 50vh; display:none; float:left; position: fixed; top: 0; right: 0%;
} }
iframe{ iframe{
width:100%; width:100%;
} }
} }
@media screen and (orientation:landscape) { @media screen and (orientation:landscape) {
#container2{ #container2{
width:60vw;height:100%;display:none; width:60vw;height:100%;display:none;
z-index:5; z-index:5;
} }
#container1{ #container1{
width: 50vw;height: 80vh; display:none; float:left; position: fixed; top: 0; right: -10vw; width: 50vw;height: 80vh; display:none; float:left; position: fixed; top: 0; right: -10vw;
} }
iframe{ iframe{
max-width:60vw; max-width:60vw;
} }
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container2"></div> <div id="container2"></div>
<div id="container1" ></div> <div id="container1" ></div>
<div id="clean"> <div id="clean">
<input placeholder="Enter a VDON stream ID" id="vdonlink" onchange="updateLink(event);" type="text" /> <input placeholder="Enter a VDON stream ID" id="vdonlink" onchange="updateLink(event);" type="text" />
<input placeholder="Enter the Youtube Video ID" id="youtube" type="text" /> <input placeholder="Enter the Youtube Video ID" id="youtube" type="text" />
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button> <button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
<div id="viewLink"> <div id="viewLink">
</div> </div>
</div> </div>
<script> <script>
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Announce the new orientation number // Announce the new orientation number
// alert(window.orientation); // alert(window.orientation);
}, false); }, false);
function updateLink(event){ function updateLink(event){
var streamid = document.getElementById("vdonlink").value; var streamid = document.getElementById("vdonlink").value;
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
path = path.replace("/examples",""); path = path.replace("/examples","");
var viewLink = "https://"+path+"/?view="+streamid; var viewLink = "https://"+path+"/?view="+streamid;
document.getElementById("viewLink").innerHTML = "View link is: "+viewLink; document.getElementById("viewLink").innerHTML = "View link is: "+viewLink;
} }
function loadIframes(url=false){ function loadIframes(url=false){
var streamid = document.getElementById("vdonlink").value; var streamid = document.getElementById("vdonlink").value;
var youtube = document.getElementById("youtube").value; var youtube = document.getElementById("youtube").value;
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean")); document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
document.getElementById("container1").style.display="inline-block"; document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block"; document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/"); var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
path = path.replace("/examples",""); path = path.replace("/examples","");
var room1 = "https://"+path+"/?push="+streamid+"&webcam&autostart&vd=front&ad=1&transparent&noheader"; var room1 = "https://"+path+"/?push="+streamid+"&webcam&autostart&vd=front&ad=1&transparent&noheader";
var room2 = "https://www.youtube.com/live_chat?is_popout=1&v="+youtube+"&embed_domain="+location.hostname; var room2 = "https://www.youtube.com/live_chat?is_popout=1&v="+youtube+"&embed_domain="+location.hostname;
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room1; iframe.src = room1;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container1").appendChild(iframeContainer); document.getElementById("container1").appendChild(iframeContainer);
setTimeout(function(){ setTimeout(function(){
var iframe = document.createElement("iframe"); var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room2; iframe.src = room2;
var iframeContainer = document.createElement("div"); var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe); iframeContainer.appendChild(iframe);
document.getElementById("container2").appendChild(iframeContainer); document.getElementById("container2").appendChild(iframeContainer);
},3000); },3000);
} }
</script> </script>
</body> </body>
</html> </html>

View File

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 342 B

View File

@ -1,182 +1,182 @@
<html> <html>
<head><style> <head><style>
span{margin:10px 0 0 0;display:block;} span{margin:10px 0 0 0;display:block;}
body { body {
background-color:#cdf; background-color:#cdf;
padding:0; padding:0;
width;100%;height:100% width;100%;height:100%
} }
input{padding:5px;} input{padding:5px;}
button {margin:10px 3px;} button {margin:10px 3px;}
#stream{ #stream{
display:block; display:block;
} }
</style></head> </style></head>
<body id="main" style="margin:5%;" <body id="main" style="margin:5%;"
<meta charset="utf-8"/> <meta charset="utf-8"/>
<video id="video" autoplay="true" muted="true" playsinline style='height:420px;background-color:black;display:block;margin:0 0 10px 0;'></video> <video id="video" autoplay="true" muted="true" playsinline style='height:420px;background-color:black;display:block;margin:0 0 10px 0;'></video>
<div id="devices"> <div id="devices">
<div class="select"> <div class="select">
<label for="videoSource">Video source: </label><select id="videoSource"></select> <label for="videoSource">Video source: </label><select id="videoSource"></select>
</div> </div>
<div class="select"> <div class="select">
<label for="audioSource">Audio source: </label><select id="audioSource"></select> <label for="audioSource">Audio source: </label><select id="audioSource"></select>
</div> </div>
</div> </div>
<button onclick="fullwindow()">FULL WINDOW</button> <button onclick="fullwindow()">FULL WINDOW</button>
<script> <script>
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) { window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
console.error(errorMsg); console.error(errorMsg);
console.error(lineNumber); console.error(lineNumber);
console.error("Unhandeled Error occured"); //or any message console.error("Unhandeled Error occured"); //or any message
return false; return false;
}; };
function fullwindow(){ function fullwindow(){
videoElement.style.width="100%"; videoElement.style.width="100%";
videoElement.style.padding= "0"; videoElement.style.padding= "0";
videoElement.style.margin="0"; videoElement.style.margin="0";
videoElement.style.height="100%"; videoElement.style.height="100%";
videoElement.style.zIndex="5"; videoElement.style.zIndex="5";
videoElement.style.position = "absolute"; videoElement.style.position = "absolute";
videoElement.style.top="0px"; videoElement.style.top="0px";
videoElement.style.left="0px"; videoElement.style.left="0px";
document.getElementById("main").style.overflow = "hidden"; document.getElementById("main").style.overflow = "hidden";
videoElement.style.overflow = "hidden" videoElement.style.overflow = "hidden"
document.getElementById("main").style.backgroundColor="#000"; document.getElementById("main").style.backgroundColor="#000";
videoElement.style.cursor="none"; videoElement.style.cursor="none";
document.getElementById("main").style.cursor="none"; document.getElementById("main").style.cursor="none";
} }
var videoElement = document.getElementById("video"); var videoElement = document.getElementById("video");
var gotDev = false; var gotDev = false;
async function gotDevices() { async function gotDevices() {
if (gotDev){return;} if (gotDev){return;}
gotDev=true; gotDev=true;
await navigator.mediaDevices.getUserMedia({audio:true, video:true}); // is needed to ask for permissinos. await navigator.mediaDevices.getUserMedia({audio:true, video:true}); // is needed to ask for permissinos.
navigator.mediaDevices.enumerateDevices().then((deviceInfos)=>{ navigator.mediaDevices.enumerateDevices().then((deviceInfos)=>{
for (let i = 0; i !== deviceInfos.length; ++i) { for (let i = 0; i !== deviceInfos.length; ++i) {
var deviceInfo = deviceInfos[i]; var deviceInfo = deviceInfos[i];
var option = document.createElement("option"); var option = document.createElement("option");
option.value = deviceInfo.deviceId; option.value = deviceInfo.deviceId;
if (deviceInfo.kind === "audioinput") { if (deviceInfo.kind === "audioinput") {
option.text = deviceInfo.label || "microphone " + (audioSelect.length + 1); option.text = deviceInfo.label || "microphone " + (audioSelect.length + 1);
audioSelect.appendChild(option); audioSelect.appendChild(option);
if (option.text.startsWith("CABLE")){ if (option.text.startsWith("CABLE")){
option.selected =true; option.selected =true;
} }
} else if (deviceInfo.kind === "videoinput") { } else if (deviceInfo.kind === "videoinput") {
option.text = deviceInfo.label || "camera " + (videoSelect.length + 1); option.text = deviceInfo.label || "camera " + (videoSelect.length + 1);
if (option.text.startsWith("NewTek")){ if (option.text.startsWith("NewTek")){
continue; continue;
} }
videoSelect.appendChild(option); videoSelect.appendChild(option);
if (option.text.startsWith("OBS")){ if (option.text.startsWith("OBS")){
option.selected =true; option.selected =true;
} }
} }
} }
getStream(); getStream();
}); });
} }
function getStream() { function getStream() {
if (window.stream) { if (window.stream) {
window.stream.getTracks().forEach(function (track) { window.stream.getTracks().forEach(function (track) {
track.stop(); track.stop();
log("TRack stopping"); log("TRack stopping");
}); });
} }
const constraints = { const constraints = {
audio: { audio: {
deviceId: { exact: audioSelect.value }, deviceId: { exact: audioSelect.value },
echoCancellation : false, echoCancellation : false,
autoGainControl : false, autoGainControl : false,
noiseSuppression : false noiseSuppression : false
}, },
video: { video: {
deviceId: { exact: videoSelect.value }, deviceId: { exact: videoSelect.value },
width: { min: 1280, ideal: 1920, max: 1920 }, width: { min: 1280, ideal: 1920, max: 1920 },
height: { min: 720, ideal: 1080, max: 1080 } height: { min: 720, ideal: 1080, max: 1080 }
} }
}; };
return navigator.mediaDevices.getUserMedia(constraints) return navigator.mediaDevices.getUserMedia(constraints)
.then(gotStream) .then(gotStream)
.catch(console.error); .catch(console.error);
} }
function gotStream(stream) { function gotStream(stream) {
if (window.stream) { if (window.stream) {
window.stream = stream; // make stream available to console window.stream = stream; // make stream available to console
videoElement.srcObject = stream; videoElement.srcObject = stream;
var senders = session.pc.getSenders(); var senders = session.pc.getSenders();
videoElement.srcObject.getVideoTracks().forEach((track)=>{ videoElement.srcObject.getVideoTracks().forEach((track)=>{
var added = false; var added = false;
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams? senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track) { if (sender.track) {
if (sender.track && sender.track.kind == "video") { if (sender.track && sender.track.kind == "video") {
sender.replaceTrack(track); // replace may not be supported by all browsers. eek. sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
track.enabled = notCensored; track.enabled = notCensored;
added = true; added = true;
} }
} }
}); });
if (added==false){ if (added==false){
session.pc.addTrack(track); session.pc.addTrack(track);
log("ADDED NOT REPLACED?"); log("ADDED NOT REPLACED?");
} }
}); });
videoElement.srcObject.getAudioTracks().forEach((track)=>{ videoElement.srcObject.getAudioTracks().forEach((track)=>{
var added = false; var added = false;
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams? senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track) { if (sender.track) {
if (sender.track && sender.track.kind == "audio") { if (sender.track && sender.track.kind == "audio") {
sender.replaceTrack(track); // replace may not be supported by all browsers. eek. sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
track.enabled = notCensored; track.enabled = notCensored;
added = true; added = true;
} }
} }
}); });
if (added==false){ if (added==false){
session.pc.addTrack(track); session.pc.addTrack(track);
log("ADDED NOT REPLACED?"); log("ADDED NOT REPLACED?");
} }
}); });
} else { } else {
window.stream = stream; // make stream available to console window.stream = stream; // make stream available to console
videoElement.srcObject = stream; videoElement.srcObject = stream;
} }
} }
var audioSelect = document.querySelector("select#audioSource"); var audioSelect = document.querySelector("select#audioSource");
var videoSelect = document.querySelector("select#videoSource"); var videoSelect = document.querySelector("select#videoSource");
audioSelect.onchange = getStream; audioSelect.onchange = getStream;
videoSelect.onchange = getStream; videoSelect.onchange = getStream;
gotDevices(); gotDevices();
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,167 +1,167 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>WebRTC File Sharing</title> <title>WebRTC File Sharing</title>
<style> <style>
body { body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
background-color: #f7f7f7; background-color: #f7f7f7;
} }
/* Container Styles */ /* Container Styles */
.container { .container {
margin: 0 auto; margin: 0 auto;
max-width: 600px; max-width: 600px;
padding: 20px; padding: 20px;
background-color: #ffffff; background-color: #ffffff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0px 0px 5px rgba(0,0,0,0.1); box-shadow: 0px 0px 5px rgba(0,0,0,0.1);
} }
/* Header Styles */ /* Header Styles */
h1 { h1 {
margin: 0; margin: 0;
text-align: center; text-align: center;
font-weight: normal; font-weight: normal;
font-size: 36px; font-size: 36px;
color: #333333; color: #333333;
margin-bottom: 20px; margin-bottom: 20px;
} }
/* File Selector Styles */ /* File Selector Styles */
#file-selector { #file-selector {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin: 20px; margin: 20px;
} }
#file-selector label { #file-selector label {
font-size: 20px; font-size: 20px;
margin-bottom: 10px; margin-bottom: 10px;
} }
#file-selector input[type="file"] { #file-selector input[type="file"] {
display: none; display: none;
} }
#file-selector button { #file-selector button {
font-size: 16px; font-size: 16px;
padding: 10px 20px; padding: 10px 20px;
background-color: #4CAF50; background-color: #4CAF50;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
#file-selector button:hover { #file-selector button:hover {
background-color: #3e8e41; background-color: #3e8e41;
} }
/* Progress Container Styles */ /* Progress Container Styles */
#progress-container { #progress-container {
display: none; display: none;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin: 20px; margin: 20px;
} }
#progress-bar { #progress-bar {
width: 100%; width: 100%;
height: 30px; height: 30px;
background-color: #f1f1f1; background-color: #f1f1f1;
border-radius: 5px; border-radius: 5px;
overflow: hidden; overflow: hidden;
margin-bottom: 10px; margin-bottom: 10px;
} }
#progress-bar-inner { #progress-bar-inner {
height: 100%; height: 100%;
background-color: #4CAF50; background-color: #4CAF50;
text-align: center; text-align: center;
line-height: 30px; line-height: 30px;
color: white; color: white;
transition: width 0.3s ease; transition: width 0.3s ease;
} }
#status { #status {
font-size: 16px; font-size: 16px;
margin-bottom: 10px; margin-bottom: 10px;
} }
#connections { #connections {
font-size: 14px; font-size: 14px;
color: #999999; color: #999999;
} }
/* Input Styles */ /* Input Styles */
input[type="file"] { input[type="file"] {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
border: 1px solid #dddddd; border: 1px solid #dddddd;
margin-bottom: 10px; margin-bottom: 10px;
} }
/* Button Styles */ /* Button Styles */
button { button {
font-size: 16px; font-size: 16px;
padding: 10px 20px; padding: 10px 20px;
background-color: #4CAF50; background-color: #4CAF50;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
button:hover { button:hover {
background-color: #3e8e41; background-color: #3e8e41;
} }
</style> </style>
</head> </head>
<body> <body>
<h1>WebRTC File Sharing</h1> <h1>WebRTC File Sharing</h1>
<div id="file-selector"> <div id="file-selector">
<label for="file-input">Select a file:</label> <label for="file-input">Select a file:</label>
<input type="file" id="file-input" /> <input type="file" id="file-input" />
<button id="share-button">Share</button> <button id="share-button">Share</button>
</div> </div>
<div id="progress-container" style="display:none"> <div id="progress-container" style="display:none">
<div id="progress-bar"> <div id="progress-bar">
<div id="progress-bar-inner"></div> <div id="progress-bar-inner"></div>
</div> </div>
<div id="status">Connecting...</div> <div id="status">Connecting...</div>
<div id="connections">0 connections</div> <div id="connections">0 connections</div>
</div> </div>
<script> <script>
var fileInput = document.getElementById("file-input"); var fileInput = document.getElementById("file-input");
var shareButton = document.getElementById("share-button"); var shareButton = document.getElementById("share-button");
var progressContainer = document.getElementById("progress-container"); var progressContainer = document.getElementById("progress-container");
var progressBarInner = document.getElementById("progress-bar-inner"); var progressBarInner = document.getElementById("progress-bar-inner");
var status = document.getElementById("status"); var status = document.getElementById("status");
var connections = document.getElementById("connections"); var connections = document.getElementById("connections");
var connectionCount = 0; var connectionCount = 0;
shareButton.addEventListener("click", function() { shareButton.addEventListener("click", function() {
if (fileInput.files.length > 0) { if (fileInput.files.length > 0) {
// Display progress bar and status message // Display progress bar and status message
progressContainer.style.display = "block"; progressContainer.style.display = "block";
status.innerText = "Connecting..."; status.innerText = "Connecting...";
// TODO: Use WebRTC to share the file with other users // TODO: Use WebRTC to share the file with other users
// Update progress bar and status messages // Update progress bar and status messages
progressBarInner.style.width = "100%"; progressBarInner.style.width = "100%";
status.innerText = "File shared successfully."; status.innerText = "File shared successfully.";
} }
}); });
// TODO: Use WebRTC to display the number of connections, progress, and speed // TODO: Use WebRTC to display the number of connections, progress, and speed
</script> </script>
</body> </body>
</html> </html>

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -31,7 +31,7 @@
<script src='libs/glfx.js'></script> <script src='libs/glfx.js'></script>
<!-- INCLUDE DEMO SCRIPT --> <!-- INCLUDE DEMO SCRIPT -->
<script src="./main.js"></script> <script src="main.js"></script>
<!-- INCLUDE ADDDRAGEVENTLISTENER.JS --> <!-- INCLUDE ADDDRAGEVENTLISTENER.JS -->
<script src='../../../helpers/addDragEventListener.js'></script> <script src='../../../helpers/addDragEventListener.js'></script>

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 378 KiB

View File

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 209 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 367 KiB

After

Width:  |  Height:  |  Size: 367 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Some files were not shown because too many files have changed in this diff Show More