vdo.ninja docker
7
app/Dockerfile
Normal 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
@ -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
@ -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 *;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
.gitignore → app/static/.gitignore
vendored
@ -1,19 +1,19 @@
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
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.
|
||||
|
||||
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 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.
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
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.
|
||||
|
||||
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 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.
|
||||
@ -1,14 +1,14 @@
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
Contributors to the VDO.Ninja project must first agree to the Contributor License Agreement (CLA).
|
||||
|
||||
Thank you for your understanding.
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
Contributors to the VDO.Ninja project must first agree to the Contributor License Agreement (CLA).
|
||||
|
||||
Thank you for your understanding.
|
||||
@ -1,267 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Generate Cloudflare Auth</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-color: #f0f0f0;
|
||||
background-image:
|
||||
linear-gradient(to right, #e0e0e0 1px, transparent 1px),
|
||||
linear-gradient(to bottom, #e0e0e0 1px, transparent 1px);
|
||||
background-size: 10px 10px;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="number"] {
|
||||
width: 93%;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
input[type="number"] {
|
||||
max-width:80px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
max-width: 500px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.section{
|
||||
max-width:700px;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
.main {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-around;
|
||||
justify-content: center;
|
||||
}
|
||||
.secondary {
|
||||
padding: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-around;
|
||||
justify-content: center;
|
||||
max-width:1200px;
|
||||
margin:auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='main'>
|
||||
<h1>Generate Cloudflare Auth for VDO.Ninja</h1>
|
||||
<form id="postForm">
|
||||
<label for="userId">Cloudflare Account ID:</label>
|
||||
<input type="text" id="userId" name="userId" required>
|
||||
<br><br>
|
||||
<label for="accessToken">Cloudflare Stream Access Token:</label>
|
||||
<input type="password" id="accessToken" name="accessToken" required>
|
||||
<br><br>
|
||||
<label for="expiration">Hours until expiration</label>
|
||||
<input min="0" type="number" id="expiration" name="expiration" placeholder="optional">
|
||||
<br><br>
|
||||
<button type="button" id="submitButton">Generate</button>
|
||||
</form>
|
||||
<br><br>
|
||||
<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>
|
||||
|
||||
<div class="section">
|
||||
<h2>
|
||||
What you can do with Cloudflare + VDO.Ninja?
|
||||
</h2>
|
||||
<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>In theory you could publish from VDO.Ninja WHIP output to Cloudstream, and then to your RTMP destinations, like Youtube.</p>
|
||||
<p></p>
|
||||
<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>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>
|
||||
<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>
|
||||
<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>VDO.Ninja is compatible with WHEP and WHIP!</p>
|
||||
<h3>Very competitive pricing</h3>
|
||||
<p>There's a free tier, which is more than enough for testing.</p>
|
||||
<p>Or pay $1 per $1000 minutes of streaming.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='secondary'>
|
||||
<h2>
|
||||
How it works?
|
||||
</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>
|
||||
<h2>
|
||||
Why do I need a special URL parameter?
|
||||
</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>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>
|
||||
Where to get my Cloudflare account ID and token?
|
||||
</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>
|
||||
As for the API token, you'll need to create it, with limited permissions.
|
||||
<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>Click to Create Token (API Tokens)</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>You can define a time-to-live (TTL), if you wish for the token to auto-expire.</li>
|
||||
</ul>
|
||||
You should now have access to both access token and account ID.
|
||||
</p>
|
||||
<h2>
|
||||
Can I self-host or hard-code my Cloudflare credentials?
|
||||
</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>
|
||||
<h2>
|
||||
Does VDO.Ninja track me or store my private information?
|
||||
</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.
|
||||
</div>
|
||||
<script>
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function clearStorage(){
|
||||
localStorage.clear();
|
||||
if (!session.cleanOutput){
|
||||
warnUser("The local storage and saved settings have been cleared", 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
document.getElementById("accessToken").value = getStorage("accessToken") || "";
|
||||
document.getElementById("userId").value = getStorage("userId") || "";
|
||||
document.getElementById("expiration").value = getStorage("expiration") || "";
|
||||
|
||||
document.getElementById("submitButton").addEventListener("click", async function () {
|
||||
|
||||
const accessToken = document.getElementById("accessToken").value;
|
||||
const userId = document.getElementById("userId").value;
|
||||
const expiration = document.getElementById("expiration").value;
|
||||
|
||||
if (!accessToken || !userId) {
|
||||
alert("Access Token and User ID are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
accessToken: accessToken,
|
||||
userId: userId,
|
||||
expiration: Math.round(expiration*60)
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("https://cloudflare.vdo.ninja/encode/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const responseData = await response.text();
|
||||
console.log(responseData);
|
||||
|
||||
setStorage("accessToken",accessToken);
|
||||
setStorage("userId", userId);
|
||||
setStorage("expiration", expiration);
|
||||
|
||||
document.getElementById("response").value = "&cftoken="+responseData;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
document.getElementById("response").value = "An error occurred.";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Generate Cloudflare Auth</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-color: #f0f0f0;
|
||||
background-image:
|
||||
linear-gradient(to right, #e0e0e0 1px, transparent 1px),
|
||||
linear-gradient(to bottom, #e0e0e0 1px, transparent 1px);
|
||||
background-size: 10px 10px;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="number"] {
|
||||
width: 93%;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
input[type="number"] {
|
||||
max-width:80px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
max-width: 500px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.section{
|
||||
max-width:700px;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
.main {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-around;
|
||||
justify-content: center;
|
||||
}
|
||||
.secondary {
|
||||
padding: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-around;
|
||||
justify-content: center;
|
||||
max-width:1200px;
|
||||
margin:auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='main'>
|
||||
<h1>Generate Cloudflare Auth for VDO.Ninja</h1>
|
||||
<form id="postForm">
|
||||
<label for="userId">Cloudflare Account ID:</label>
|
||||
<input type="text" id="userId" name="userId" required>
|
||||
<br><br>
|
||||
<label for="accessToken">Cloudflare Stream Access Token:</label>
|
||||
<input type="password" id="accessToken" name="accessToken" required>
|
||||
<br><br>
|
||||
<label for="expiration">Hours until expiration</label>
|
||||
<input min="0" type="number" id="expiration" name="expiration" placeholder="optional">
|
||||
<br><br>
|
||||
<button type="button" id="submitButton">Generate</button>
|
||||
</form>
|
||||
<br><br>
|
||||
<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>
|
||||
|
||||
<div class="section">
|
||||
<h2>
|
||||
What you can do with Cloudflare + VDO.Ninja?
|
||||
</h2>
|
||||
<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>In theory you could publish from VDO.Ninja WHIP output to Cloudstream, and then to your RTMP destinations, like Youtube.</p>
|
||||
<p></p>
|
||||
<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>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>
|
||||
<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>
|
||||
<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>VDO.Ninja is compatible with WHEP and WHIP!</p>
|
||||
<h3>Very competitive pricing</h3>
|
||||
<p>There's a free tier, which is more than enough for testing.</p>
|
||||
<p>Or pay $1 per $1000 minutes of streaming.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='secondary'>
|
||||
<h2>
|
||||
How it works?
|
||||
</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>
|
||||
<h2>
|
||||
Why do I need a special URL parameter?
|
||||
</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>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>
|
||||
Where to get my Cloudflare account ID and token?
|
||||
</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>
|
||||
As for the API token, you'll need to create it, with limited permissions.
|
||||
<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>Click to Create Token (API Tokens)</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>You can define a time-to-live (TTL), if you wish for the token to auto-expire.</li>
|
||||
</ul>
|
||||
You should now have access to both access token and account ID.
|
||||
</p>
|
||||
<h2>
|
||||
Can I self-host or hard-code my Cloudflare credentials?
|
||||
</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>
|
||||
<h2>
|
||||
Does VDO.Ninja track me or store my private information?
|
||||
</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.
|
||||
</div>
|
||||
<script>
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function clearStorage(){
|
||||
localStorage.clear();
|
||||
if (!session.cleanOutput){
|
||||
warnUser("The local storage and saved settings have been cleared", 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
document.getElementById("accessToken").value = getStorage("accessToken") || "";
|
||||
document.getElementById("userId").value = getStorage("userId") || "";
|
||||
document.getElementById("expiration").value = getStorage("expiration") || "";
|
||||
|
||||
document.getElementById("submitButton").addEventListener("click", async function () {
|
||||
|
||||
const accessToken = document.getElementById("accessToken").value;
|
||||
const userId = document.getElementById("userId").value;
|
||||
const expiration = document.getElementById("expiration").value;
|
||||
|
||||
if (!accessToken || !userId) {
|
||||
alert("Access Token and User ID are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
accessToken: accessToken,
|
||||
userId: userId,
|
||||
expiration: Math.round(expiration*60)
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("https://cloudflare.vdo.ninja/encode/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const responseData = await response.text();
|
||||
console.log(responseData);
|
||||
|
||||
setStorage("accessToken",accessToken);
|
||||
setStorage("userId", userId);
|
||||
setStorage("expiration", expiration);
|
||||
|
||||
document.getElementById("response").value = "&cftoken="+responseData;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
document.getElementById("response").value = "An error occurred.";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,84 +1,84 @@
|
||||
<html>
|
||||
<body>
|
||||
<div id="output"></div>
|
||||
<script>
|
||||
|
||||
function getSupportedMimeTypes(media, types, codecs) {
|
||||
const isSupported = MediaRecorder.isTypeSupported;
|
||||
const supported = [];
|
||||
types.forEach((type) => {
|
||||
const mimeType = `${media}/${type}`;
|
||||
acodecs.forEach((codec2) => {
|
||||
codecs.forEach((codec) => [
|
||||
mimeType+';codecs="'+codec+', '+codec2+'"',
|
||||
mimeType+';codecs:"'+codec+', '+codec2+'"',
|
||||
mimeType+';codecs="'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"',
|
||||
mimeType+';codecs:"'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"'
|
||||
].forEach(variation => {
|
||||
if (isSupported(variation)){
|
||||
try {
|
||||
options.mimeType = variation;
|
||||
new MediaRecorder(stream, options);
|
||||
|
||||
mediaSource.addSourceBuffer(variation);
|
||||
|
||||
supported.push(variation);
|
||||
} catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
if (isSupported(mimeType))
|
||||
supported.push(mimeType);
|
||||
});
|
||||
return supported;
|
||||
};
|
||||
|
||||
|
||||
var options = {};
|
||||
options.videoBitsPerSecond = parseInt(2500 * 1024);
|
||||
|
||||
const videoTypes = ["webm", "ogg", "mp4", "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 acodecs = ["opus", "pcm", "aac", "mpeg", "mp4a", "mp3", "vorbis"];
|
||||
|
||||
document.getElementById("output").innerHTML= "";
|
||||
var supportedVideos = null;
|
||||
var supportedAudios = null;
|
||||
// Usage ------------------
|
||||
var stream = null;
|
||||
var mediaSource = new MediaSource();
|
||||
|
||||
var video = document.createElement("video");
|
||||
video.autoplay = true;
|
||||
video.muted = false;
|
||||
video.setAttribute("playsinline","");
|
||||
video.src = URL.createObjectURL(mediaSource);
|
||||
|
||||
mediaSource.addEventListener('sourceopen', sourceOpen);
|
||||
console.log("1");
|
||||
function sourceOpen(e) {
|
||||
console.log("2");
|
||||
navigator.mediaDevices.getUserMedia({audio:true,video:true}).then(function(s) {
|
||||
stream = s;
|
||||
|
||||
supportedVideos = getSupportedMimeTypes("video", videoTypes, codecs);
|
||||
supportedAudios = getSupportedMimeTypes("audio", audioTypes, codecs);
|
||||
|
||||
for (var i=0;i<supportedVideos.length;i++){
|
||||
document.getElementById("output").innerHTML += supportedVideos[i]+"<br/>";
|
||||
}
|
||||
}).catch(function(err) {
|
||||
/* handle the error */
|
||||
});
|
||||
}
|
||||
|
||||
//for (var i=0;i<supportedAudios.length;i++){
|
||||
//document.getElementById("output").innerHTML += supportedAudios[i]+"<br/>";
|
||||
//}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
<html>
|
||||
<body>
|
||||
<div id="output"></div>
|
||||
<script>
|
||||
|
||||
function getSupportedMimeTypes(media, types, codecs) {
|
||||
const isSupported = MediaRecorder.isTypeSupported;
|
||||
const supported = [];
|
||||
types.forEach((type) => {
|
||||
const mimeType = `${media}/${type}`;
|
||||
acodecs.forEach((codec2) => {
|
||||
codecs.forEach((codec) => [
|
||||
mimeType+';codecs="'+codec+', '+codec2+'"',
|
||||
mimeType+';codecs:"'+codec+', '+codec2+'"',
|
||||
mimeType+';codecs="'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"',
|
||||
mimeType+';codecs:"'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"'
|
||||
].forEach(variation => {
|
||||
if (isSupported(variation)){
|
||||
try {
|
||||
options.mimeType = variation;
|
||||
new MediaRecorder(stream, options);
|
||||
|
||||
mediaSource.addSourceBuffer(variation);
|
||||
|
||||
supported.push(variation);
|
||||
} catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
if (isSupported(mimeType))
|
||||
supported.push(mimeType);
|
||||
});
|
||||
return supported;
|
||||
};
|
||||
|
||||
|
||||
var options = {};
|
||||
options.videoBitsPerSecond = parseInt(2500 * 1024);
|
||||
|
||||
const videoTypes = ["webm", "ogg", "mp4", "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 acodecs = ["opus", "pcm", "aac", "mpeg", "mp4a", "mp3", "vorbis"];
|
||||
|
||||
document.getElementById("output").innerHTML= "";
|
||||
var supportedVideos = null;
|
||||
var supportedAudios = null;
|
||||
// Usage ------------------
|
||||
var stream = null;
|
||||
var mediaSource = new MediaSource();
|
||||
|
||||
var video = document.createElement("video");
|
||||
video.autoplay = true;
|
||||
video.muted = false;
|
||||
video.setAttribute("playsinline","");
|
||||
video.src = URL.createObjectURL(mediaSource);
|
||||
|
||||
mediaSource.addEventListener('sourceopen', sourceOpen);
|
||||
console.log("1");
|
||||
function sourceOpen(e) {
|
||||
console.log("2");
|
||||
navigator.mediaDevices.getUserMedia({audio:true,video:true}).then(function(s) {
|
||||
stream = s;
|
||||
|
||||
supportedVideos = getSupportedMimeTypes("video", videoTypes, codecs);
|
||||
supportedAudios = getSupportedMimeTypes("audio", audioTypes, codecs);
|
||||
|
||||
for (var i=0;i<supportedVideos.length;i++){
|
||||
document.getElementById("output").innerHTML += supportedVideos[i]+"<br/>";
|
||||
}
|
||||
}).catch(function(err) {
|
||||
/* handle the error */
|
||||
});
|
||||
}
|
||||
|
||||
//for (var i=0;i<supportedAudios.length;i++){
|
||||
//document.getElementById("output").innerHTML += supportedAudios[i]+"<br/>";
|
||||
//}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,121 +1,121 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
border:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<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,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,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
|
||||
<script>
|
||||
|
||||
function updateURL(param, force=false) {
|
||||
var para = param.split('=')[0];
|
||||
if (!(urlParams.has(para)) || (force)){
|
||||
if (history.pushState){
|
||||
|
||||
var arr = window.location.href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = window.location.href + '&' +param;
|
||||
} else {
|
||||
newurl = window.location.href + '?' +param;
|
||||
}
|
||||
|
||||
window.history.pushState({path:newurl},'',newurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
var roomID = "undefined";
|
||||
|
||||
if (urlParams.has("room")){
|
||||
roomID = urlParams.get("room");
|
||||
} else {
|
||||
roomID = generateStreamID();
|
||||
updateURL("room="+roomID);
|
||||
}
|
||||
|
||||
var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
|
||||
|
||||
|
||||
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
|
||||
/* clipboard successfully set */
|
||||
}, () => {
|
||||
/* clipboard write failed */
|
||||
});
|
||||
|
||||
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
|
||||
|
||||
|
||||
var socket = new WebSocket("wss://api.action.wtf:666");
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
socket.send(JSON.stringify({"join":roomID}));
|
||||
}
|
||||
|
||||
|
||||
socket.addEventListener('message', function (event) {
|
||||
if (event.data){
|
||||
var data = JSON.parse(event.data);
|
||||
log(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
var counter=0;
|
||||
function send(scene){
|
||||
counter+=1;
|
||||
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
border:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<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,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,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
|
||||
<script>
|
||||
|
||||
function updateURL(param, force=false) {
|
||||
var para = param.split('=')[0];
|
||||
if (!(urlParams.has(para)) || (force)){
|
||||
if (history.pushState){
|
||||
|
||||
var arr = window.location.href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = window.location.href + '&' +param;
|
||||
} else {
|
||||
newurl = window.location.href + '?' +param;
|
||||
}
|
||||
|
||||
window.history.pushState({path:newurl},'',newurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
var roomID = "undefined";
|
||||
|
||||
if (urlParams.has("room")){
|
||||
roomID = urlParams.get("room");
|
||||
} else {
|
||||
roomID = generateStreamID();
|
||||
updateURL("room="+roomID);
|
||||
}
|
||||
|
||||
var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
|
||||
|
||||
|
||||
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
|
||||
/* clipboard successfully set */
|
||||
}, () => {
|
||||
/* clipboard write failed */
|
||||
});
|
||||
|
||||
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
|
||||
|
||||
|
||||
var socket = new WebSocket("wss://api.action.wtf:666");
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
socket.send(JSON.stringify({"join":roomID}));
|
||||
}
|
||||
|
||||
|
||||
socket.addEventListener('message', function (event) {
|
||||
if (event.data){
|
||||
var data = JSON.parse(event.data);
|
||||
log(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
var counter=0;
|
||||
function send(scene){
|
||||
counter+=1;
|
||||
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,285 +1,285 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="./main.css?ver=40" />
|
||||
<style>
|
||||
.container {
|
||||
max-width: min(80%,875px);
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style='color:white'>
|
||||
<div id="header">
|
||||
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px">
|
||||
<span data-translate="logo-header">
|
||||
<font id="qos">V</font>DO.Ninja
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div id="info">
|
||||
<h1>Web-based Media Conversion Tools</h1>
|
||||
<div class="card">
|
||||
<h2>WebM (or MKV/FLV) to MP4 (fixed 1280x720 resolution) <span class='warning'>(very slow!)</span></h2>
|
||||
<div>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>WebM to MP4 files (no transcoding, *attempts* to force high resolutions)</h2>
|
||||
<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>
|
||||
<input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>WebM to Audio-only files (opus or wav)</h2>
|
||||
<div>
|
||||
<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)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>MKV (or FLV/WebM) to MP4 (no transcoding)</h2>
|
||||
<div>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Having problems?</h2>
|
||||
<div>
|
||||
For larger files, over 2-gigabytes in size, the browser may not be able to properly process the video in memory.
|
||||
</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.
|
||||
</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.
|
||||
</div>
|
||||
</div>
|
||||
<div id="processing">
|
||||
<span id="message"></span>
|
||||
<video id="player" controls style="display:none"></video>
|
||||
<audio id="player2" controls style="display:none"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (window.location.hostname.indexOf("vdo.ninja") == 0) {
|
||||
window.location = window.location.href.replace("vdo.ninja","isolated.vdo.ninja"); // FFMPEG requires an isolated domain.
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="./thirdparty/ffmpeg.min.js"></script>
|
||||
<script>
|
||||
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
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.");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
function download(data, filename) {
|
||||
const blob = new Blob([data.buffer]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const { createFFmpeg, fetchFile } = FFmpeg;
|
||||
const ffmpeg = createFFmpeg({ log: true });
|
||||
|
||||
const transcode = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
console.log(files[0]);
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
await ffmpeg.run('-i', name, '-vf', 'scale=1280:720', 'output.mp4');
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
}
|
||||
|
||||
const transmux = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
await ffmpeg.run('-i', name, '-vcodec', 'copy', '-acodec', 'copy', 'output.mp4');
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
}
|
||||
|
||||
const force1080 = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
const sourceBuffer = await fetch("./media/cap.webm").then(r => r.arrayBuffer());
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Tweaking file... this will take a moment";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
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");
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
}
|
||||
|
||||
const convertToAudioOnly = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
const video = document.getElementById('player');
|
||||
|
||||
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.opus');
|
||||
const data = ffmpeg.FS('readFile', 'output.opus');
|
||||
|
||||
console.log(data.buffer.byteLength);
|
||||
if (data.buffer.byteLength) {
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/opus' }));
|
||||
download(data, name.split(".")[0] + ".opus");
|
||||
} else {
|
||||
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.wav');
|
||||
const data2 = ffmpeg.FS('readFile', 'output.wav');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/pcm' }));
|
||||
download(data2, name.split(".")[0] + ".wav");
|
||||
}
|
||||
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play audio or download it.";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
}
|
||||
|
||||
document.getElementById('uploader').addEventListener('change', transcode);
|
||||
document.getElementById('uploader2').addEventListener('change', transmux);
|
||||
document.getElementById('uploader3').addEventListener('change', force1080);
|
||||
document.getElementById('uploader4').addEventListener('change', convertToAudioOnly);
|
||||
</script>
|
||||
<head>
|
||||
<link rel="stylesheet" href="main.css?ver=40" />
|
||||
<style>
|
||||
.container {
|
||||
max-width: min(80%,875px);
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #00538c;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style='color:white'>
|
||||
<div id="header">
|
||||
<a id="logoname" href="../.." style="text-decoration: none; color: white; margin: 2px">
|
||||
<span data-translate="logo-header">
|
||||
<font id="qos">V</font>DO.Ninja
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div id="info">
|
||||
<h1>Web-based Media Conversion Tools</h1>
|
||||
<div class="card">
|
||||
<h2>WebM (or MKV/FLV) to MP4 (fixed 1280x720 resolution) <span class='warning'>(very slow!)</span></h2>
|
||||
<div>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>WebM to MP4 files (no transcoding, *attempts* to force high resolutions)</h2>
|
||||
<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>
|
||||
<input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>WebM to Audio-only files (opus or wav)</h2>
|
||||
<div>
|
||||
<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)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>MKV (or FLV/WebM) to MP4 (no transcoding)</h2>
|
||||
<div>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Having problems?</h2>
|
||||
<div>
|
||||
For larger files, over 2-gigabytes in size, the browser may not be able to properly process the video in memory.
|
||||
</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.
|
||||
</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.
|
||||
</div>
|
||||
</div>
|
||||
<div id="processing">
|
||||
<span id="message"></span>
|
||||
<video id="player" controls style="display:none"></video>
|
||||
<audio id="player2" controls style="display:none"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (window.location.hostname.indexOf("vdo.ninja") == 0) {
|
||||
window.location = window.location.href.replace("vdo.ninja","isolated.vdo.ninja"); // FFMPEG requires an isolated domain.
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="thirdpartyfmpeg.min.js"></script>
|
||||
<script>
|
||||
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
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.");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
function download(data, filename) {
|
||||
const blob = new Blob([data.buffer]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const { createFFmpeg, fetchFile } = FFmpeg;
|
||||
const ffmpeg = createFFmpeg({ log: true });
|
||||
|
||||
const transcode = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
console.log(files[0]);
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
await ffmpeg.run('-i', name, '-vf', 'scale=1280:720', 'output.mp4');
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
}
|
||||
|
||||
const transmux = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
await ffmpeg.run('-i', name, '-vcodec', 'copy', '-acodec', 'copy', 'output.mp4');
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
}
|
||||
|
||||
const force1080 = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
const sourceBuffer = await fetch("./media/cap.webm").then(r => r.arrayBuffer());
|
||||
document.getElementById('uploader').style.display = "none";
|
||||
document.getElementById('uploader2').style.display = "none";
|
||||
document.getElementById('uploader3').style.display = "none";
|
||||
document.getElementById('message').innerText = "Tweaking file... this will take a moment";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
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");
|
||||
const data = ffmpeg.FS('readFile', 'output.mp4');
|
||||
const video = document.getElementById('player');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play video or download it.";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
}
|
||||
|
||||
const convertToAudioOnly = async ({ target: { files } }) => {
|
||||
const { name } = files[0];
|
||||
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. ");
|
||||
return;
|
||||
}
|
||||
document.getElementById('message').innerText = "Transcoding file... this will take a while";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
await ffmpeg.load();
|
||||
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
|
||||
const video = document.getElementById('player');
|
||||
|
||||
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.opus');
|
||||
const data = ffmpeg.FS('readFile', 'output.opus');
|
||||
|
||||
console.log(data.buffer.byteLength);
|
||||
if (data.buffer.byteLength) {
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/opus' }));
|
||||
download(data, name.split(".")[0] + ".opus");
|
||||
} else {
|
||||
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.wav');
|
||||
const data2 = ffmpeg.FS('readFile', 'output.wav');
|
||||
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/pcm' }));
|
||||
download(data2, name.split(".")[0] + ".wav");
|
||||
}
|
||||
|
||||
video.style.display = "block";
|
||||
document.getElementById('message').innerText = "Operation Done. Play audio or download it.";
|
||||
document.getElementById('processing').style.display = 'flex';
|
||||
}
|
||||
|
||||
document.getElementById('uploader').addEventListener('change', transcode);
|
||||
document.getElementById('uploader2').addEventListener('change', transmux);
|
||||
document.getElementById('uploader3').addEventListener('change', force1080);
|
||||
document.getElementById('uploader4').addEventListener('change', convertToAudioOnly);
|
||||
</script>
|
||||
</body>
|
||||
@ -1,227 +1,227 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
||||
<link rel="stylesheet" href="./main.css?ver=11" />
|
||||
<link rel="stylesheet" href="./devices.css?ver=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf8" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<a
|
||||
id="logoname"
|
||||
href="./"
|
||||
style="text-decoration: none; color: white; margin: 2px"
|
||||
>
|
||||
<span data-translate="logo-header">
|
||||
<font id="qos">V</font>DO.Ninja
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="devices">
|
||||
<div class="notice">
|
||||
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
|
||||
app. <br/>
|
||||
Click device names to preset them. Select multiple audio inputs by clicking multiple devices.
|
||||
</div>
|
||||
<div class="notice">
|
||||
Check for browser and camera capabilities <a href="/supports">here</a>.
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>🎤 Audio Inputs</h1>
|
||||
<div id="audioInputs"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>📹 Video Inputs</h1>
|
||||
<div id="videoInputs"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>🔉 Audio Outputs</h1>
|
||||
<div id="audioOutputs"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sharedDevices" style="display: none">
|
||||
<span>Click to copy. Use this URL to preset audio/video devices.</span>
|
||||
<span id="close" onclick="this.parentNode.style.display='none'">×</span>
|
||||
<input id="devicesUrl" value="" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const list = [];
|
||||
const url = new URL(document.location.origin);
|
||||
const audioInputDevices = [];
|
||||
|
||||
function isAudioInput(value) {
|
||||
return value.kind === "audioinput";
|
||||
}
|
||||
|
||||
function isAudioOutput(value) {
|
||||
return value.kind === "audiooutput";
|
||||
}
|
||||
|
||||
function isVideoInput(value) {
|
||||
return value.kind === "videoinput";
|
||||
}
|
||||
|
||||
function sanitizeDeviceName(deviceName) {
|
||||
return String(deviceName).toLowerCase().replace(/[\W]+/g, "_");
|
||||
}
|
||||
|
||||
function addDevice(element) {
|
||||
const type = element.dataset.deviceType;
|
||||
const device = sanitizeDeviceName(element.querySelector('span').innerText);
|
||||
|
||||
if (type === "audioinput") {
|
||||
setAudioSearchParams(element);
|
||||
}
|
||||
if (type === "videoinput") {
|
||||
setVideoSearchParams(element);
|
||||
}
|
||||
if (type === "audiooutput") {
|
||||
setAudioOutputSearchParams(element);
|
||||
}
|
||||
|
||||
/*
|
||||
Allows for multiple audio devices to be selected
|
||||
Will be output as a comma separated string to &ad
|
||||
*/
|
||||
|
||||
function setAudioSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
// Remove device from list of selected devices
|
||||
const index = audioInputDevices.indexOf(device);
|
||||
if (index !== -1) {
|
||||
audioInputDevices.splice(index, 1);
|
||||
}
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("ad", audioInputDevices.join(","));
|
||||
element.className = "device";
|
||||
|
||||
// If no audio devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("ad");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
|
||||
audioInputDevices.push(device);
|
||||
url.searchParams.set("ad", audioInputDevices.join(","));
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Only allows for a single video device to be selected
|
||||
*/
|
||||
function setVideoSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
element.className = "device";
|
||||
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("vd", device);
|
||||
element.className = "device";
|
||||
|
||||
// If no devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("vd");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
try {
|
||||
element.parentElement.querySelector('.device.selected').className = "device";
|
||||
} catch (error) {
|
||||
console.log("There was no video device already selected.");
|
||||
}
|
||||
|
||||
url.searchParams.set("vd", device);
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Only allows for a single audio output device to be selected
|
||||
*/
|
||||
function setAudioOutputSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
element.className = "device";
|
||||
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("od", device);
|
||||
element.className = "device";
|
||||
|
||||
// If no devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("od");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
try {
|
||||
element.parentElement.querySelector('.device.selected').className = "device";
|
||||
} catch (error) {
|
||||
console.log("There was no video device already selected.");
|
||||
}
|
||||
|
||||
url.searchParams.set("od", device);
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
showDeviceIdsPopup();
|
||||
}
|
||||
|
||||
function showDeviceIdsPopup() {
|
||||
document.getElementById("devicesUrl").value = decodeURIComponent(url);
|
||||
document.getElementById("sharedDevices").style.display = "block";
|
||||
}
|
||||
|
||||
function prettyPrint(json, element) {
|
||||
let output = "<div class='prettyJson two-col'>";
|
||||
let nestedObjs;
|
||||
|
||||
Object.entries(json)
|
||||
.sort()
|
||||
.forEach(([key, value]) => {
|
||||
output += `
|
||||
<div class='device' onclick='addDevice(this)' data-device-type='${value.kind}'>
|
||||
<span class='device-name'>${value.label}</span>
|
||||
<span class='device-id'>
|
||||
${value.deviceId}
|
||||
</span>
|
||||
</div>`;
|
||||
});
|
||||
output += "</div>";
|
||||
document.getElementById(element).innerHTML = output;
|
||||
}
|
||||
|
||||
document.getElementById("devicesUrl").onclick = (e) => {
|
||||
e.target.select();
|
||||
document.execCommand("copy");
|
||||
};
|
||||
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
.then((devices) => {
|
||||
devices.forEach((device) => {
|
||||
console.log(
|
||||
`${device.kind}: ${device.label} id = ${device.deviceId}`
|
||||
);
|
||||
list.push(device);
|
||||
});
|
||||
prettyPrint(devices.filter(isAudioInput), "audioInputs");
|
||||
prettyPrint(devices.filter(isAudioOutput), "audioOutputs");
|
||||
prettyPrint(devices.filter(isVideoInput), "videoInputs");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`${err.name}: ${err.message}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="lineawesome/css/line-awesome.min.css" />
|
||||
<link rel="stylesheet" href="main.css?ver=11" />
|
||||
<link rel="stylesheet" href="devices.css?ver=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf8" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<a
|
||||
id="logoname"
|
||||
href="../.."
|
||||
style="text-decoration: none; color: white; margin: 2px"
|
||||
>
|
||||
<span data-translate="logo-header">
|
||||
<font id="qos">V</font>DO.Ninja
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="devices">
|
||||
<div class="notice">
|
||||
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
|
||||
app. <br/>
|
||||
Click device names to preset them. Select multiple audio inputs by clicking multiple devices.
|
||||
</div>
|
||||
<div class="notice">
|
||||
Check for browser and camera capabilities <a href="/supports">here</a>.
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>🎤 Audio Inputs</h1>
|
||||
<div id="audioInputs"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>📹 Video Inputs</h1>
|
||||
<div id="videoInputs"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>🔉 Audio Outputs</h1>
|
||||
<div id="audioOutputs"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sharedDevices" style="display: none">
|
||||
<span>Click to copy. Use this URL to preset audio/video devices.</span>
|
||||
<span id="close" onclick="this.parentNode.style.display='none'">×</span>
|
||||
<input id="devicesUrl" value="" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const list = [];
|
||||
const url = new URL(document.location.origin);
|
||||
const audioInputDevices = [];
|
||||
|
||||
function isAudioInput(value) {
|
||||
return value.kind === "audioinput";
|
||||
}
|
||||
|
||||
function isAudioOutput(value) {
|
||||
return value.kind === "audiooutput";
|
||||
}
|
||||
|
||||
function isVideoInput(value) {
|
||||
return value.kind === "videoinput";
|
||||
}
|
||||
|
||||
function sanitizeDeviceName(deviceName) {
|
||||
return String(deviceName).toLowerCase().replace(/[\W]+/g, "_");
|
||||
}
|
||||
|
||||
function addDevice(element) {
|
||||
const type = element.dataset.deviceType;
|
||||
const device = sanitizeDeviceName(element.querySelector('span').innerText);
|
||||
|
||||
if (type === "audioinput") {
|
||||
setAudioSearchParams(element);
|
||||
}
|
||||
if (type === "videoinput") {
|
||||
setVideoSearchParams(element);
|
||||
}
|
||||
if (type === "audiooutput") {
|
||||
setAudioOutputSearchParams(element);
|
||||
}
|
||||
|
||||
/*
|
||||
Allows for multiple audio devices to be selected
|
||||
Will be output as a comma separated string to &ad
|
||||
*/
|
||||
|
||||
function setAudioSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
// Remove device from list of selected devices
|
||||
const index = audioInputDevices.indexOf(device);
|
||||
if (index !== -1) {
|
||||
audioInputDevices.splice(index, 1);
|
||||
}
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("ad", audioInputDevices.join(","));
|
||||
element.className = "device";
|
||||
|
||||
// If no audio devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("ad");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
|
||||
audioInputDevices.push(device);
|
||||
url.searchParams.set("ad", audioInputDevices.join(","));
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Only allows for a single video device to be selected
|
||||
*/
|
||||
function setVideoSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
element.className = "device";
|
||||
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("vd", device);
|
||||
element.className = "device";
|
||||
|
||||
// If no devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("vd");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
try {
|
||||
element.parentElement.querySelector('.device.selected').className = "device";
|
||||
} catch (error) {
|
||||
console.log("There was no video device already selected.");
|
||||
}
|
||||
|
||||
url.searchParams.set("vd", device);
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Only allows for a single audio output device to be selected
|
||||
*/
|
||||
function setAudioOutputSearchParams(info) {
|
||||
// Device was already selected
|
||||
if (info.className === "device selected") {
|
||||
element.className = "device";
|
||||
|
||||
|
||||
// Set the url param to the devices that are left
|
||||
url.searchParams.set("od", device);
|
||||
element.className = "device";
|
||||
|
||||
// If no devices remained, just remove the param completely
|
||||
if (audioInputDevices.length === 0) {
|
||||
url.searchParams.delete("od");
|
||||
}
|
||||
} else {
|
||||
// Device is unselected
|
||||
try {
|
||||
element.parentElement.querySelector('.device.selected').className = "device";
|
||||
} catch (error) {
|
||||
console.log("There was no video device already selected.");
|
||||
}
|
||||
|
||||
url.searchParams.set("od", device);
|
||||
element.className = "device selected";
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
showDeviceIdsPopup();
|
||||
}
|
||||
|
||||
function showDeviceIdsPopup() {
|
||||
document.getElementById("devicesUrl").value = decodeURIComponent(url);
|
||||
document.getElementById("sharedDevices").style.display = "block";
|
||||
}
|
||||
|
||||
function prettyPrint(json, element) {
|
||||
let output = "<div class='prettyJson two-col'>";
|
||||
let nestedObjs;
|
||||
|
||||
Object.entries(json)
|
||||
.sort()
|
||||
.forEach(([key, value]) => {
|
||||
output += `
|
||||
<div class='device' onclick='addDevice(this)' data-device-type='${value.kind}'>
|
||||
<span class='device-name'>${value.label}</span>
|
||||
<span class='device-id'>
|
||||
${value.deviceId}
|
||||
</span>
|
||||
</div>`;
|
||||
});
|
||||
output += "</div>";
|
||||
document.getElementById(element).innerHTML = output;
|
||||
}
|
||||
|
||||
document.getElementById("devicesUrl").onclick = (e) => {
|
||||
e.target.select();
|
||||
document.execCommand("copy");
|
||||
};
|
||||
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
.then((devices) => {
|
||||
devices.forEach((device) => {
|
||||
console.log(
|
||||
`${device.kind}: ${device.label} id = ${device.deviceId}`
|
||||
);
|
||||
list.push(device);
|
||||
});
|
||||
prettyPrint(devices.filter(isAudioInput), "audioInputs");
|
||||
prettyPrint(devices.filter(isAudioOutput), "audioOutputs");
|
||||
prettyPrint(devices.filter(isVideoInput), "videoInputs");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`${err.name}: ${err.message}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,24 +1,24 @@
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body>
|
||||
<script>
|
||||
var list = [];
|
||||
|
||||
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(function(devices) {
|
||||
devices.forEach(function(device) {
|
||||
console.log(device.kind + ": " + device.label +
|
||||
" id = " + device.deviceId);
|
||||
list.push(device);
|
||||
});
|
||||
document.write(JSON.stringify(list, null, 2));
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.name + ": " + err.message);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body>
|
||||
<script>
|
||||
var list = [];
|
||||
|
||||
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(function(devices) {
|
||||
devices.forEach(function(device) {
|
||||
console.log(device.kind + ": " + device.label +
|
||||
" id = " + device.deviceId);
|
||||
list.push(device);
|
||||
});
|
||||
document.write(JSON.stringify(list, null, 2));
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.name + ": " + err.message);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,298 +1,298 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
transform: scale(0.7);
|
||||
transform-origin: 0 0;
|
||||
margin:2px;
|
||||
padding:0;
|
||||
border:0;
|
||||
color: #FFF;
|
||||
background-color: #1F1E1F;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
width:300px;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width:1px;
|
||||
}
|
||||
|
||||
|
||||
#container-links {
|
||||
z-index:10;
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:none;
|
||||
|
||||
}
|
||||
|
||||
#container-setup {
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:block;
|
||||
}
|
||||
.red {
|
||||
background-color:#FCC;
|
||||
}
|
||||
.green {
|
||||
background-color:#CFC;
|
||||
}
|
||||
.task {
|
||||
cursor:grab;
|
||||
width:100%;
|
||||
padding:5px;
|
||||
border:2px solid black;
|
||||
margin:0;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
transform: scale(1.4);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.gone {
|
||||
position: absolute;
|
||||
display:inline-block;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function getById(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) {
|
||||
console.warn(id + " is not defined; skipping.");
|
||||
el = document.createElement("span"); // create a fake element
|
||||
}
|
||||
return el;
|
||||
}
|
||||
function copyFunction(copyText) {
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
document.execCommand("copy");
|
||||
|
||||
}
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
console.log(text);
|
||||
return text;
|
||||
};
|
||||
function toHexString(byteArray){
|
||||
return Array.prototype.map.call(byteArray, function(byte){
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||
}).join('');
|
||||
}
|
||||
generateHash = function (str, length=false){
|
||||
var buffer = new TextEncoder("utf-8").encode(str);
|
||||
return crypto.subtle.digest("SHA-256", buffer).then(
|
||||
function (hash) {
|
||||
hash = new Uint8Array(hash);
|
||||
if (length){
|
||||
hash = hash.slice(0, parseInt(parseInt(length)/2));
|
||||
}
|
||||
hash = toHexString(hash);
|
||||
return hash;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function generateInvite(){
|
||||
var title = encodeURI(getById("videoname").value.replace(/[\W]+/g,"_"));
|
||||
if (title.length){
|
||||
title = "&label="+title;
|
||||
}
|
||||
var sid = generateStreamID();
|
||||
var viewstr = "";
|
||||
var sendstr = "";
|
||||
|
||||
if (getById("invite_bitrate").checked){
|
||||
viewstr+="&bitrate=20000";
|
||||
}
|
||||
if (getById("invite_vp9").checked){
|
||||
viewstr+="&codec=vp9";
|
||||
}
|
||||
if (getById("invite_h264").checked){
|
||||
viewstr+="&codec=h264";
|
||||
}
|
||||
if (getById("invite_stereo").checked){
|
||||
viewstr+="&stereo";
|
||||
sendstr+="&stereo";
|
||||
}
|
||||
//if (getById("invite_secure").checked){
|
||||
// sendstr+="&secure";
|
||||
//}
|
||||
if (getById("invite_hidescreen").checked){
|
||||
sendstr+="&webcam";
|
||||
}
|
||||
|
||||
//if (getById("invite_remotecontrol").checked){ //
|
||||
// var remote_gen_id = generateStreamID();
|
||||
// sendstr+="&remote="+remote_gen_id; // security
|
||||
// viewstr+="&remote="+remote_gen_id;
|
||||
//}
|
||||
|
||||
if (getById("invite_joinroom").value.trim().length){
|
||||
sendstr+="&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==1){ // no video
|
||||
sendstr+="&novideo";
|
||||
} else if (getById("invite_group_chat_type").value==2){ // no view or audio
|
||||
sendstr+="&view";
|
||||
}
|
||||
}
|
||||
|
||||
if (getById("invite_quality").value){
|
||||
if (getById("invite_quality").value==0){
|
||||
sendstr+="&quality=0";
|
||||
} else if (getById("invite_quality").value==1){
|
||||
sendstr+="&quality=1";
|
||||
} else if (getById("invite_quality").value==2){
|
||||
sendstr+="&quality=2";
|
||||
}
|
||||
}
|
||||
|
||||
var href = window.location.href;
|
||||
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.
|
||||
|
||||
if (getById("invite_password").value.trim().length){
|
||||
generateHash(getById("invite_password").value.trim().replace(/[\W]+/g,"_")+salt,4).then(function(hash){
|
||||
sendstr+="&hash="+hash;
|
||||
viewstr+="&password="+getById("invite_password").value.trim();
|
||||
sendstr = dir+'?push=' + sid + sendstr;
|
||||
viewstr = dir+'?view=' + sid + viewstr + title;
|
||||
getById("container-setup").style.display="none";
|
||||
getById("container-links").style.display="block";
|
||||
|
||||
getById("guest-link").value = sendstr;
|
||||
getById("obs-link").value = viewstr;
|
||||
});
|
||||
} else {
|
||||
sendstr = dir+'?push=' + sid + sendstr;
|
||||
viewstr = dir+'?view=' + sid + viewstr + title;
|
||||
getById("container-setup").style.display="none";
|
||||
getById("container-links").style.display="block";
|
||||
|
||||
getById("guest-link").value = sendstr;
|
||||
getById("obs-link").value = viewstr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function goBack(){
|
||||
getById("container-setup").style.display="block";
|
||||
getById("container-links").style.display="none";
|
||||
}
|
||||
|
||||
document.addEventListener("dragstart", event => {
|
||||
var url = event.target.href || event.target.value;
|
||||
|
||||
if (!url || !url.startsWith('https://')) return;
|
||||
if (event.target.dataset.drag!="1"){
|
||||
return;
|
||||
}
|
||||
//event.target.ondragend = function(){event.target.blur();}
|
||||
|
||||
var streamId = url.split('view=');
|
||||
var label = url.split('label=');
|
||||
|
||||
url += '&layer-name=OBSN';
|
||||
if (streamId.length>1) url += ': ' + streamId[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-height=1080';
|
||||
|
||||
event.dataTransfer.setDragImage(document.querySelector('#dragImage'), 24, 24);
|
||||
event.dataTransfer.setData("text/uri-list", encodeURI(url));
|
||||
//event.dataTransfer.setData("url", encodeURI(url));
|
||||
|
||||
//warnlog(event);
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div id="container-setup" >
|
||||
<button style="padding:5px;" onclick="generateInvite()" >
|
||||
<span data-translate="generate-invite-link">GENERATE THE INVITE LINK</span>
|
||||
</button>
|
||||
<br /><br />
|
||||
|
||||
|
||||
<input type="checkbox" id="invite_bitrate" /><label for="invite_bitrate"> <span data-translate="unlock-video-bitrate">Unlock Video Bitrate (20mbps)</span></label>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<br />
|
||||
<br />
|
||||
<label for="invite_quality" data-translate="video-resolution">Video Resolution: </label>
|
||||
<select id="invite_quality" name="invite_quality">
|
||||
<option value="-1" selected>User Selectable</option>
|
||||
<option value="0">Maximum Resolution</option>
|
||||
<option value="1">Balanced</option>
|
||||
<option value="2">Smooth and Cool</option>
|
||||
</select>
|
||||
<br />
|
||||
<br />
|
||||
<input type="checkbox" id="invite_hidescreen" />
|
||||
<label for="invite_hidescreen"> <span data-translate="hide-screen-share">Hide Screenshare Option</span></label>
|
||||
<br />
|
||||
<br />
|
||||
<label for="videoname">Stream Label:</label>
|
||||
<input id="videoname" placeholder="Give stream a description" />
|
||||
<br />
|
||||
<br />
|
||||
<span data-translate="add-a-password-to-stream"> Add password:</span>
|
||||
<input id="invite_password" placeholder="Add an optional password" />
|
||||
<br /><br />
|
||||
<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';" />
|
||||
<br />
|
||||
<br />
|
||||
<span id="invitegroupchat" style="display:none;">
|
||||
<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">
|
||||
<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="2" data-translate="cant-see-or-hear">Cannot hear or see the group chat</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div id="container-links" >
|
||||
<button onclick="goBack()" >
|
||||
<span >Go Back</span>
|
||||
</button>
|
||||
<div id="container-links-inner" >
|
||||
<br /><br />
|
||||
<h3>Guest Invite Link:</h3>
|
||||
<input id="guest-link" class="task green" onclick="copyFunction(this)" onmousedown="copyFunction(this)"/>
|
||||
<br /><br />
|
||||
<h3>OBS Browser Source Link:</h3>
|
||||
<input id="obs-link" class="task red" data-drag="1" onmousedown="copyFunction(this)" onclick="copyFunction(this)" />
|
||||
<br />
|
||||
<br />
|
||||
<i>(links are draggable)</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gone" >
|
||||
<!-- This image is used when dragging elements -->
|
||||
<img src="./media/favicon-32x32.png" id="dragImage" />
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
transform: scale(0.7);
|
||||
transform-origin: 0 0;
|
||||
margin:2px;
|
||||
padding:0;
|
||||
border:0;
|
||||
color: #FFF;
|
||||
background-color: #1F1E1F;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
width:300px;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width:1px;
|
||||
}
|
||||
|
||||
|
||||
#container-links {
|
||||
z-index:10;
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:none;
|
||||
|
||||
}
|
||||
|
||||
#container-setup {
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:block;
|
||||
}
|
||||
.red {
|
||||
background-color:#FCC;
|
||||
}
|
||||
.green {
|
||||
background-color:#CFC;
|
||||
}
|
||||
.task {
|
||||
cursor:grab;
|
||||
width:100%;
|
||||
padding:5px;
|
||||
border:2px solid black;
|
||||
margin:0;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
transform: scale(1.4);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.gone {
|
||||
position: absolute;
|
||||
display:inline-block;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function getById(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) {
|
||||
console.warn(id + " is not defined; skipping.");
|
||||
el = document.createElement("span"); // create a fake element
|
||||
}
|
||||
return el;
|
||||
}
|
||||
function copyFunction(copyText) {
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
document.execCommand("copy");
|
||||
|
||||
}
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
console.log(text);
|
||||
return text;
|
||||
};
|
||||
function toHexString(byteArray){
|
||||
return Array.prototype.map.call(byteArray, function(byte){
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||
}).join('');
|
||||
}
|
||||
generateHash = function (str, length=false){
|
||||
var buffer = new TextEncoder("utf-8").encode(str);
|
||||
return crypto.subtle.digest("SHA-256", buffer).then(
|
||||
function (hash) {
|
||||
hash = new Uint8Array(hash);
|
||||
if (length){
|
||||
hash = hash.slice(0, parseInt(parseInt(length)/2));
|
||||
}
|
||||
hash = toHexString(hash);
|
||||
return hash;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function generateInvite(){
|
||||
var title = encodeURI(getById("videoname").value.replace(/[\W]+/g,"_"));
|
||||
if (title.length){
|
||||
title = "&label="+title;
|
||||
}
|
||||
var sid = generateStreamID();
|
||||
var viewstr = "";
|
||||
var sendstr = "";
|
||||
|
||||
if (getById("invite_bitrate").checked){
|
||||
viewstr+="&bitrate=20000";
|
||||
}
|
||||
if (getById("invite_vp9").checked){
|
||||
viewstr+="&codec=vp9";
|
||||
}
|
||||
if (getById("invite_h264").checked){
|
||||
viewstr+="&codec=h264";
|
||||
}
|
||||
if (getById("invite_stereo").checked){
|
||||
viewstr+="&stereo";
|
||||
sendstr+="&stereo";
|
||||
}
|
||||
//if (getById("invite_secure").checked){
|
||||
// sendstr+="&secure";
|
||||
//}
|
||||
if (getById("invite_hidescreen").checked){
|
||||
sendstr+="&webcam";
|
||||
}
|
||||
|
||||
//if (getById("invite_remotecontrol").checked){ //
|
||||
// var remote_gen_id = generateStreamID();
|
||||
// sendstr+="&remote="+remote_gen_id; // security
|
||||
// viewstr+="&remote="+remote_gen_id;
|
||||
//}
|
||||
|
||||
if (getById("invite_joinroom").value.trim().length){
|
||||
sendstr+="&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==1){ // no video
|
||||
sendstr+="&novideo";
|
||||
} else if (getById("invite_group_chat_type").value==2){ // no view or audio
|
||||
sendstr+="&view";
|
||||
}
|
||||
}
|
||||
|
||||
if (getById("invite_quality").value){
|
||||
if (getById("invite_quality").value==0){
|
||||
sendstr+="&quality=0";
|
||||
} else if (getById("invite_quality").value==1){
|
||||
sendstr+="&quality=1";
|
||||
} else if (getById("invite_quality").value==2){
|
||||
sendstr+="&quality=2";
|
||||
}
|
||||
}
|
||||
|
||||
var href = window.location.href;
|
||||
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.
|
||||
|
||||
if (getById("invite_password").value.trim().length){
|
||||
generateHash(getById("invite_password").value.trim().replace(/[\W]+/g,"_")+salt,4).then(function(hash){
|
||||
sendstr+="&hash="+hash;
|
||||
viewstr+="&password="+getById("invite_password").value.trim();
|
||||
sendstr = dir+'?push=' + sid + sendstr;
|
||||
viewstr = dir+'?view=' + sid + viewstr + title;
|
||||
getById("container-setup").style.display="none";
|
||||
getById("container-links").style.display="block";
|
||||
|
||||
getById("guest-link").value = sendstr;
|
||||
getById("obs-link").value = viewstr;
|
||||
});
|
||||
} else {
|
||||
sendstr = dir+'?push=' + sid + sendstr;
|
||||
viewstr = dir+'?view=' + sid + viewstr + title;
|
||||
getById("container-setup").style.display="none";
|
||||
getById("container-links").style.display="block";
|
||||
|
||||
getById("guest-link").value = sendstr;
|
||||
getById("obs-link").value = viewstr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function goBack(){
|
||||
getById("container-setup").style.display="block";
|
||||
getById("container-links").style.display="none";
|
||||
}
|
||||
|
||||
document.addEventListener("dragstart", event => {
|
||||
var url = event.target.href || event.target.value;
|
||||
|
||||
if (!url || !url.startsWith('https://')) return;
|
||||
if (event.target.dataset.drag!="1"){
|
||||
return;
|
||||
}
|
||||
//event.target.ondragend = function(){event.target.blur();}
|
||||
|
||||
var streamId = url.split('view=');
|
||||
var label = url.split('label=');
|
||||
|
||||
url += '&layer-name=OBSN';
|
||||
if (streamId.length>1) url += ': ' + streamId[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-height=1080';
|
||||
|
||||
event.dataTransfer.setDragImage(document.querySelector('#dragImage'), 24, 24);
|
||||
event.dataTransfer.setData("text/uri-list", encodeURI(url));
|
||||
//event.dataTransfer.setData("url", encodeURI(url));
|
||||
|
||||
//warnlog(event);
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div id="container-setup" >
|
||||
<button style="padding:5px;" onclick="generateInvite()" >
|
||||
<span data-translate="generate-invite-link">GENERATE THE INVITE LINK</span>
|
||||
</button>
|
||||
<br /><br />
|
||||
|
||||
|
||||
<input type="checkbox" id="invite_bitrate" /><label for="invite_bitrate"> <span data-translate="unlock-video-bitrate">Unlock Video Bitrate (20mbps)</span></label>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<br />
|
||||
<br />
|
||||
<label for="invite_quality" data-translate="video-resolution">Video Resolution: </label>
|
||||
<select id="invite_quality" name="invite_quality">
|
||||
<option value="-1" selected>User Selectable</option>
|
||||
<option value="0">Maximum Resolution</option>
|
||||
<option value="1">Balanced</option>
|
||||
<option value="2">Smooth and Cool</option>
|
||||
</select>
|
||||
<br />
|
||||
<br />
|
||||
<input type="checkbox" id="invite_hidescreen" />
|
||||
<label for="invite_hidescreen"> <span data-translate="hide-screen-share">Hide Screenshare Option</span></label>
|
||||
<br />
|
||||
<br />
|
||||
<label for="videoname">Stream Label:</label>
|
||||
<input id="videoname" placeholder="Give stream a description" />
|
||||
<br />
|
||||
<br />
|
||||
<span data-translate="add-a-password-to-stream"> Add password:</span>
|
||||
<input id="invite_password" placeholder="Add an optional password" />
|
||||
<br /><br />
|
||||
<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';" />
|
||||
<br />
|
||||
<br />
|
||||
<span id="invitegroupchat" style="display:none;">
|
||||
<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">
|
||||
<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="2" data-translate="cant-see-or-hear">Cannot hear or see the group chat</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div id="container-links" >
|
||||
<button onclick="goBack()" >
|
||||
<span >Go Back</span>
|
||||
</button>
|
||||
<div id="container-links-inner" >
|
||||
<br /><br />
|
||||
<h3>Guest Invite Link:</h3>
|
||||
<input id="guest-link" class="task green" onclick="copyFunction(this)" onmousedown="copyFunction(this)"/>
|
||||
<br /><br />
|
||||
<h3>OBS Browser Source Link:</h3>
|
||||
<input id="obs-link" class="task red" data-drag="1" onmousedown="copyFunction(this)" onclick="copyFunction(this)" />
|
||||
<br />
|
||||
<br />
|
||||
<i>(links are draggable)</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gone" >
|
||||
<!-- This image is used when dragging elements -->
|
||||
<img src="media/favicon-32x32.png" id="dragImage" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -2,7 +2,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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>
|
||||
html {
|
||||
border:0;
|
||||
@ -403,7 +403,7 @@ if (location.hostname.toLowerCase() == "vdo.ninja"){
|
||||
} else{
|
||||
document.getElementById("version").innerHTML = "Elevate app privilleges to see current version";
|
||||
try{
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const ipcRenderer = require('app/static/electron').ipcRenderer;
|
||||
console.log("ELECTRON DETECTED");
|
||||
ipcRenderer.on('appVersion', function(event, version) {
|
||||
console.log("version: "+version);
|
||||
@ -1,131 +1,131 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "List connected StreamIDs";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "HIDE ALL";
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
if (i!==0){
|
||||
iframesrc+=",";
|
||||
}
|
||||
iframesrc+=listOfStreamIDs[i];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "SHOW "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
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.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<button onclick="loadIframe();">CONNECT</button>
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "List connected StreamIDs";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "HIDE ALL";
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
if (i!==0){
|
||||
iframesrc+=",";
|
||||
}
|
||||
iframesrc+=listOfStreamIDs[i];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "SHOW "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
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.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<button onclick="loadIframe();">CONNECT</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,145 +1,145 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
iframe.src = "../?dir=teststeve123&password=1234";
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
|
||||
var button = document.createElement("a");
|
||||
button.innerHTML = "Invite "+listOfStreamIDs[i];
|
||||
button.target = "_blank";
|
||||
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "TOGGLE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: "1",
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<button onclick="loadIframe();">Go to Directors Room</button>
|
||||
<br />
|
||||
The password for guests is 1234<br />
|
||||
<br />
|
||||
<br />
|
||||
Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
iframe.src = "../?dir=teststeve123&password=1234";
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
|
||||
var button = document.createElement("a");
|
||||
button.innerHTML = "Invite "+listOfStreamIDs[i];
|
||||
button.target = "_blank";
|
||||
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "TOGGLE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: "1",
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<button onclick="loadIframe();">Go to Directors Room</button>
|
||||
<br />
|
||||
The password for guests is 1234<br />
|
||||
<br />
|
||||
<br />
|
||||
Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,121 +1,121 @@
|
||||
<html>
|
||||
<head><title>Twitch + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
margin:10%;
|
||||
}
|
||||
|
||||
#startButton{
|
||||
margin: 10px;
|
||||
padding: 20px
|
||||
display: block;
|
||||
border-radius: 50px;
|
||||
font-size:1.5em;
|
||||
}
|
||||
|
||||
#toggleMute{
|
||||
margin: 10px;
|
||||
padding: 30px 0;
|
||||
border-radius: 50px;
|
||||
font-size:1.5em;
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width:200px;
|
||||
left: calc(50% - 100px);
|
||||
}
|
||||
|
||||
.pressed {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="clean">
|
||||
<center>
|
||||
<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>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
|
||||
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var streamID = document.getElementById("viewlink").value;
|
||||
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 streamSrc = "https://"+path+"/?push="+streamID+"&label&webcam&cleanoutput&ad=1&vd=1";
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = streamSrc;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
function sendSelfCommand(action, value=null){
|
||||
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
|
||||
}
|
||||
|
||||
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.id = "toggleMute";
|
||||
button.innerHTML = "Mute";
|
||||
button.dataset.value = "true";
|
||||
document.body.appendChild(button);
|
||||
|
||||
button.onclick = function(){
|
||||
|
||||
if (this.dataset.value=="true"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerHTML = "Un-Mute";
|
||||
sendSelfCommand("mic",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerHTML = "Mute";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("mic",true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Twitch + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
margin:10%;
|
||||
}
|
||||
|
||||
#startButton{
|
||||
margin: 10px;
|
||||
padding: 20px
|
||||
display: block;
|
||||
border-radius: 50px;
|
||||
font-size:1.5em;
|
||||
}
|
||||
|
||||
#toggleMute{
|
||||
margin: 10px;
|
||||
padding: 30px 0;
|
||||
border-radius: 50px;
|
||||
font-size:1.5em;
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width:200px;
|
||||
left: calc(50% - 100px);
|
||||
}
|
||||
|
||||
.pressed {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="clean">
|
||||
<center>
|
||||
<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>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
|
||||
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var streamID = document.getElementById("viewlink").value;
|
||||
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 streamSrc = "https://"+path+"/?push="+streamID+"&label&webcam&cleanoutput&ad=1&vd=1";
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = streamSrc;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
function sendSelfCommand(action, value=null){
|
||||
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
|
||||
}
|
||||
|
||||
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.id = "toggleMute";
|
||||
button.innerHTML = "Mute";
|
||||
button.dataset.value = "true";
|
||||
document.body.appendChild(button);
|
||||
|
||||
button.onclick = function(){
|
||||
|
||||
if (this.dataset.value=="true"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerHTML = "Un-Mute";
|
||||
sendSelfCommand("mic",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerHTML = "Mute";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("mic",true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,27 +1,27 @@
|
||||
<html><body><script>
|
||||
var generateHash = function (str, length=false){
|
||||
var buffer = new TextEncoder("utf-8").encode(str);
|
||||
return crypto.subtle.digest("SHA-256", buffer).then(
|
||||
function (hash) {
|
||||
hash = new Uint8Array(hash);
|
||||
if (length){
|
||||
hash = hash.slice(0, parseInt(parseInt(length)/2));
|
||||
}
|
||||
hash = toHexString(hash);
|
||||
return hash;
|
||||
}
|
||||
);
|
||||
};
|
||||
function toHexString(byteArray){
|
||||
return Array.prototype.map.call(byteArray, function(byte){
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||
}).join('');
|
||||
}
|
||||
var password = prompt("Please enter the password");
|
||||
password = password.trim();
|
||||
password = encodeURIComponent(password);
|
||||
|
||||
generateHash(password + location.hostname, 4).then(function(hash) { // million to one error.
|
||||
alert("hash value: "+hash)
|
||||
});
|
||||
<html><body><script>
|
||||
var generateHash = function (str, length=false){
|
||||
var buffer = new TextEncoder("utf-8").encode(str);
|
||||
return crypto.subtle.digest("SHA-256", buffer).then(
|
||||
function (hash) {
|
||||
hash = new Uint8Array(hash);
|
||||
if (length){
|
||||
hash = hash.slice(0, parseInt(parseInt(length)/2));
|
||||
}
|
||||
hash = toHexString(hash);
|
||||
return hash;
|
||||
}
|
||||
);
|
||||
};
|
||||
function toHexString(byteArray){
|
||||
return Array.prototype.map.call(byteArray, function(byte){
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||
}).join('');
|
||||
}
|
||||
var password = prompt("Please enter the password");
|
||||
password = password.trim();
|
||||
password = encodeURIComponent(password);
|
||||
|
||||
generateHash(password + location.hostname, 4).then(function(hash) { // million to one error.
|
||||
alert("hash value: "+hash)
|
||||
});
|
||||
</script></body></html>
|
||||
@ -1,159 +1,159 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OBSN Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
div {
|
||||
margin:0;
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
}
|
||||
var room="";
|
||||
if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
}
|
||||
|
||||
var password="";
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var span = document.createElement('span');
|
||||
var entry = document.createElement('div');
|
||||
if (type){
|
||||
type = "<i>"+type.replace(/_/g, ' ')+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
span.appendChild(entry);
|
||||
document.body.prepend(span);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OBSN Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
div {
|
||||
margin:0;
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
}
|
||||
var room="";
|
||||
if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
}
|
||||
|
||||
var password="";
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var span = document.createElement('span');
|
||||
var entry = document.createElement('div');
|
||||
if (type){
|
||||
type = "<i>"+type.replace(/_/g, ' ')+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
span.appendChild(entry);
|
||||
document.body.prepend(span);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
</body>
|
||||
</html>
|
||||
@ -1,162 +1,162 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>VDON Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
div {
|
||||
margin:0;
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
var room="";
|
||||
var password="";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
} else if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
} else {
|
||||
var help = document.createElement("h2");
|
||||
help.innerHTML = "This app supports <i>&room, &view, </i>and<i> &password</i> URL parameters.";
|
||||
document.body.appendChild(help);
|
||||
return;
|
||||
}
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "../?datamode&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var span = document.createElement('span');
|
||||
var entry = document.createElement('div');
|
||||
if (type){
|
||||
type = "<i>"+type.replace(/_/g, ' ')+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
span.appendChild(entry);
|
||||
document.body.prepend(span);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>VDON Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
div {
|
||||
margin:0;
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
var room="";
|
||||
var password="";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
} else if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
} else {
|
||||
var help = document.createElement("h2");
|
||||
help.innerHTML = "This app supports <i>&room, &view, </i>and<i> &password</i> URL parameters.";
|
||||
document.body.appendChild(help);
|
||||
return;
|
||||
}
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "../?datamode&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var span = document.createElement('span');
|
||||
var entry = document.createElement('div');
|
||||
if (type){
|
||||
type = "<i>"+type.replace(/_/g, ' ')+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
span.appendChild(entry);
|
||||
document.body.prepend(span);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
</body>
|
||||
</html>
|
||||
@ -1,123 +1,123 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
border:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<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,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,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
|
||||
<script>
|
||||
|
||||
// this app is mainly for demo purposes at this time. It has been depreciated.
|
||||
|
||||
function updateURL(param, force=false) {
|
||||
var para = param.split('=')[0];
|
||||
if (!(urlParams.has(para)) || (force)){
|
||||
if (history.pushState){
|
||||
|
||||
var arr = window.location.href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = window.location.href + '&' +param;
|
||||
} else {
|
||||
newurl = window.location.href + '?' +param;
|
||||
}
|
||||
|
||||
window.history.pushState({path:newurl},'',newurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
var roomID = "undefined";
|
||||
|
||||
if (urlParams.has("room")){
|
||||
roomID = urlParams.get("room");
|
||||
} else {
|
||||
roomID = generateStreamID();
|
||||
updateURL("room="+roomID);
|
||||
}
|
||||
|
||||
var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
|
||||
|
||||
|
||||
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
|
||||
/* clipboard successfully set */
|
||||
}, () => {
|
||||
/* clipboard write failed */
|
||||
});
|
||||
|
||||
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
|
||||
|
||||
|
||||
var socket = new WebSocket("wss://api.action.wtf:666"); // api.action.wtf has been deprecated.
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
socket.send(JSON.stringify({"join":roomID}));
|
||||
}
|
||||
|
||||
|
||||
socket.addEventListener('message', function (event) {
|
||||
if (event.data){
|
||||
var data = JSON.parse(event.data);
|
||||
log(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
var counter=0;
|
||||
function send(scene){
|
||||
counter+=1;
|
||||
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
border:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<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,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,100,100],abc1232:[70,70,20,20],abc1233:[0,0,0,0],abc1234:[0,0,0,0]});'>Pip</button>
|
||||
<script>
|
||||
|
||||
// this app is mainly for demo purposes at this time. It has been depreciated.
|
||||
|
||||
function updateURL(param, force=false) {
|
||||
var para = param.split('=')[0];
|
||||
if (!(urlParams.has(para)) || (force)){
|
||||
if (history.pushState){
|
||||
|
||||
var arr = window.location.href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = window.location.href + '&' +param;
|
||||
} else {
|
||||
newurl = window.location.href + '?' +param;
|
||||
}
|
||||
|
||||
window.history.pushState({path:newurl},'',newurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 7; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
var roomID = "undefined";
|
||||
|
||||
if (urlParams.has("room")){
|
||||
roomID = urlParams.get("room");
|
||||
} else {
|
||||
roomID = generateStreamID();
|
||||
updateURL("room="+roomID);
|
||||
}
|
||||
|
||||
var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
|
||||
|
||||
|
||||
navigator.clipboard.writeText(url+"/mixer?room="+roomID).then(() => {
|
||||
/* clipboard successfully set */
|
||||
}, () => {
|
||||
/* clipboard write failed */
|
||||
});
|
||||
|
||||
document.getElementById("body").innerHTML+=url+"/mixer?room="+roomID;
|
||||
|
||||
|
||||
var socket = new WebSocket("wss://api.action.wtf:666"); // api.action.wtf has been deprecated.
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
socket.send(JSON.stringify({"join":roomID}));
|
||||
}
|
||||
|
||||
|
||||
socket.addEventListener('message', function (event) {
|
||||
if (event.data){
|
||||
var data = JSON.parse(event.data);
|
||||
log(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.onclose = function (){
|
||||
setTimeout(function(){window.location.reload(true);},100);
|
||||
};
|
||||
|
||||
var counter=0;
|
||||
function send(scene){
|
||||
counter+=1;
|
||||
socket.send(JSON.stringify({"msg":true, "scene":scene, "id":counter}));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,331 +1,331 @@
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
margin:0px;
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
max-height: 95%;
|
||||
max-width: 99%%;
|
||||
float: left;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
position:relative;
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 10;
|
||||
float:right;
|
||||
right: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.close {
|
||||
background-color: #d33;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reload {
|
||||
background-color: #0a0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup {
|
||||
z-index: 9;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #d3d3d3;
|
||||
text-align: center;
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
max-height: 95%;
|
||||
max-width: 99%;
|
||||
scale: 0.5;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
/*resize: both; !*enable this to css resize*! */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
cursor: move;
|
||||
background-color: #2196f3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.popup .resizer-right {
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.popup .resizer-bottom {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.popup .resizer-both {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: transparent;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
|
||||
/*NOSELECT*/
|
||||
|
||||
.popup * {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome and Opera */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<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.
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
var currentZIndex = 100;
|
||||
function initDragElement(popup){
|
||||
|
||||
var pos1 = 0,
|
||||
pos2 = 0,
|
||||
pos3 = 0,
|
||||
pos4 = 0;
|
||||
|
||||
var elmnt = null;
|
||||
|
||||
var header = getHeader(popup);
|
||||
var iframe = getIFrame(popup);
|
||||
|
||||
popup.onmousedown = function() {
|
||||
this.style.zIndex = "" + ++currentZIndex;
|
||||
};
|
||||
|
||||
if (header) {
|
||||
header.parentPopup = popup;
|
||||
header.onmousedown = dragMouseDown;
|
||||
}
|
||||
|
||||
|
||||
function dragMouseDown(e) {
|
||||
elmnt = this.parentPopup;
|
||||
elmnt.style.zIndex = "" + ++currentZIndex;
|
||||
|
||||
e = e || window.event;
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
document.onmouseup = closeDragElement;
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag;
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
if (!elmnt) {
|
||||
return;
|
||||
}
|
||||
|
||||
e = e || window.event;
|
||||
// calculate the new cursor position:
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
// set the element's new position:
|
||||
elmnt.style.top = elmnt.offsetTop - pos2 + "px";
|
||||
elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
|
||||
|
||||
iframe.style.top = elmnt.offsetTop - pos2 + "px";
|
||||
iframe.style.left = elmnt.offsetLeft - pos1 + "px";
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
/* stop moving when mouse button is released:*/
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
}
|
||||
|
||||
function getHeader(element) {
|
||||
var headerItems = element.getElementsByClassName("popup-header");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getIFrame(element) {
|
||||
var headerItems = element.getElementsByTagName("iframe");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function initResizeElement(p) {
|
||||
|
||||
var iframe = getIFrame(p);
|
||||
var element = null;
|
||||
var startX, startY, startWidth, startHeight;
|
||||
|
||||
var right = document.createElement("div");
|
||||
right.className = "resizer-right";
|
||||
p.appendChild(right);
|
||||
right.addEventListener("mousedown", initDrag, false);
|
||||
right.parentPopup = p;
|
||||
|
||||
var bottom = document.createElement("div");
|
||||
bottom.className = "resizer-bottom";
|
||||
p.appendChild(bottom);
|
||||
bottom.addEventListener("mousedown", initDrag, false);
|
||||
bottom.parentPopup = p;
|
||||
|
||||
var both = document.createElement("div");
|
||||
both.className = "resizer-both";
|
||||
p.appendChild(both);
|
||||
both.addEventListener("mousedown", initDrag, false);
|
||||
both.parentPopup = p;
|
||||
|
||||
|
||||
function initDrag(e) {
|
||||
element = this.parentPopup;
|
||||
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startWidth = parseInt(
|
||||
document.defaultView.getComputedStyle(element).width,
|
||||
10
|
||||
);
|
||||
startHeight = parseInt(
|
||||
document.defaultView.getComputedStyle(element).height,
|
||||
10
|
||||
);
|
||||
document.documentElement.addEventListener("mousemove", doDrag, false);
|
||||
document.documentElement.addEventListener("mouseup", stopDrag, false);
|
||||
document.documentElement.addEventListener("click", stopDrag, false)
|
||||
}
|
||||
|
||||
function doDrag(e) {
|
||||
if (e.buttons==0){
|
||||
stopDrag(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
element.style.width = startWidth + e.clientX - startX + "px";
|
||||
element.style.height = startHeight + e.clientY - startY + "px";
|
||||
|
||||
iframe.style.width = startWidth + e.clientX - startX + "px";
|
||||
iframe.style.height = startHeight + e.clientY - startY + "px";
|
||||
}
|
||||
|
||||
function stopDrag(e) {
|
||||
document.documentElement.removeEventListener("mousemove", doDrag, false);
|
||||
document.documentElement.removeEventListener("mouseup", stopDrag, false);
|
||||
}
|
||||
|
||||
function getIFrame(element) {
|
||||
var headerItems = element.getElementsByTagName("iframe");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.className="popup";
|
||||
iframeContainer.style.zIndex = "" + ++currentZIndex;
|
||||
iframeContainer.style.width="325px";
|
||||
iframeContainer.style.height="420px";
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Move";
|
||||
button.className = "popup-header menu";
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Close";
|
||||
button.className = "menu close";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');iframe.parentNode.parentNode.removeChild(iframeContainer);}
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Reload";
|
||||
button.className = "menu reload";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow="autoplay";
|
||||
iframe.src = document.getElementById("viewlink").value || "https://vdo.ninja";
|
||||
iframe.style.width="325px";
|
||||
iframe.style.height="420px";
|
||||
|
||||
iframeContainer.appendChild(iframe);
|
||||
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
initDragElement(iframeContainer);
|
||||
initResizeElement(iframeContainer);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
margin:0px;
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
max-height: 95%;
|
||||
max-width: 99%%;
|
||||
float: left;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
position:relative;
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 10;
|
||||
float:right;
|
||||
right: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.close {
|
||||
background-color: #d33;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reload {
|
||||
background-color: #0a0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup {
|
||||
z-index: 9;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #d3d3d3;
|
||||
text-align: center;
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
max-height: 95%;
|
||||
max-width: 99%;
|
||||
scale: 0.5;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
/*resize: both; !*enable this to css resize*! */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
cursor: move;
|
||||
background-color: #2196f3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.popup .resizer-right {
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.popup .resizer-bottom {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.popup .resizer-both {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: transparent;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
|
||||
/*NOSELECT*/
|
||||
|
||||
.popup * {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome and Opera */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<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.
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
var currentZIndex = 100;
|
||||
function initDragElement(popup){
|
||||
|
||||
var pos1 = 0,
|
||||
pos2 = 0,
|
||||
pos3 = 0,
|
||||
pos4 = 0;
|
||||
|
||||
var elmnt = null;
|
||||
|
||||
var header = getHeader(popup);
|
||||
var iframe = getIFrame(popup);
|
||||
|
||||
popup.onmousedown = function() {
|
||||
this.style.zIndex = "" + ++currentZIndex;
|
||||
};
|
||||
|
||||
if (header) {
|
||||
header.parentPopup = popup;
|
||||
header.onmousedown = dragMouseDown;
|
||||
}
|
||||
|
||||
|
||||
function dragMouseDown(e) {
|
||||
elmnt = this.parentPopup;
|
||||
elmnt.style.zIndex = "" + ++currentZIndex;
|
||||
|
||||
e = e || window.event;
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
document.onmouseup = closeDragElement;
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag;
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
if (!elmnt) {
|
||||
return;
|
||||
}
|
||||
|
||||
e = e || window.event;
|
||||
// calculate the new cursor position:
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
// set the element's new position:
|
||||
elmnt.style.top = elmnt.offsetTop - pos2 + "px";
|
||||
elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
|
||||
|
||||
iframe.style.top = elmnt.offsetTop - pos2 + "px";
|
||||
iframe.style.left = elmnt.offsetLeft - pos1 + "px";
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
/* stop moving when mouse button is released:*/
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
}
|
||||
|
||||
function getHeader(element) {
|
||||
var headerItems = element.getElementsByClassName("popup-header");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getIFrame(element) {
|
||||
var headerItems = element.getElementsByTagName("iframe");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function initResizeElement(p) {
|
||||
|
||||
var iframe = getIFrame(p);
|
||||
var element = null;
|
||||
var startX, startY, startWidth, startHeight;
|
||||
|
||||
var right = document.createElement("div");
|
||||
right.className = "resizer-right";
|
||||
p.appendChild(right);
|
||||
right.addEventListener("mousedown", initDrag, false);
|
||||
right.parentPopup = p;
|
||||
|
||||
var bottom = document.createElement("div");
|
||||
bottom.className = "resizer-bottom";
|
||||
p.appendChild(bottom);
|
||||
bottom.addEventListener("mousedown", initDrag, false);
|
||||
bottom.parentPopup = p;
|
||||
|
||||
var both = document.createElement("div");
|
||||
both.className = "resizer-both";
|
||||
p.appendChild(both);
|
||||
both.addEventListener("mousedown", initDrag, false);
|
||||
both.parentPopup = p;
|
||||
|
||||
|
||||
function initDrag(e) {
|
||||
element = this.parentPopup;
|
||||
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startWidth = parseInt(
|
||||
document.defaultView.getComputedStyle(element).width,
|
||||
10
|
||||
);
|
||||
startHeight = parseInt(
|
||||
document.defaultView.getComputedStyle(element).height,
|
||||
10
|
||||
);
|
||||
document.documentElement.addEventListener("mousemove", doDrag, false);
|
||||
document.documentElement.addEventListener("mouseup", stopDrag, false);
|
||||
document.documentElement.addEventListener("click", stopDrag, false)
|
||||
}
|
||||
|
||||
function doDrag(e) {
|
||||
if (e.buttons==0){
|
||||
stopDrag(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
element.style.width = startWidth + e.clientX - startX + "px";
|
||||
element.style.height = startHeight + e.clientY - startY + "px";
|
||||
|
||||
iframe.style.width = startWidth + e.clientX - startX + "px";
|
||||
iframe.style.height = startHeight + e.clientY - startY + "px";
|
||||
}
|
||||
|
||||
function stopDrag(e) {
|
||||
document.documentElement.removeEventListener("mousemove", doDrag, false);
|
||||
document.documentElement.removeEventListener("mouseup", stopDrag, false);
|
||||
}
|
||||
|
||||
function getIFrame(element) {
|
||||
var headerItems = element.getElementsByTagName("iframe");
|
||||
|
||||
if (headerItems.length === 1) {
|
||||
return headerItems[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.className="popup";
|
||||
iframeContainer.style.zIndex = "" + ++currentZIndex;
|
||||
iframeContainer.style.width="325px";
|
||||
iframeContainer.style.height="420px";
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Move";
|
||||
button.className = "popup-header menu";
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Close";
|
||||
button.className = "menu close";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');iframe.parentNode.parentNode.removeChild(iframeContainer);}
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Reload";
|
||||
button.className = "menu reload";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow="autoplay";
|
||||
iframe.src = document.getElementById("viewlink").value || "https://vdo.ninja";
|
||||
iframe.style.width="325px";
|
||||
iframe.style.height="420px";
|
||||
|
||||
iframeContainer.appendChild(iframe);
|
||||
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
initDragElement(iframeContainer);
|
||||
initResizeElement(iframeContainer);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,76 +1,76 @@
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()"/>
|
||||
<script>
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
|
||||
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
|
||||
document.getElementById("container1").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 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 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.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()"/>
|
||||
<script>
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
|
||||
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
|
||||
document.getElementById("container1").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 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 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.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,131 +1,131 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "List connected StreamIDs";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "HIDE ALL";
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
if (i!==0){
|
||||
iframesrc+=",";
|
||||
}
|
||||
iframesrc+=listOfStreamIDs[i];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "SHOW "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
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.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<button onclick="loadIframe();">CONNECT</button>
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&bitrate=200&manual&noaudio&view=";
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov",
|
||||
"2345_pov",
|
||||
"3456_pov",
|
||||
"4567_pov",
|
||||
"5678_pov"
|
||||
];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "List connected StreamIDs";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "HIDE ALL";
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');};
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
if (i!==0){
|
||||
iframesrc+=",";
|
||||
}
|
||||
iframesrc+=listOfStreamIDs[i];
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "SHOW "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.title = "Publish using: https://vdo.ninja/?push="+listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
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.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<button onclick="loadIframe();">CONNECT</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 814 B |
@ -1,5 +1,5 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="../main.css?ver=40" />
|
||||
<link rel="stylesheet" href="../../main.css?ver=40" />
|
||||
<style>
|
||||
.container {
|
||||
max-width: 900px;
|
||||
@ -58,7 +58,7 @@
|
||||
|
||||
<body style='color:white'>
|
||||
<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">
|
||||
<font id="qos">V</font>DO.Ninja
|
||||
</span>
|
||||
@ -1,3 +1,3 @@
|
||||
.tile {
|
||||
max-width:200px !important;
|
||||
.tile {
|
||||
max-width:200px !important;
|
||||
}
|
||||
@ -1,451 +1,451 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:1280px;
|
||||
height:720px;
|
||||
background-color: #111;
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
canvas{
|
||||
padding:10px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.thing {
|
||||
width: 100px;
|
||||
height: 2em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
cursor: grab;
|
||||
}
|
||||
.empty {
|
||||
width: 100px;
|
||||
height: 2em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
.col {
|
||||
width: 130px;
|
||||
height: 450px;
|
||||
padding: 1em;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
float: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function swapNodes(n1, n2) {
|
||||
var p1 = n1.parentNode;
|
||||
var p2 = n2.parentNode;
|
||||
var i1, i2;
|
||||
|
||||
if ( !p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1) ) return;
|
||||
|
||||
for (var i = 0; i < p1.children.length; i++) {
|
||||
if (p1.children[i].isEqualNode(n1)) {
|
||||
i1 = i;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < p2.children.length; i++) {
|
||||
if (p2.children[i].isEqualNode(n2)) {
|
||||
i2 = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ( p1.isEqualNode(p2) && i1 < i2 ) {
|
||||
i2++;
|
||||
}
|
||||
p1.insertBefore(n2, p1.children[i1]);
|
||||
p2.insertBefore(n1, p2.children[i2]);
|
||||
}
|
||||
|
||||
function drag(ev) {
|
||||
ev.dataTransfer.setData("text", ev.target.id);
|
||||
}
|
||||
|
||||
function drop(ev) {
|
||||
ev.preventDefault();
|
||||
var data = ev.dataTransfer.getData("text");
|
||||
var origThing = document.getElementById(data);
|
||||
console.log(origThing);
|
||||
console.log(data);
|
||||
console.log(ev);
|
||||
//var newThing = origThing.cloneNode(true);
|
||||
if (ev.target.classList.contains("thing")){
|
||||
//ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
//elem.parentNode.insertBefore(elem, elem.parentNode.firstChild);
|
||||
swapNodes( ev.target, origThing);
|
||||
var slot = origThing.dataset.slot;
|
||||
origThing.dataset.slot = ev.target.dataset.slot;
|
||||
ev.target.dataset.slot = slot;
|
||||
|
||||
} else if (ev.target.classList.contains("empty")){
|
||||
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
origThing.dataset.slot = ev.target.dataset.slot;
|
||||
ev.target.style.display = "none";
|
||||
}
|
||||
origThing.style.backgroundColor = ev.target.style.backgroundColor;
|
||||
|
||||
|
||||
}
|
||||
|
||||
function dropRemove(ev) {
|
||||
ev.preventDefault();
|
||||
var data = ev.dataTransfer.getData("text");
|
||||
var origThing = document.getElementById(data);
|
||||
if (origThing.dataset.slot){
|
||||
document.querySelector(".empty[data-slot='"+origThing.dataset.slot+"']").style.display = "block";
|
||||
delete origThing.dataset.slot;
|
||||
}
|
||||
origThing.style.backgroundColor = "#000";
|
||||
if (ev.target.classList.contains("thing")){
|
||||
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
} else {
|
||||
ev.target.appendChild(origThing);
|
||||
}
|
||||
document.getElementById("col2").appendChild(document.getElementById("delete"));
|
||||
}
|
||||
|
||||
var streamIDs = [];
|
||||
|
||||
function updateList(){
|
||||
//<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="thing1">THING 1</div>
|
||||
//</div>
|
||||
for (var i=0;i<streamIDs.length;i++){
|
||||
if (!document.getElementById("sid_"+streamIDs[i])){
|
||||
var thing = document.createElement("div");
|
||||
thing.draggable = true;
|
||||
thing.classList.add("thing");
|
||||
thing.addEventListener("dragstart", drag);
|
||||
thing.dataset.sid = streamIDs[i];
|
||||
thing.id = "sid_"+streamIDs[i];
|
||||
thing.innerText = streamIDs[i];
|
||||
document.getElementById("col2").appendChild(thing);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("col2").appendChild(document.getElementById("delete"));
|
||||
}
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
var promptRoom = prompt("Enter a room name to use");
|
||||
if (promptRoom){
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
|
||||
} else {
|
||||
promptRoom = "testroom123312";
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
|
||||
}
|
||||
|
||||
function activate(){
|
||||
console.log(this.dataset.layout);
|
||||
var layout = JSON.parse(this.dataset.layout);
|
||||
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
|
||||
|
||||
|
||||
for (var i=0;i<layout.length;i++){
|
||||
|
||||
var stream = document.querySelector(".thing[data-slot='"+(i+1)+"'");
|
||||
if (!stream){continue;}
|
||||
|
||||
var x = layout[i].x|| 0;
|
||||
var y = layout[i].y || 0;
|
||||
var w = layout[i].w || 0;
|
||||
var h = layout[i].h || 0;
|
||||
var cover = layout[i].cover || false;
|
||||
|
||||
if (!(w && h)){continue;}
|
||||
|
||||
x = x + "%";
|
||||
y = y + "%";
|
||||
w = w + "%";
|
||||
h = h + "%";
|
||||
|
||||
if (cover){
|
||||
cover = "object-fit:cover;";
|
||||
} else {
|
||||
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");
|
||||
button.innerHTML = "Refresh list";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
button.style.display = "block";
|
||||
document.getElementById("sources").appendChild(button);
|
||||
|
||||
var a = document.createElement("a");
|
||||
a.innerHTML = "Invite Guest Link";
|
||||
a.href = "https://vdo.ninja/?room="+promptRoom+"&broadcast";
|
||||
a.target = "_blank";
|
||||
document.getElementById("sources").appendChild(a);
|
||||
|
||||
var colors = [
|
||||
"#00AAAA",
|
||||
"#FF0000",
|
||||
"#0000FF",
|
||||
"#AA00AA",
|
||||
"#00FF00",
|
||||
"#AAAA00"
|
||||
];
|
||||
|
||||
|
||||
var slots = document.getElementById("col1").children;
|
||||
for (var i=0;i<slots.length;i++){
|
||||
slots[i].style.backgroundColor = colors[i];
|
||||
}
|
||||
|
||||
|
||||
function drawLayout(layout){
|
||||
for (var i=0;i<layout.length;i++){
|
||||
layout[i].i = i;
|
||||
}
|
||||
|
||||
function compare( a, b ) { // sorts layout based on z-index.
|
||||
var aa = a.z || 0;
|
||||
var bb = b.z || 0;
|
||||
if ( aa > bb ){
|
||||
return 1;
|
||||
}
|
||||
if ( aa < bb ){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
layout.sort(compare);
|
||||
|
||||
|
||||
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width="80";
|
||||
canvas.height="45";
|
||||
var ctx = canvas.getContext('2d');
|
||||
document.getElementById("container").appendChild(canvas);
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, 80, 45);
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fill();
|
||||
|
||||
for (var i=0;i<layout.length;i++){
|
||||
|
||||
ctx.fillStyle = colors[layout[i].i];
|
||||
ctx.lineWidth = 3;
|
||||
var x = layout[i].x*0.8 || 0;
|
||||
var y = layout[i].y*0.45 || 0;
|
||||
var w = layout[i].w*0.8 || 0;
|
||||
var h = layout[i].h*0.45 || 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.rect(x, y, w, h);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
canvas.dataset.layout = JSON.stringify(layout);
|
||||
canvas.onclick = activate;
|
||||
}
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:0, h:0},
|
||||
{x:0, y:0, w:100, h:100, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:25, w:50, h:50, cover:true},
|
||||
{x:50, y:25, w:50, h:50, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
|
||||
var data = [
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true},
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:20, h:20, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:50, h:50},
|
||||
{x:50, y:0, w:50, h:50},
|
||||
{x:0, y:50, w:50, h:50},
|
||||
{x:50, y:50, w:50, h:50}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{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:33.333, w:33.333, h:33.333},
|
||||
{x:66.667, y:66.667, w:33.333, h:33.333}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:66.667, y:0, w:33.333, h:33.333},
|
||||
{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:66.667, w:33.333, h:33.333}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:0, h:0},
|
||||
{},
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{x:0, y:25, w:50, h:50, cover:true},
|
||||
{x:50, y:25, w:50, h:50, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
|
||||
if (e.data.action === "new-view-connection"){
|
||||
iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
streamIDs = [];
|
||||
for (var key in e.data.streamIDs){
|
||||
streamIDs.push(key);
|
||||
}
|
||||
updateList();
|
||||
console.log(streamIDs);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
<div class="col" id="sources">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<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="2">SLOT 2</div>
|
||||
<div class="empty" data-slot="3">SLOT 3</div>
|
||||
<div class="empty" data-slot="4">SLOT 4</div>
|
||||
</div>
|
||||
<div id="container">
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:1280px;
|
||||
height:720px;
|
||||
background-color: #111;
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
canvas{
|
||||
padding:10px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.thing {
|
||||
width: 100px;
|
||||
height: 2em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
cursor: grab;
|
||||
}
|
||||
.empty {
|
||||
width: 100px;
|
||||
height: 2em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
.col {
|
||||
width: 130px;
|
||||
height: 450px;
|
||||
padding: 1em;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
float: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function swapNodes(n1, n2) {
|
||||
var p1 = n1.parentNode;
|
||||
var p2 = n2.parentNode;
|
||||
var i1, i2;
|
||||
|
||||
if ( !p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1) ) return;
|
||||
|
||||
for (var i = 0; i < p1.children.length; i++) {
|
||||
if (p1.children[i].isEqualNode(n1)) {
|
||||
i1 = i;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < p2.children.length; i++) {
|
||||
if (p2.children[i].isEqualNode(n2)) {
|
||||
i2 = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ( p1.isEqualNode(p2) && i1 < i2 ) {
|
||||
i2++;
|
||||
}
|
||||
p1.insertBefore(n2, p1.children[i1]);
|
||||
p2.insertBefore(n1, p2.children[i2]);
|
||||
}
|
||||
|
||||
function drag(ev) {
|
||||
ev.dataTransfer.setData("text", ev.target.id);
|
||||
}
|
||||
|
||||
function drop(ev) {
|
||||
ev.preventDefault();
|
||||
var data = ev.dataTransfer.getData("text");
|
||||
var origThing = document.getElementById(data);
|
||||
console.log(origThing);
|
||||
console.log(data);
|
||||
console.log(ev);
|
||||
//var newThing = origThing.cloneNode(true);
|
||||
if (ev.target.classList.contains("thing")){
|
||||
//ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
//elem.parentNode.insertBefore(elem, elem.parentNode.firstChild);
|
||||
swapNodes( ev.target, origThing);
|
||||
var slot = origThing.dataset.slot;
|
||||
origThing.dataset.slot = ev.target.dataset.slot;
|
||||
ev.target.dataset.slot = slot;
|
||||
|
||||
} else if (ev.target.classList.contains("empty")){
|
||||
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
origThing.dataset.slot = ev.target.dataset.slot;
|
||||
ev.target.style.display = "none";
|
||||
}
|
||||
origThing.style.backgroundColor = ev.target.style.backgroundColor;
|
||||
|
||||
|
||||
}
|
||||
|
||||
function dropRemove(ev) {
|
||||
ev.preventDefault();
|
||||
var data = ev.dataTransfer.getData("text");
|
||||
var origThing = document.getElementById(data);
|
||||
if (origThing.dataset.slot){
|
||||
document.querySelector(".empty[data-slot='"+origThing.dataset.slot+"']").style.display = "block";
|
||||
delete origThing.dataset.slot;
|
||||
}
|
||||
origThing.style.backgroundColor = "#000";
|
||||
if (ev.target.classList.contains("thing")){
|
||||
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
|
||||
} else {
|
||||
ev.target.appendChild(origThing);
|
||||
}
|
||||
document.getElementById("col2").appendChild(document.getElementById("delete"));
|
||||
}
|
||||
|
||||
var streamIDs = [];
|
||||
|
||||
function updateList(){
|
||||
//<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="thing1">THING 1</div>
|
||||
//</div>
|
||||
for (var i=0;i<streamIDs.length;i++){
|
||||
if (!document.getElementById("sid_"+streamIDs[i])){
|
||||
var thing = document.createElement("div");
|
||||
thing.draggable = true;
|
||||
thing.classList.add("thing");
|
||||
thing.addEventListener("dragstart", drag);
|
||||
thing.dataset.sid = streamIDs[i];
|
||||
thing.id = "sid_"+streamIDs[i];
|
||||
thing.innerText = streamIDs[i];
|
||||
document.getElementById("col2").appendChild(thing);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("col2").appendChild(document.getElementById("delete"));
|
||||
}
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
var promptRoom = prompt("Enter a room name to use");
|
||||
if (promptRoom){
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
|
||||
} else {
|
||||
promptRoom = "testroom123312";
|
||||
var iframesrc = "https://vdo.ninja/?transparent&cleanoutput&manual&scene=manualtestscene&room="+promptRoom;
|
||||
}
|
||||
|
||||
function activate(){
|
||||
console.log(this.dataset.layout);
|
||||
var layout = JSON.parse(this.dataset.layout);
|
||||
|
||||
iframe.contentWindow.postMessage({"target":"*", "remove":true}, '*');
|
||||
|
||||
|
||||
|
||||
for (var i=0;i<layout.length;i++){
|
||||
|
||||
var stream = document.querySelector(".thing[data-slot='"+(i+1)+"'");
|
||||
if (!stream){continue;}
|
||||
|
||||
var x = layout[i].x|| 0;
|
||||
var y = layout[i].y || 0;
|
||||
var w = layout[i].w || 0;
|
||||
var h = layout[i].h || 0;
|
||||
var cover = layout[i].cover || false;
|
||||
|
||||
if (!(w && h)){continue;}
|
||||
|
||||
x = x + "%";
|
||||
y = y + "%";
|
||||
w = w + "%";
|
||||
h = h + "%";
|
||||
|
||||
if (cover){
|
||||
cover = "object-fit:cover;";
|
||||
} else {
|
||||
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");
|
||||
button.innerHTML = "Refresh list";
|
||||
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
|
||||
button.style.display = "block";
|
||||
document.getElementById("sources").appendChild(button);
|
||||
|
||||
var a = document.createElement("a");
|
||||
a.innerHTML = "Invite Guest Link";
|
||||
a.href = "https://vdo.ninja/?room="+promptRoom+"&broadcast";
|
||||
a.target = "_blank";
|
||||
document.getElementById("sources").appendChild(a);
|
||||
|
||||
var colors = [
|
||||
"#00AAAA",
|
||||
"#FF0000",
|
||||
"#0000FF",
|
||||
"#AA00AA",
|
||||
"#00FF00",
|
||||
"#AAAA00"
|
||||
];
|
||||
|
||||
|
||||
var slots = document.getElementById("col1").children;
|
||||
for (var i=0;i<slots.length;i++){
|
||||
slots[i].style.backgroundColor = colors[i];
|
||||
}
|
||||
|
||||
|
||||
function drawLayout(layout){
|
||||
for (var i=0;i<layout.length;i++){
|
||||
layout[i].i = i;
|
||||
}
|
||||
|
||||
function compare( a, b ) { // sorts layout based on z-index.
|
||||
var aa = a.z || 0;
|
||||
var bb = b.z || 0;
|
||||
if ( aa > bb ){
|
||||
return 1;
|
||||
}
|
||||
if ( aa < bb ){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
layout.sort(compare);
|
||||
|
||||
|
||||
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width="80";
|
||||
canvas.height="45";
|
||||
var ctx = canvas.getContext('2d');
|
||||
document.getElementById("container").appendChild(canvas);
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, 80, 45);
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fill();
|
||||
|
||||
for (var i=0;i<layout.length;i++){
|
||||
|
||||
ctx.fillStyle = colors[layout[i].i];
|
||||
ctx.lineWidth = 3;
|
||||
var x = layout[i].x*0.8 || 0;
|
||||
var y = layout[i].y*0.45 || 0;
|
||||
var w = layout[i].w*0.8 || 0;
|
||||
var h = layout[i].h*0.45 || 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.rect(x, y, w, h);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
canvas.dataset.layout = JSON.stringify(layout);
|
||||
canvas.onclick = activate;
|
||||
}
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:0, h:0},
|
||||
{x:0, y:0, w:100, h:100, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:25, w:50, h:50, cover:true},
|
||||
{x:50, y:25, w:50, h:50, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
|
||||
var data = [
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true},
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:20, h:20, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:50, h:50},
|
||||
{x:50, y:0, w:50, h:50},
|
||||
{x:0, y:50, w:50, h:50},
|
||||
{x:50, y:50, w:50, h:50}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{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:33.333, w:33.333, h:33.333},
|
||||
{x:66.667, y:66.667, w:33.333, h:33.333}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:66.667, y:0, w:33.333, h:33.333},
|
||||
{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:66.667, w:33.333, h:33.333}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{x:0, y:0, w:0, h:0},
|
||||
{},
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{x:0, y:0, w:100, h:100}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{x:70, y:70, w:30, h:30, z:1, cover:false},
|
||||
{x:0, y:0, w:100, h:100,z:0, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
var data = [
|
||||
{},
|
||||
{},
|
||||
{x:0, y:25, w:50, h:50, cover:true},
|
||||
{x:50, y:25, w:50, h:50, cover:true}
|
||||
];
|
||||
drawLayout(data);
|
||||
|
||||
|
||||
iframe.src = iframesrc;
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
|
||||
if (e.data.action === "new-view-connection"){
|
||||
iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
streamIDs = [];
|
||||
for (var key in e.data.streamIDs){
|
||||
streamIDs.push(key);
|
||||
}
|
||||
updateList();
|
||||
console.log(streamIDs);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
<div class="col" id="sources">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<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="2">SLOT 2</div>
|
||||
<div class="empty" data-slot="3">SLOT 3</div>
|
||||
<div class="empty" data-slot="4">SLOT 4</div>
|
||||
</div>
|
||||
<div id="container">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,147 +1,147 @@
|
||||
body{
|
||||
zoom: 75%;
|
||||
}
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type] {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
#controlButtons{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-cluster='2'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-video'] {
|
||||
display:unset!important;
|
||||
visibility: visible;
|
||||
width:unset;
|
||||
height:unset;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div > a.soloLink{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div.shift{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div.streamID{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='forward'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='direct-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='hangup'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-cluster='1'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
|
||||
button[data-action-type='recorder-local'] {
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='ordering'] {
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='open-file-share'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='add-channel']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='toggle-remote-speaker']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='toggle-remote-display']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='hide-guest']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='create-timer']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='change-url']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='change-params']{
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='change-quality']{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
span[data-action-type='sceneCluster2']{
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='sceneCluster1']{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
.orderspan{
|
||||
display:none! important;
|
||||
}
|
||||
#roomHeader{
|
||||
display:none! important;
|
||||
}
|
||||
.directorContainer {
|
||||
display:none!important;
|
||||
}
|
||||
|
||||
.hideDropMenu{
|
||||
display:none!important;
|
||||
}
|
||||
|
||||
#header{
|
||||
display:none!important;
|
||||
}
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
button[class="pull-right"]{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div#guestFeeds {
|
||||
padding: 1px!important;
|
||||
margin: 1px!important;
|
||||
}
|
||||
div[class="vidcon directorMargins"] {
|
||||
padding: 1px!important;
|
||||
margin: 1px!important;
|
||||
width: 260px;
|
||||
}
|
||||
button[data-action-type]{
|
||||
margin: 1px!important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
body{
|
||||
zoom: 75%;
|
||||
}
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type] {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
#controlButtons{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-cluster='2'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-video'] {
|
||||
display:unset!important;
|
||||
visibility: visible;
|
||||
width:unset;
|
||||
height:unset;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div > a.soloLink{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div.shift{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div.streamID{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='forward'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='direct-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='hangup'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='solo-chat'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-cluster='1'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
|
||||
button[data-action-type='recorder-local'] {
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='ordering'] {
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='open-file-share'] {
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
button[data-action-type='add-channel']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='toggle-remote-speaker']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='toggle-remote-display']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='hide-guest']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='create-timer']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='change-url']{
|
||||
display:none! important;
|
||||
}
|
||||
button[data-action-type='change-params']{
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='change-quality']{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
span[data-action-type='sceneCluster2']{
|
||||
display:none! important;
|
||||
}
|
||||
span[data-action-type='sceneCluster1']{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
.orderspan{
|
||||
display:none! important;
|
||||
}
|
||||
#roomHeader{
|
||||
display:none! important;
|
||||
}
|
||||
.directorContainer {
|
||||
display:none!important;
|
||||
}
|
||||
|
||||
.hideDropMenu{
|
||||
display:none!important;
|
||||
}
|
||||
|
||||
#header{
|
||||
display:none!important;
|
||||
}
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
button[class="pull-right"]{
|
||||
display:none! important;
|
||||
}
|
||||
|
||||
div#guestFeeds {
|
||||
padding: 1px!important;
|
||||
margin: 1px!important;
|
||||
}
|
||||
div[class="vidcon directorMargins"] {
|
||||
padding: 1px!important;
|
||||
margin: 1px!important;
|
||||
width: 260px;
|
||||
}
|
||||
button[data-action-type]{
|
||||
margin: 1px!important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,112 +1,112 @@
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:470px;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
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 rooms = "";
|
||||
if (urlParams.has("rooms") || urlParams.has("room") || urlParams.has("r")){
|
||||
rooms = urlParams.get("rooms") || urlParams.get("room") || urlParams.get("r");
|
||||
rooms = rooms.split(",");
|
||||
|
||||
if (password == null){
|
||||
password = prompt("Enter the password for the rooms; leave blank for none");
|
||||
}
|
||||
if (password){
|
||||
password = password.split(",");
|
||||
} else {
|
||||
password = "";
|
||||
}
|
||||
for (var i = 0;i<rooms.length;i++){
|
||||
var pass = "";
|
||||
if (password && (password.length>i)){
|
||||
pass = decodeURIComponent(password[i]);
|
||||
if (pass){
|
||||
pass = "&password="+pass;
|
||||
}
|
||||
} else if (password[0]){
|
||||
pass = decodeURIComponent(password[0]);
|
||||
if (pass){
|
||||
pass = "&password="+pass;
|
||||
}
|
||||
}
|
||||
loadIframes("https://"+path+"/../?clean&hidecodirectors&director="+rooms[i]+pass);
|
||||
}
|
||||
} else {
|
||||
document.write("To use, comma separate the room names. ie: https://vdo.ninja/examples/multi?rooms=xxxx,yyy,ccc");
|
||||
}
|
||||
|
||||
function loadIframes(url){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
var params = window.location.search || "";
|
||||
|
||||
if (params.startsWith("?")){
|
||||
params = params.slice(1);
|
||||
iframe.src = url + "&" + params
|
||||
} else {
|
||||
iframe.src = url + params
|
||||
}
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Dual Input</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:470px;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
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 rooms = "";
|
||||
if (urlParams.has("rooms") || urlParams.has("room") || urlParams.has("r")){
|
||||
rooms = urlParams.get("rooms") || urlParams.get("room") || urlParams.get("r");
|
||||
rooms = rooms.split(",");
|
||||
|
||||
if (password == null){
|
||||
password = prompt("Enter the password for the rooms; leave blank for none");
|
||||
}
|
||||
if (password){
|
||||
password = password.split(",");
|
||||
} else {
|
||||
password = "";
|
||||
}
|
||||
for (var i = 0;i<rooms.length;i++){
|
||||
var pass = "";
|
||||
if (password && (password.length>i)){
|
||||
pass = decodeURIComponent(password[i]);
|
||||
if (pass){
|
||||
pass = "&password="+pass;
|
||||
}
|
||||
} else if (password[0]){
|
||||
pass = decodeURIComponent(password[0]);
|
||||
if (pass){
|
||||
pass = "&password="+pass;
|
||||
}
|
||||
}
|
||||
loadIframes("https://"+path+"/../?clean&hidecodirectors&director="+rooms[i]+pass);
|
||||
}
|
||||
} else {
|
||||
document.write("To use, comma separate the room names. ie: https://vdo.ninja/examples/multi?rooms=xxxx,yyy,ccc");
|
||||
}
|
||||
|
||||
function loadIframes(url){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
var params = window.location.search || "";
|
||||
|
||||
if (params.startsWith("?")){
|
||||
params = params.slice(1);
|
||||
iframe.src = url + "&" + params
|
||||
} else {
|
||||
iframe.src = url + params
|
||||
}
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,319 +1,319 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
iframe.src = "../?dir=teststeve123&password=1234";
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov"
|
||||
];
|
||||
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
|
||||
var button = document.createElement("a");
|
||||
button.innerHTML = "Invite "+listOfStreamIDs[i];
|
||||
button.target = "_blank";
|
||||
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "speaker true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "speaker",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "speaker false "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "speaker",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "display true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "display",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "display false "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "display",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "MUTE true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "UN-MUTE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "addScene 1 toggle "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: 1,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Scene 1 toggle "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: "toggle",
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "add Scene 1"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remove Scene 1"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "MUTE SCENE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "muteScene",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "un-mute Scene "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "muteScene",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "soloChat "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "soloChat",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "soloChat off"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "soloChat",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if (typeof e.data !== "object"){return;}
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<button onclick="loadIframe();">Go to Directors Room</button>
|
||||
<br />
|
||||
The password for guests is 1234<br />
|
||||
<br />
|
||||
<br />
|
||||
Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
|
||||
</div>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>IFRAME Example</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:90%
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function loadIframe(){
|
||||
|
||||
document.getElementById("container").innerHTML = "";
|
||||
|
||||
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
|
||||
|
||||
iframe.src = "../?dir=teststeve123&password=1234";
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var listOfStreamIDs = [
|
||||
"1234_pov"
|
||||
];
|
||||
|
||||
|
||||
for (var i=0;i<listOfStreamIDs.length;i++){
|
||||
|
||||
var button = document.createElement("a");
|
||||
button.innerHTML = "Invite "+listOfStreamIDs[i];
|
||||
button.target = "_blank";
|
||||
button.href = "../?room=teststeve123&password=1234&broadcast&transparent&autostart&nmb&nvb&gain=0&webcam&l=stevetest&push="+listOfStreamIDs[i];
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "speaker true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "speaker",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "speaker false "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "speaker",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "display true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "display",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "display false "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "display",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "MUTE true "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "UN-MUTE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "mic",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "addScene 1 toggle "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: 1,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Scene 1 toggle "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: "toggle",
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "add Scene 1"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remove Scene 1"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "addScene",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "MUTE SCENE "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "muteScene",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "un-mute Scene "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "muteScene",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
///////////////////
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "soloChat "+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "soloChat",
|
||||
value: true,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "soloChat off"+listOfStreamIDs[i];
|
||||
button.dataset.sid = listOfStreamIDs[i];
|
||||
button.onclick = function(){
|
||||
iframe.contentWindow.postMessage({
|
||||
action: "soloChat",
|
||||
value: false,
|
||||
target: this.dataset.sid
|
||||
}, '*');
|
||||
|
||||
}; // target can be a stream ID or * for all.
|
||||
iframeContainer.appendChild(button);
|
||||
}
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if (typeof e.data !== "object"){return;}
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "event: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "streamID list:<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<button onclick="loadIframe();">Go to Directors Room</button>
|
||||
<br />
|
||||
The password for guests is 1234<br />
|
||||
<br />
|
||||
<br />
|
||||
Custom guest invites and toggles for add/removing from scene=1 are on the bottom.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,401 +1,401 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="./thirdparty/obs-websocket.min.js"></script>
|
||||
<link rel="stylesheet" href="https://vdo.ninja/main.css" />
|
||||
<title>OBS Controller Demo using VDO.Ninja</title>
|
||||
<style>
|
||||
|
||||
.container {
|
||||
max-width: 80%;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
button {
|
||||
margin:5px;
|
||||
border:solid black 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
color:white;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #CEF!important;
|
||||
}
|
||||
|
||||
#info{
|
||||
margin: 20px;
|
||||
max-height: 50%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#client {
|
||||
margin:10px;
|
||||
display:block;
|
||||
}
|
||||
label {
|
||||
color:white;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>OBS remote (server)</h1>
|
||||
<span id='setup'>
|
||||
<label for="address">Websocket Address</label>
|
||||
<input name="address" id="address" placeholder="address (optional)" value="localhost:4444" />
|
||||
<label for="address">Websocket Password</label>
|
||||
<input name="password" id="password" placeholder="password here (optional)" />
|
||||
<br />
|
||||
<label for="vdoroomname">Room name to use</label>
|
||||
<input name="vdoroomname" id="vdoroomname" placeholder="vdo room name to use (optional)" />
|
||||
<label for="vdopassword">Room password to use</label>
|
||||
<input name="vdopassword" id="vdopassword" placeholder="vdo password to use (optional)" />
|
||||
<br />
|
||||
<button id="address_button">Connect</button>
|
||||
<button id="address_button_2">Connect and share OBS Output</button>
|
||||
</span>
|
||||
<a id="client" target="_blank"></a>
|
||||
<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>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var hostname = "vdo.ninja"; // all that's supported as of this moment.
|
||||
|
||||
const obs = new OBSWebSocket();
|
||||
var scenesData = {};
|
||||
scenesData.scenes = [];
|
||||
|
||||
function sendToOBS(action, data={}){
|
||||
document.getElementById("info").innerHTML = action + "<br />"+document.getElementById("info").innerHTML;
|
||||
obs.sendCallback(action, data, sendCallback)
|
||||
}
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
var roomname = Math.floor(Math.random() * 1000000);
|
||||
var pwurl = Math.floor(Math.random() * 1000000);
|
||||
|
||||
if (urlParams.get("password")){
|
||||
pwurl = urlParams.get("password");
|
||||
localStorage.setItem('password',pwurl)
|
||||
} else if (localStorage.getItem('password')){
|
||||
pwurl = localStorage.getItem('password');
|
||||
} else {
|
||||
localStorage.setItem('password',pwurl)
|
||||
}
|
||||
|
||||
if (urlParams.get("room")){
|
||||
roomname = urlParams.get("room")
|
||||
localStorage.setItem('roomname',roomname)
|
||||
} else if (localStorage.getItem('roomname')){
|
||||
roomname = localStorage.getItem('roomname');
|
||||
} else {
|
||||
localStorage.setItem('roomname',roomname)
|
||||
}
|
||||
|
||||
document.getElementById('vdoroomname').value = roomname;
|
||||
document.getElementById('vdopassword').value = pwurl;
|
||||
|
||||
|
||||
if (localStorage.getItem('address')){
|
||||
document.getElementById('address').value = localStorage.getItem('address');
|
||||
}
|
||||
|
||||
if (localStorage.getItem('wspass')){
|
||||
document.getElementById('password').value = localStorage.getItem('wspass');
|
||||
}
|
||||
|
||||
var iframe = null;
|
||||
function createIFrame(visible=true){
|
||||
iframe = document.createElement("iframe");
|
||||
|
||||
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.style.minWidth = "720px";
|
||||
iframe.style.minHeight = "480px";
|
||||
iframe.style.maxWidth = "50%";
|
||||
iframe.style.maxHeight = "50%";
|
||||
iframe.style.display = "block";
|
||||
iframe.style.margin = "auto";
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
} 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.style.opacity = 0;
|
||||
iframe.style.width = 0;
|
||||
iframe.style.height = 0;
|
||||
iframe.style.position = "absolulte";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.style.left = "-100px";
|
||||
}
|
||||
document.getElementById("client").parentNode.insertBefore(iframe, document.getElementById("client").nextSibling);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.log(e);
|
||||
if ("dataReceived" in e.data){
|
||||
if ("sendToOBS" in e.data.dataReceived){
|
||||
if ("action" in e.data.dataReceived.sendToOBS){
|
||||
if ("data" in e.data.dataReceived.sendToOBS){
|
||||
sendToOBS(e.data.dataReceived.sendToOBS.action, e.data.dataReceived.sendToOBS.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("action" in e.data){
|
||||
if (e.data.action === "new-push-connection"){
|
||||
console.log(e.data);
|
||||
updateSceneList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendCallback(err, data){
|
||||
console.log("CALLBACK TRIGGERED");
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.callbackData = data;
|
||||
msg.sentFromOBS.callbackError = err;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
function sendRawData(data){
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.rawData = data;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
function heartBeat(){
|
||||
obs.send("GetStats");
|
||||
}
|
||||
|
||||
function updateSceneList(){
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.scenes = scenesData;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
console.log(msg);
|
||||
obs.send("GetSourcesList");
|
||||
obs.send('GetCurrentScene');
|
||||
obs.send("GetVideoInfo");
|
||||
obs.send("ListOutputs");
|
||||
}
|
||||
|
||||
document.getElementById('address_button').addEventListener('click', e => {
|
||||
connect(e, false);
|
||||
});
|
||||
|
||||
document.getElementById('address_button_2').addEventListener('click', e => {
|
||||
connect(e, true);
|
||||
});
|
||||
|
||||
var heartBeatInterval = null;
|
||||
|
||||
function connect(e, camera){
|
||||
const address = document.getElementById('address').value || "localhost:4444";
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
|
||||
roomname = document.getElementById('vdoroomname').value || Math.floor(Math.random() * 1000000);
|
||||
pwurl = document.getElementById('vdopassword').value || Math.floor(Math.random() * 1000000);
|
||||
localStorage.setItem('roomname',roomname)
|
||||
localStorage.setItem('password',pwurl)
|
||||
|
||||
createIFrame(camera); // connects to VDO.Ninja's IFRAME API
|
||||
|
||||
localStorage.setItem('address',address);
|
||||
if (password){
|
||||
localStorage.setItem('wspass',password);
|
||||
var ret = obs.connect({
|
||||
address: address,
|
||||
password: password
|
||||
});
|
||||
} else {
|
||||
var ret = obs.connect({
|
||||
address: address
|
||||
});
|
||||
}
|
||||
|
||||
ret.then(() => {
|
||||
console.log(`Success!`);
|
||||
return obs.send('GetSceneList');
|
||||
}).then(data => {
|
||||
document.getElementById("setup").style.display = "none";
|
||||
scenesData = data;
|
||||
updateSceneList();
|
||||
var pathname = window.location.pathname.split("/");
|
||||
pathname.pop();
|
||||
pathname = pathname.join("/");
|
||||
var clientLink = window.location.protocol + "//" + window.location.host + pathname + "/interface.html?room="+roomname+"&password="+pwurl;
|
||||
document.getElementById("client").href = 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;
|
||||
try {
|
||||
obs._socket.onmessage2 = obs._socket.onmessage; // hijacking the obs-websocket.js framework
|
||||
obs._socket.onmessage = function(data){
|
||||
console.log(data);
|
||||
obs._socket.onmessage2(data);
|
||||
if (data.type && data.data){
|
||||
if (data.type == "message"){
|
||||
sendRawData(data.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e){console.error(e);}
|
||||
|
||||
clearInterval(heartBeatInterval);
|
||||
heartBeatInterval = setInterval(function(){heartBeat();},3000);
|
||||
|
||||
}).catch(err => { // Promise convention dicates you have a catch on every chain.
|
||||
console.log(err);
|
||||
document.getElementById("info").innerHTML = "<br />Error trying to connect. Is SSL enabled on your domain?" + document.getElementById("info").innerHTML;
|
||||
if ("error" in err){
|
||||
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.
|
||||
|
||||
obs.on('SwitchScenes', data => {
|
||||
console.log(`New Active Scene: ${data.sceneName}`);
|
||||
scenesData.currentScene = data.sceneName
|
||||
updateSceneList();
|
||||
});
|
||||
obs.on('ConnectionOpened', (data) => function(){
|
||||
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;
|
||||
});
|
||||
obs.on('ConnectionClosed', (data) => function(){
|
||||
document.getElementById("setup").style.display = "unset";
|
||||
document.getElementById("info").innerHTML = "<br />Connection to OBS websockets closed" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('AuthenticationSuccess', (data) => function(){
|
||||
document.getElementById("setup").style.display = "none";
|
||||
document.getElementById("info").innerHTML = "<br />OBS websockets authenticated" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('AuthenticationFailure', (data) => function(){
|
||||
document.getElementById("setup").style.display = "unset";
|
||||
document.getElementById("info").innerHTML = "<br />Authentication to OBS websockets failed" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('ScenesChanged', (data) => function(){
|
||||
scenesData = data;
|
||||
updateSceneList()
|
||||
});
|
||||
// You must add this handler to avoid uncaught exceptions.
|
||||
obs.on('error', err => {
|
||||
document.getElementById("info").innerHTML = "<br />OBS websockets error" + document.getElementById("info").innerHTML;
|
||||
console.error('socket error:', err);
|
||||
if ("error" in err){
|
||||
document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="thirdparty/obs-websocket.min.js"></script>
|
||||
<link rel="stylesheet" href="https://vdo.ninja/main.css" />
|
||||
<title>OBS Controller Demo using VDO.Ninja</title>
|
||||
<style>
|
||||
|
||||
.container {
|
||||
max-width: 80%;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
button {
|
||||
margin:5px;
|
||||
border:solid black 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
color:white;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #CEF!important;
|
||||
}
|
||||
|
||||
#info{
|
||||
margin: 20px;
|
||||
max-height: 50%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#client {
|
||||
margin:10px;
|
||||
display:block;
|
||||
}
|
||||
label {
|
||||
color:white;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>OBS remote (server)</h1>
|
||||
<span id='setup'>
|
||||
<label for="address">Websocket Address</label>
|
||||
<input name="address" id="address" placeholder="address (optional)" value="localhost:4444" />
|
||||
<label for="address">Websocket Password</label>
|
||||
<input name="password" id="password" placeholder="password here (optional)" />
|
||||
<br />
|
||||
<label for="vdoroomname">Room name to use</label>
|
||||
<input name="vdoroomname" id="vdoroomname" placeholder="vdo room name to use (optional)" />
|
||||
<label for="vdopassword">Room password to use</label>
|
||||
<input name="vdopassword" id="vdopassword" placeholder="vdo password to use (optional)" />
|
||||
<br />
|
||||
<button id="address_button">Connect</button>
|
||||
<button id="address_button_2">Connect and share OBS Output</button>
|
||||
</span>
|
||||
<a id="client" target="_blank"></a>
|
||||
<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>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var hostname = "vdo.ninja"; // all that's supported as of this moment.
|
||||
|
||||
const obs = new OBSWebSocket();
|
||||
var scenesData = {};
|
||||
scenesData.scenes = [];
|
||||
|
||||
function sendToOBS(action, data={}){
|
||||
document.getElementById("info").innerHTML = action + "<br />"+document.getElementById("info").innerHTML;
|
||||
obs.sendCallback(action, data, sendCallback)
|
||||
}
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
var roomname = Math.floor(Math.random() * 1000000);
|
||||
var pwurl = Math.floor(Math.random() * 1000000);
|
||||
|
||||
if (urlParams.get("password")){
|
||||
pwurl = urlParams.get("password");
|
||||
localStorage.setItem('password',pwurl)
|
||||
} else if (localStorage.getItem('password')){
|
||||
pwurl = localStorage.getItem('password');
|
||||
} else {
|
||||
localStorage.setItem('password',pwurl)
|
||||
}
|
||||
|
||||
if (urlParams.get("room")){
|
||||
roomname = urlParams.get("room")
|
||||
localStorage.setItem('roomname',roomname)
|
||||
} else if (localStorage.getItem('roomname')){
|
||||
roomname = localStorage.getItem('roomname');
|
||||
} else {
|
||||
localStorage.setItem('roomname',roomname)
|
||||
}
|
||||
|
||||
document.getElementById('vdoroomname').value = roomname;
|
||||
document.getElementById('vdopassword').value = pwurl;
|
||||
|
||||
|
||||
if (localStorage.getItem('address')){
|
||||
document.getElementById('address').value = localStorage.getItem('address');
|
||||
}
|
||||
|
||||
if (localStorage.getItem('wspass')){
|
||||
document.getElementById('password').value = localStorage.getItem('wspass');
|
||||
}
|
||||
|
||||
var iframe = null;
|
||||
function createIFrame(visible=true){
|
||||
iframe = document.createElement("iframe");
|
||||
|
||||
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.style.minWidth = "720px";
|
||||
iframe.style.minHeight = "480px";
|
||||
iframe.style.maxWidth = "50%";
|
||||
iframe.style.maxHeight = "50%";
|
||||
iframe.style.display = "block";
|
||||
iframe.style.margin = "auto";
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
} 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.style.opacity = 0;
|
||||
iframe.style.width = 0;
|
||||
iframe.style.height = 0;
|
||||
iframe.style.position = "absolulte";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.style.left = "-100px";
|
||||
}
|
||||
document.getElementById("client").parentNode.insertBefore(iframe, document.getElementById("client").nextSibling);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.log(e);
|
||||
if ("dataReceived" in e.data){
|
||||
if ("sendToOBS" in e.data.dataReceived){
|
||||
if ("action" in e.data.dataReceived.sendToOBS){
|
||||
if ("data" in e.data.dataReceived.sendToOBS){
|
||||
sendToOBS(e.data.dataReceived.sendToOBS.action, e.data.dataReceived.sendToOBS.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("action" in e.data){
|
||||
if (e.data.action === "new-push-connection"){
|
||||
console.log(e.data);
|
||||
updateSceneList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendCallback(err, data){
|
||||
console.log("CALLBACK TRIGGERED");
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.callbackData = data;
|
||||
msg.sentFromOBS.callbackError = err;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
function sendRawData(data){
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.rawData = data;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
function heartBeat(){
|
||||
obs.send("GetStats");
|
||||
}
|
||||
|
||||
function updateSceneList(){
|
||||
var msg = {};
|
||||
msg.sentFromOBS = {};
|
||||
msg.sentFromOBS.scenes = scenesData;
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
} catch(e){}
|
||||
console.log(msg);
|
||||
obs.send("GetSourcesList");
|
||||
obs.send('GetCurrentScene');
|
||||
obs.send("GetVideoInfo");
|
||||
obs.send("ListOutputs");
|
||||
}
|
||||
|
||||
document.getElementById('address_button').addEventListener('click', e => {
|
||||
connect(e, false);
|
||||
});
|
||||
|
||||
document.getElementById('address_button_2').addEventListener('click', e => {
|
||||
connect(e, true);
|
||||
});
|
||||
|
||||
var heartBeatInterval = null;
|
||||
|
||||
function connect(e, camera){
|
||||
const address = document.getElementById('address').value || "localhost:4444";
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
|
||||
roomname = document.getElementById('vdoroomname').value || Math.floor(Math.random() * 1000000);
|
||||
pwurl = document.getElementById('vdopassword').value || Math.floor(Math.random() * 1000000);
|
||||
localStorage.setItem('roomname',roomname)
|
||||
localStorage.setItem('password',pwurl)
|
||||
|
||||
createIFrame(camera); // connects to VDO.Ninja's IFRAME API
|
||||
|
||||
localStorage.setItem('address',address);
|
||||
if (password){
|
||||
localStorage.setItem('wspass',password);
|
||||
var ret = obs.connect({
|
||||
address: address,
|
||||
password: password
|
||||
});
|
||||
} else {
|
||||
var ret = obs.connect({
|
||||
address: address
|
||||
});
|
||||
}
|
||||
|
||||
ret.then(() => {
|
||||
console.log(`Success!`);
|
||||
return obs.send('GetSceneList');
|
||||
}).then(data => {
|
||||
document.getElementById("setup").style.display = "none";
|
||||
scenesData = data;
|
||||
updateSceneList();
|
||||
var pathname = window.location.pathname.split("/");
|
||||
pathname.pop();
|
||||
pathname = pathname.join("/");
|
||||
var clientLink = window.location.protocol + "//" + window.location.host + pathname + "/interface.html?room="+roomname+"&password="+pwurl;
|
||||
document.getElementById("client").href = 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;
|
||||
try {
|
||||
obs._socket.onmessage2 = obs._socket.onmessage; // hijacking the obs-websocket.js framework
|
||||
obs._socket.onmessage = function(data){
|
||||
console.log(data);
|
||||
obs._socket.onmessage2(data);
|
||||
if (data.type && data.data){
|
||||
if (data.type == "message"){
|
||||
sendRawData(data.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e){console.error(e);}
|
||||
|
||||
clearInterval(heartBeatInterval);
|
||||
heartBeatInterval = setInterval(function(){heartBeat();},3000);
|
||||
|
||||
}).catch(err => { // Promise convention dicates you have a catch on every chain.
|
||||
console.log(err);
|
||||
document.getElementById("info").innerHTML = "<br />Error trying to connect. Is SSL enabled on your domain?" + document.getElementById("info").innerHTML;
|
||||
if ("error" in err){
|
||||
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.
|
||||
|
||||
obs.on('SwitchScenes', data => {
|
||||
console.log(`New Active Scene: ${data.sceneName}`);
|
||||
scenesData.currentScene = data.sceneName
|
||||
updateSceneList();
|
||||
});
|
||||
obs.on('ConnectionOpened', (data) => function(){
|
||||
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;
|
||||
});
|
||||
obs.on('ConnectionClosed', (data) => function(){
|
||||
document.getElementById("setup").style.display = "unset";
|
||||
document.getElementById("info").innerHTML = "<br />Connection to OBS websockets closed" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('AuthenticationSuccess', (data) => function(){
|
||||
document.getElementById("setup").style.display = "none";
|
||||
document.getElementById("info").innerHTML = "<br />OBS websockets authenticated" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('AuthenticationFailure', (data) => function(){
|
||||
document.getElementById("setup").style.display = "unset";
|
||||
document.getElementById("info").innerHTML = "<br />Authentication to OBS websockets failed" + document.getElementById("info").innerHTML;
|
||||
});
|
||||
obs.on('ScenesChanged', (data) => function(){
|
||||
scenesData = data;
|
||||
updateSceneList()
|
||||
});
|
||||
// You must add this handler to avoid uncaught exceptions.
|
||||
obs.on('error', err => {
|
||||
document.getElementById("info").innerHTML = "<br />OBS websockets error" + document.getElementById("info").innerHTML;
|
||||
console.error('socket error:', err);
|
||||
if ("error" in err){
|
||||
document.getElementById("info").innerHTML = "<br />"+err.error + document.getElementById("info").innerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,474 +1,474 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="./thirdparty/obs-websocket.min.js"></script>
|
||||
<link rel="stylesheet" href="https://vdo.ninja/main.css" />
|
||||
<style>
|
||||
|
||||
.container {
|
||||
max-width: 80%;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
button {
|
||||
margin:5px;
|
||||
border:solid black 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
color:white;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #225273!important;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background-color: black;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
}
|
||||
.stat:nth-child(2n) {
|
||||
background-color: #333;
|
||||
}
|
||||
.stat:empty {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<title>OBS Controller Demo using VDO.NInja</title>
|
||||
</head>
|
||||
|
||||
<div class="container">
|
||||
<h1>OBS remote (client)</h1>
|
||||
<div id="info">
|
||||
<div class="card">
|
||||
<h2>Scenes</h2>
|
||||
<div id="scene_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card hidden">
|
||||
<h2>General</h2>
|
||||
<div>
|
||||
<button onclick="basicCommand(this);" data-command="GetVersion">GetVersion</button>
|
||||
<button onclick="basicCommand(this);" data-command="GetStats">GetStats</button>
|
||||
<button onclick="basicCommand(this);" data-command="GetVideoInfo">GetVideoInfo</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Output</h2>
|
||||
<div id="outputs">
|
||||
<button onclick="basicCommand(this);" data-command="ListOutputs">ListOutputs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Active Sources</h2>
|
||||
<div id="active_source_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>All Sources</h2>
|
||||
<div id="source_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card hidden">
|
||||
<h2>Source debug</h2>
|
||||
<div>
|
||||
<button onclick="basicCommand(this);" data-command="GetMediaSourcesList">GetMediaSourcesList</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="GetAudioActive">GetAudioActive</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="audio" class="card hidden">
|
||||
<h2>Audio</h2>
|
||||
<div>
|
||||
<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" />
|
||||
<button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="ToggleMute">ToggleMute</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id='commands'>
|
||||
<h2>Custom Commands</h2>
|
||||
<input id="newCommand" placeholder="enter a custom command" type='text' /><button id="goCreate" onclick="createCommand()">Create</button>
|
||||
<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 style="width:100%;display:block;margin:0;padding:0;">
|
||||
<div id='OBSstats' style="width:49%;display:inline-block;vertical-align: top;">
|
||||
<div id="stat-current-profile" class='stat'></div>
|
||||
<div id="stat-current-scene" class='stat'></div>
|
||||
<div id="stat-streaming" class='stat'></div>
|
||||
<div id="stat-memory-usage" class='stat'></div>
|
||||
<div id="stat-recording" class='stat'></div>
|
||||
<div id="stat-recording-paused" class='stat'></div>
|
||||
<div id="stat-average-frame-time" class='stat'></div>
|
||||
<div id="stat-cpu-usage" class='stat'></div>
|
||||
<div id="stat-fps" class='stat'></div>
|
||||
<div id="stat-free-disk-space" class='stat'></div>
|
||||
</div>
|
||||
<div id='OBSsettings' style="width:49%;display:inline-block;vertical-align: top;">
|
||||
<div id="setting-baseHeight" class='stat'></div>
|
||||
<div id="setting-baseWidth" class='stat'></div>
|
||||
<div id="setting-outputHeight" class='stat'></div>
|
||||
<div id="setting-outputWidth" class='stat'></div>
|
||||
<div id="setting-scaleType" class='stat'></div>
|
||||
<div id="setting-fps" class='stat'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function basicCommand(ele, data={}){
|
||||
sendToOBS(ele.dataset.command, data);
|
||||
}
|
||||
|
||||
function createCommand(){
|
||||
var command = document.getElementById("newCommand").value;
|
||||
document.getElementById("newCommand").value = "";
|
||||
var button = document.createElement("button");
|
||||
button.innerText = command;
|
||||
button.dataset.command = command;
|
||||
button.onclick = function(){basicCommand(this);};
|
||||
document.getElementById("commands").appendChild(button);
|
||||
}
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
if (urlParams.get("password")){
|
||||
var password = urlParams.get("password");
|
||||
localStorage.setItem('password',password)
|
||||
} else if (localStorage.getItem('password')){
|
||||
password = localStorage.getItem('password');
|
||||
}
|
||||
if (urlParams.get("room")){
|
||||
var roomname = urlParams.get("room")
|
||||
localStorage.setItem('roomname',roomname)
|
||||
} else if (localStorage.getItem('roomname')){
|
||||
roomname = localStorage.getItem('roomname');
|
||||
}
|
||||
|
||||
const obs = new OBSWebSocket();
|
||||
var scenesData = {};
|
||||
var selectedSource = null;
|
||||
scenesData.scenes = [];
|
||||
|
||||
function sendToOBS(action, data){
|
||||
var msg = {};
|
||||
msg.sendToOBS = {};
|
||||
msg.sendToOBS.action = action;
|
||||
msg.sendToOBS.data = data;
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
}
|
||||
|
||||
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.style.opacity = 0;
|
||||
iframe.style.width = "160px";
|
||||
iframe.style.height = "90px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.top = "10px";
|
||||
iframe.style.right = "170px";
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){
|
||||
if ("sentFromOBS" in e.data.dataReceived){
|
||||
processIncoming(e.data.dataReceived.sentFromOBS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function changeScene(scene){
|
||||
sendToOBS('SetCurrentScene', {
|
||||
'scene-name': scene
|
||||
});
|
||||
}
|
||||
|
||||
function processIncoming(data){
|
||||
if ("scenes" in data){
|
||||
scenesData = data.scenes;
|
||||
updateSceneList();
|
||||
}
|
||||
if ("callbackData" in data){
|
||||
console.log(data.callbackData);
|
||||
} else if ("callbackError" in data){
|
||||
console.log(data.callbackError);
|
||||
}
|
||||
if ("rawData" in data){
|
||||
var data = JSON.parse(data.rawData);
|
||||
console.log(data);
|
||||
if ("stats" in data){
|
||||
var i = "stats";
|
||||
for (var j in data[i]){
|
||||
if (document.getElementById("stat-"+j)){
|
||||
|
||||
if (typeof data[i][j] == "number"){
|
||||
data[i][j] = parseInt(data[i][j]*100)/100.0;
|
||||
}
|
||||
if (data[i][j]===true){
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#CFC'>" + data[i][j]+"</font>";
|
||||
} else if (data[i][j] === false){
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#FCC'>" + data[i][j]+"</font>";
|
||||
} else {
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#DDD'>" + data[i][j]+"</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("baseHeight" in data){
|
||||
for (var i in data){
|
||||
if (document.getElementById("setting-"+i)){
|
||||
if (data[i]===true){
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#CFC'>" + data[i]+"</font>";
|
||||
} else if (data[i] === false){
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#FCC'>" + data[i]+"</font>";
|
||||
} else {
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#DDD'>" + data[i]+"</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("sources" in data){
|
||||
if ("name" in data){
|
||||
document.getElementById("active_source_list").innerHTML = "";
|
||||
for (var i =0;i<data.sources.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.sources[i].name;
|
||||
button.dataset.name = data.sources[i].name;
|
||||
button.dataset.type = "source"
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
selectedSource = this.dataset.name;
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
sources[k].classList.add("pressed");
|
||||
}
|
||||
};
|
||||
document.getElementById("active_source_list").appendChild(button);
|
||||
}
|
||||
if (selectedSource){
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("source_list").innerHTML = "";
|
||||
for (var i =0;i<data.sources.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.sources[i].name;
|
||||
button.dataset.name = data.sources[i].name;
|
||||
button.dataset.type = "source"
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
selectedSource = this.dataset.name;
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
};
|
||||
document.getElementById("source_list").appendChild(button);
|
||||
}
|
||||
if (selectedSource){
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
<!-- congestion: 0 -->
|
||||
<!-- droppedFrames: 0 -->
|
||||
<!-- flags: {audio: true, encoded: true, multiTrack: true, rawValue: 31, service: true, …} -->
|
||||
<!-- height: 1080 -->
|
||||
<!-- name: "simple_stream" -->
|
||||
<!-- reconnecting: false -->
|
||||
<!-- settings: {bind_ip: 'default', dyn_bitrate: false, low_latency_mode_enabled: false, new_socket_loop_enabled: false} -->
|
||||
<!-- totalBytes: 351121 -->
|
||||
<!-- totalFrames: 30 -->
|
||||
<!-- type: "rtmp_output" -->
|
||||
<!-- width: 1920 -->
|
||||
|
||||
} else if ("outputs" in data){
|
||||
document.getElementById("outputs").innerHTML = "";
|
||||
for (var i =0;i<data.outputs.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.outputs[i].name;
|
||||
button.dataset.output = data.outputs[i].name;
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
var outputName = this.dataset.output;
|
||||
if (this.classList.contains("pressed")){
|
||||
this.classList.remove("pressed");
|
||||
sendToOBS("StopOutput",{outputName:outputName});
|
||||
} else {
|
||||
this.classList.add("pressed");
|
||||
sendToOBS("StartOutput",{outputName:outputName});
|
||||
}
|
||||
};
|
||||
document.getElementById("outputs").appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updateSceneList(){
|
||||
var scenes = scenesData.scenes;
|
||||
document.getElementById("scene_list").innerHTML = "";
|
||||
scenes.forEach(scene => {
|
||||
var button = document.createElement("button");
|
||||
button.innerText = scene.name;
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED");
|
||||
changeScene(this.innerText);
|
||||
}; // "speaker" also works in the same way.
|
||||
document.getElementById("scene_list").appendChild(button);
|
||||
|
||||
if (scene.name === scenesData.currentScene) {
|
||||
button.classList.add("pressed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="thirdparty/obs-websocket.min.js"></script>
|
||||
<link rel="stylesheet" href="https://vdo.ninja/main.css" />
|
||||
<style>
|
||||
|
||||
.container {
|
||||
max-width: 80%;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.card>div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.5em;
|
||||
padding: 10px;
|
||||
background-color: #457b9d;
|
||||
color: white;
|
||||
border-bottom: 2px solid #3b6a87;
|
||||
}
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgb(212, 191, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
audio {
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
div#processing {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
background: #141926;
|
||||
flex-direction: column;
|
||||
}
|
||||
button {
|
||||
margin:5px;
|
||||
border:solid black 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
color:white;
|
||||
display: inline-block;
|
||||
flex-flow: unset;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #225273!important;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background-color: black;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
}
|
||||
.stat:nth-child(2n) {
|
||||
background-color: #333;
|
||||
}
|
||||
.stat:empty {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<title>OBS Controller Demo using VDO.NInja</title>
|
||||
</head>
|
||||
|
||||
<div class="container">
|
||||
<h1>OBS remote (client)</h1>
|
||||
<div id="info">
|
||||
<div class="card">
|
||||
<h2>Scenes</h2>
|
||||
<div id="scene_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card hidden">
|
||||
<h2>General</h2>
|
||||
<div>
|
||||
<button onclick="basicCommand(this);" data-command="GetVersion">GetVersion</button>
|
||||
<button onclick="basicCommand(this);" data-command="GetStats">GetStats</button>
|
||||
<button onclick="basicCommand(this);" data-command="GetVideoInfo">GetVideoInfo</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Output</h2>
|
||||
<div id="outputs">
|
||||
<button onclick="basicCommand(this);" data-command="ListOutputs">ListOutputs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Active Sources</h2>
|
||||
<div id="active_source_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>All Sources</h2>
|
||||
<div id="source_list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card hidden">
|
||||
<h2>Source debug</h2>
|
||||
<div>
|
||||
<button onclick="basicCommand(this);" data-command="GetMediaSourcesList">GetMediaSourcesList</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="GetAudioActive">GetAudioActive</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="audio" class="card hidden">
|
||||
<h2>Audio</h2>
|
||||
<div>
|
||||
<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" />
|
||||
<button class="hidden" onclick="basicCommand(this, {source:selectedSource});" data-command="ToggleMute">ToggleMute</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id='commands'>
|
||||
<h2>Custom Commands</h2>
|
||||
<input id="newCommand" placeholder="enter a custom command" type='text' /><button id="goCreate" onclick="createCommand()">Create</button>
|
||||
<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 style="width:100%;display:block;margin:0;padding:0;">
|
||||
<div id='OBSstats' style="width:49%;display:inline-block;vertical-align: top;">
|
||||
<div id="stat-current-profile" class='stat'></div>
|
||||
<div id="stat-current-scene" class='stat'></div>
|
||||
<div id="stat-streaming" class='stat'></div>
|
||||
<div id="stat-memory-usage" class='stat'></div>
|
||||
<div id="stat-recording" class='stat'></div>
|
||||
<div id="stat-recording-paused" class='stat'></div>
|
||||
<div id="stat-average-frame-time" class='stat'></div>
|
||||
<div id="stat-cpu-usage" class='stat'></div>
|
||||
<div id="stat-fps" class='stat'></div>
|
||||
<div id="stat-free-disk-space" class='stat'></div>
|
||||
</div>
|
||||
<div id='OBSsettings' style="width:49%;display:inline-block;vertical-align: top;">
|
||||
<div id="setting-baseHeight" class='stat'></div>
|
||||
<div id="setting-baseWidth" class='stat'></div>
|
||||
<div id="setting-outputHeight" class='stat'></div>
|
||||
<div id="setting-outputWidth" class='stat'></div>
|
||||
<div id="setting-scaleType" class='stat'></div>
|
||||
<div id="setting-fps" class='stat'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function basicCommand(ele, data={}){
|
||||
sendToOBS(ele.dataset.command, data);
|
||||
}
|
||||
|
||||
function createCommand(){
|
||||
var command = document.getElementById("newCommand").value;
|
||||
document.getElementById("newCommand").value = "";
|
||||
var button = document.createElement("button");
|
||||
button.innerText = command;
|
||||
button.dataset.command = command;
|
||||
button.onclick = function(){basicCommand(this);};
|
||||
document.getElementById("commands").appendChild(button);
|
||||
}
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
if (urlParams.get("password")){
|
||||
var password = urlParams.get("password");
|
||||
localStorage.setItem('password',password)
|
||||
} else if (localStorage.getItem('password')){
|
||||
password = localStorage.getItem('password');
|
||||
}
|
||||
if (urlParams.get("room")){
|
||||
var roomname = urlParams.get("room")
|
||||
localStorage.setItem('roomname',roomname)
|
||||
} else if (localStorage.getItem('roomname')){
|
||||
roomname = localStorage.getItem('roomname');
|
||||
}
|
||||
|
||||
const obs = new OBSWebSocket();
|
||||
var scenesData = {};
|
||||
var selectedSource = null;
|
||||
scenesData.scenes = [];
|
||||
|
||||
function sendToOBS(action, data){
|
||||
var msg = {};
|
||||
msg.sendToOBS = {};
|
||||
msg.sendToOBS.action = action;
|
||||
msg.sendToOBS.data = data;
|
||||
iframe.contentWindow.postMessage({"sendData":msg}, '*');
|
||||
}
|
||||
|
||||
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.style.opacity = 0;
|
||||
iframe.style.width = "160px";
|
||||
iframe.style.height = "90px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.top = "10px";
|
||||
iframe.style.right = "170px";
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){
|
||||
if ("sentFromOBS" in e.data.dataReceived){
|
||||
processIncoming(e.data.dataReceived.sentFromOBS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function changeScene(scene){
|
||||
sendToOBS('SetCurrentScene', {
|
||||
'scene-name': scene
|
||||
});
|
||||
}
|
||||
|
||||
function processIncoming(data){
|
||||
if ("scenes" in data){
|
||||
scenesData = data.scenes;
|
||||
updateSceneList();
|
||||
}
|
||||
if ("callbackData" in data){
|
||||
console.log(data.callbackData);
|
||||
} else if ("callbackError" in data){
|
||||
console.log(data.callbackError);
|
||||
}
|
||||
if ("rawData" in data){
|
||||
var data = JSON.parse(data.rawData);
|
||||
console.log(data);
|
||||
if ("stats" in data){
|
||||
var i = "stats";
|
||||
for (var j in data[i]){
|
||||
if (document.getElementById("stat-"+j)){
|
||||
|
||||
if (typeof data[i][j] == "number"){
|
||||
data[i][j] = parseInt(data[i][j]*100)/100.0;
|
||||
}
|
||||
if (data[i][j]===true){
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#CFC'>" + data[i][j]+"</font>";
|
||||
} else if (data[i][j] === false){
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#FCC'>" + data[i][j]+"</font>";
|
||||
} else {
|
||||
document.getElementById("stat-"+j).innerHTML = j+ " : <font color='#DDD'>" + data[i][j]+"</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("baseHeight" in data){
|
||||
for (var i in data){
|
||||
if (document.getElementById("setting-"+i)){
|
||||
if (data[i]===true){
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#CFC'>" + data[i]+"</font>";
|
||||
} else if (data[i] === false){
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#FCC'>" + data[i]+"</font>";
|
||||
} else {
|
||||
document.getElementById("setting-"+i).innerHTML = i+ " : <font color='#DDD'>" + data[i]+"</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("sources" in data){
|
||||
if ("name" in data){
|
||||
document.getElementById("active_source_list").innerHTML = "";
|
||||
for (var i =0;i<data.sources.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.sources[i].name;
|
||||
button.dataset.name = data.sources[i].name;
|
||||
button.dataset.type = "source"
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
selectedSource = this.dataset.name;
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
sources[k].classList.add("pressed");
|
||||
}
|
||||
};
|
||||
document.getElementById("active_source_list").appendChild(button);
|
||||
}
|
||||
if (selectedSource){
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("source_list").innerHTML = "";
|
||||
for (var i =0;i<data.sources.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.sources[i].name;
|
||||
button.dataset.name = data.sources[i].name;
|
||||
button.dataset.type = "source"
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
selectedSource = this.dataset.name;
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
};
|
||||
document.getElementById("source_list").appendChild(button);
|
||||
}
|
||||
if (selectedSource){
|
||||
var sources = document.querySelectorAll("[data-name");
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.remove("pressed");
|
||||
}
|
||||
var sources = document.querySelectorAll("[data-name='"+selectedSource+"']");
|
||||
console.log(sources);
|
||||
for (var k = 0 ; k<sources.length; k++){
|
||||
sources[k].classList.add("pressed");
|
||||
document.getElementById("audio").classList.remove("hidden");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("audio").classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
<!-- congestion: 0 -->
|
||||
<!-- droppedFrames: 0 -->
|
||||
<!-- flags: {audio: true, encoded: true, multiTrack: true, rawValue: 31, service: true, …} -->
|
||||
<!-- height: 1080 -->
|
||||
<!-- name: "simple_stream" -->
|
||||
<!-- reconnecting: false -->
|
||||
<!-- settings: {bind_ip: 'default', dyn_bitrate: false, low_latency_mode_enabled: false, new_socket_loop_enabled: false} -->
|
||||
<!-- totalBytes: 351121 -->
|
||||
<!-- totalFrames: 30 -->
|
||||
<!-- type: "rtmp_output" -->
|
||||
<!-- width: 1920 -->
|
||||
|
||||
} else if ("outputs" in data){
|
||||
document.getElementById("outputs").innerHTML = "";
|
||||
for (var i =0;i<data.outputs.length;i++){
|
||||
var button = document.createElement("button");
|
||||
button.innerText = data.outputs[i].name;
|
||||
button.dataset.output = data.outputs[i].name;
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED: " + this.innerText);
|
||||
var outputName = this.dataset.output;
|
||||
if (this.classList.contains("pressed")){
|
||||
this.classList.remove("pressed");
|
||||
sendToOBS("StopOutput",{outputName:outputName});
|
||||
} else {
|
||||
this.classList.add("pressed");
|
||||
sendToOBS("StartOutput",{outputName:outputName});
|
||||
}
|
||||
};
|
||||
document.getElementById("outputs").appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updateSceneList(){
|
||||
var scenes = scenesData.scenes;
|
||||
document.getElementById("scene_list").innerHTML = "";
|
||||
scenes.forEach(scene => {
|
||||
var button = document.createElement("button");
|
||||
button.innerText = scene.name;
|
||||
button.onclick = function(){
|
||||
console.log("CLICKED");
|
||||
changeScene(this.innerText);
|
||||
}; // "speaker" also works in the same way.
|
||||
document.getElementById("scene_list").appendChild(button);
|
||||
|
||||
if (scene.name === scenesData.currentScene) {
|
||||
button.classList.add("pressed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,146 +1,146 @@
|
||||
<html>
|
||||
<head><title>overlay + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="clean">
|
||||
<h1>Apply an Overlay to VDO.Ninja</h1>
|
||||
<input placeholder="Enter a VDON URL here" id="viewlink" 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)
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("overlayChatLink")){
|
||||
document.getElementById("overlay").value = getStorage("overlayChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaoverlayURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaoverlayURL");
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var overlay = document.getElementById("overlay").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
|
||||
if (!roomname){
|
||||
var room1 = "../";
|
||||
} else if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
var room1 = "https://"+roomname;
|
||||
}
|
||||
|
||||
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.src = room1;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
|
||||
if (!overlay){
|
||||
var room2 = "./test_overlay";
|
||||
} else if (overlay.startsWith("https://")){
|
||||
var room2 = overlay;
|
||||
} else {
|
||||
var room2 = "https://"+overlay;
|
||||
}
|
||||
|
||||
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.src = room2;
|
||||
iframe.style.pointerEvents = "none";
|
||||
iframe.style.backgroundColor = "#0000";
|
||||
iframe.style.width = "25vw";
|
||||
iframe.style.height = "25vh";
|
||||
iframe.style.overflow = "hidden";
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
if (roomname && overlay){
|
||||
setStorage("overlayChatLink", room2);
|
||||
setStorage("vdoNinjaoverlayURL", room1);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>overlay + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="clean">
|
||||
<h1>Apply an Overlay to VDO.Ninja</h1>
|
||||
<input placeholder="Enter a VDON URL here" id="viewlink" 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)
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("overlayChatLink")){
|
||||
document.getElementById("overlay").value = getStorage("overlayChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaoverlayURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaoverlayURL");
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var overlay = document.getElementById("overlay").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
|
||||
if (!roomname){
|
||||
var room1 = "../";
|
||||
} else if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
var room1 = "https://"+roomname;
|
||||
}
|
||||
|
||||
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.src = room1;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
|
||||
if (!overlay){
|
||||
var room2 = "./test_overlay";
|
||||
} else if (overlay.startsWith("https://")){
|
||||
var room2 = overlay;
|
||||
} else {
|
||||
var room2 = "https://"+overlay;
|
||||
}
|
||||
|
||||
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.src = room2;
|
||||
iframe.style.pointerEvents = "none";
|
||||
iframe.style.backgroundColor = "#0000";
|
||||
iframe.style.width = "25vw";
|
||||
iframe.style.height = "25vh";
|
||||
iframe.style.overflow = "hidden";
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
if (roomname && overlay){
|
||||
setStorage("overlayChatLink", room2);
|
||||
setStorage("vdoNinjaoverlayURL", room1);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,69 +1,69 @@
|
||||
<html>
|
||||
<body>
|
||||
<div id="results" style="overflow:scroll;max-height:300px;">
|
||||
starting...
|
||||
</div>
|
||||
<script> // https://jsfiddle.net/steveseguin/0t3ayvk8/31/
|
||||
var connectionID = Math.random()*100000001;
|
||||
function RecvDataWindow(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = "https://vdo.ninja/beta/?view="+connectionID+"&cleanoutput";
|
||||
iframe.style.width = "0px";
|
||||
iframe.style.height = "0px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.left = "-100px";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.id = "frame1"
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){ // raw data
|
||||
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
|
||||
console.log(e.data);
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":"pong!!", "UUID":e.data.UUID}, '*');
|
||||
} catch(E){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function SendDataWindow(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = "https://vdo.ninja/beta/?push="+connectionID+"&vd=0&ad=0&autostart&cleanoutput";
|
||||
iframe.style.width = "0px";
|
||||
iframe.style.height = "0px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.left = "-100px";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.id = "frame2";
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
setInterval(function(){
|
||||
try {
|
||||
console.log(".");
|
||||
document.getElementById("frame2").contentWindow.postMessage({"sendData":"ping!!"}, '*');
|
||||
} catch(E){}
|
||||
}, 1000);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){ // raw data
|
||||
console.log(e.data);
|
||||
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
|
||||
}
|
||||
});
|
||||
}
|
||||
SendDataWindow();
|
||||
RecvDataWindow();
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<body>
|
||||
<div id="results" style="overflow:scroll;max-height:300px;">
|
||||
starting...
|
||||
</div>
|
||||
<script> // https://jsfiddle.net/steveseguin/0t3ayvk8/31/
|
||||
var connectionID = Math.random()*100000001;
|
||||
function RecvDataWindow(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = "https://vdo.ninja/beta/?view="+connectionID+"&cleanoutput";
|
||||
iframe.style.width = "0px";
|
||||
iframe.style.height = "0px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.left = "-100px";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.id = "frame1"
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){ // raw data
|
||||
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
|
||||
console.log(e.data);
|
||||
try {
|
||||
iframe.contentWindow.postMessage({"sendData":"pong!!", "UUID":e.data.UUID}, '*');
|
||||
} catch(E){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function SendDataWindow(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = "https://vdo.ninja/beta/?push="+connectionID+"&vd=0&ad=0&autostart&cleanoutput";
|
||||
iframe.style.width = "0px";
|
||||
iframe.style.height = "0px";
|
||||
iframe.style.position = "fixed";
|
||||
iframe.style.left = "-100px";
|
||||
iframe.style.top = "-100px";
|
||||
iframe.id = "frame2";
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
setInterval(function(){
|
||||
try {
|
||||
console.log(".");
|
||||
document.getElementById("frame2").contentWindow.postMessage({"sendData":"ping!!"}, '*');
|
||||
} catch(E){}
|
||||
}, 1000);
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
if ("dataReceived" in e.data){ // raw data
|
||||
console.log(e.data);
|
||||
document.getElementById("results").innerHTML += e.data.dataReceived+"<br />";
|
||||
}
|
||||
});
|
||||
}
|
||||
SendDataWindow();
|
||||
RecvDataWindow();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,136 +1,136 @@
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
font-family: tahoma, arial;
|
||||
}
|
||||
|
||||
a {
|
||||
color:white
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
div{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
width:49%;
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div id="container1" style="width:100%;height:89%;display:none;"></div>
|
||||
<div id="container2" style="width:100%;height:10%;display:none;">
|
||||
<button onclick="prevSlide()" style='background-color:red'>Previous Slide</button>
|
||||
<button onclick="nextSlide()" style='background-color:green'>Next Slide</button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>PowerPoint Remote Control interface</h2>
|
||||
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()" /><br>
|
||||
<br>
|
||||
This app is a custom remote client for VDO.Ninja's PowerPoint remote control feature.
|
||||
<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.
|
||||
</div>
|
||||
<script>
|
||||
var iframe;
|
||||
|
||||
function nextSlide(){
|
||||
if (iframe){
|
||||
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 11]}}, '*');
|
||||
/// OR AS OF V22.12 YOU CAN DO:
|
||||
//iframe.contentWindow.postMessage({"nextSlide":true}, '*');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function prevSlide(){
|
||||
if (iframe){
|
||||
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 10]}}, '*');
|
||||
/// OR AS OF V22.12 YOU CAN DO:
|
||||
//iframe.contentWindow.postMessage({"prevSlide":true}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
function customCommand(){ // just an example of what you can do to make a custom action.
|
||||
if (iframe){
|
||||
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, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
var room = false;
|
||||
|
||||
if (urlParams.has("room") || urlParams.has("r")){
|
||||
room = urlParams.get("room") || urlParams.get("r") || false;
|
||||
}
|
||||
|
||||
if (room){
|
||||
loadIframes(room);
|
||||
}
|
||||
|
||||
function loadIframes(roomname=false){
|
||||
|
||||
if (!roomname){
|
||||
roomname = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
document.getElementById("viewlink").parentNode.parentNode.removeChild(document.getElementById("viewlink").parentNode);
|
||||
document.getElementById("container1").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 room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_controller&webcam&autostart&minipreview";
|
||||
|
||||
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.src = room1;
|
||||
document.getElementById("container1").appendChild(iframe);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
font-family: tahoma, arial;
|
||||
}
|
||||
|
||||
a {
|
||||
color:white
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
div{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
width:49%;
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div id="container1" style="width:100%;height:89%;display:none;"></div>
|
||||
<div id="container2" style="width:100%;height:10%;display:none;">
|
||||
<button onclick="prevSlide()" style='background-color:red'>Previous Slide</button>
|
||||
<button onclick="nextSlide()" style='background-color:green'>Next Slide</button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>PowerPoint Remote Control interface</h2>
|
||||
<input placeholder="Enter a Room name" id="viewlink" type="text" onchange="loadIframes()" /><br>
|
||||
<br>
|
||||
This app is a custom remote client for VDO.Ninja's PowerPoint remote control feature.
|
||||
<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.
|
||||
</div>
|
||||
<script>
|
||||
var iframe;
|
||||
|
||||
function nextSlide(){
|
||||
if (iframe){
|
||||
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 11]}}, '*');
|
||||
/// OR AS OF V22.12 YOU CAN DO:
|
||||
//iframe.contentWindow.postMessage({"nextSlide":true}, '*');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function prevSlide(){
|
||||
if (iframe){
|
||||
iframe.contentWindow.postMessage({"sendRawMIDI":{data:[176, 110, 10]}}, '*');
|
||||
/// OR AS OF V22.12 YOU CAN DO:
|
||||
//iframe.contentWindow.postMessage({"prevSlide":true}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
function customCommand(){ // just an example of what you can do to make a custom action.
|
||||
if (iframe){
|
||||
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, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
warnlog(window.location.search + " changed to " + urlEdited);
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
var room = false;
|
||||
|
||||
if (urlParams.has("room") || urlParams.has("r")){
|
||||
room = urlParams.get("room") || urlParams.get("r") || false;
|
||||
}
|
||||
|
||||
if (room){
|
||||
loadIframes(room);
|
||||
}
|
||||
|
||||
function loadIframes(roomname=false){
|
||||
|
||||
if (!roomname){
|
||||
roomname = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
document.getElementById("viewlink").parentNode.parentNode.removeChild(document.getElementById("viewlink").parentNode);
|
||||
document.getElementById("container1").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 room1 = "https://"+path+"/../?room="+roomname+"&push="+roomname+"_controller&webcam&autostart&minipreview";
|
||||
|
||||
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.src = room1;
|
||||
document.getElementById("container1").appendChild(iframe);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,23 +1,23 @@
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
@ -1,477 +1,477 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="./nes.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
html, body, pre, code, kbd, samp {
|
||||
font-family:"Press Start 2P";
|
||||
}
|
||||
|
||||
body{
|
||||
margin:1%;
|
||||
border:0;
|
||||
background-image: linear-gradient(to left, #e1c5d5, #ddc5da, #d8c6e0, #d1c7e5, #c8c9e9, #c1cded, #bad2f0, #b4d6f2, #b1ddf3, #b1e4f3, #b4eaf0, #bbf0ed);
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
button {
|
||||
margin:10px 3px;
|
||||
}
|
||||
|
||||
button, input, optgroup, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
button:active{
|
||||
background-color:#BBB;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header"></div>
|
||||
<div id="target_self"></div>
|
||||
<div id="guest_1_container"></div>
|
||||
<script>
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 10; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
console.error(errorMsg);
|
||||
console.error(lineNumber);
|
||||
console.error("Unhandeled Error occured"); //or any message
|
||||
return false;
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
return "Dude, are you sure you want to leave? Think of the kittens!"; // prevents accidental page reloads.
|
||||
}
|
||||
|
||||
var WID = "testVDON";
|
||||
if (urlParams.has("api")){
|
||||
WID = urlParams.get("api");
|
||||
} else if (urlParams.has("osc")){
|
||||
WID = urlParams.get("osc");
|
||||
} else if (urlParams.has("id")){
|
||||
WID = urlParams.get("id");
|
||||
} else if (urlParams.has("ID")){
|
||||
WID = urlParams.get("ID");
|
||||
} else if (urlParams.has("wid")){
|
||||
WID = urlParams.get("wid");
|
||||
} else {
|
||||
WID = generateStreamID(10);
|
||||
|
||||
var href = window.location.href;
|
||||
var arr = href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = href + '&api=' + WID;
|
||||
} else {
|
||||
newurl = href + '?api=' + WID;
|
||||
}
|
||||
|
||||
window.history.pushState({path: newurl.toString()}, '', newurl.toString());
|
||||
|
||||
}
|
||||
|
||||
var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
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 += "<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>";
|
||||
|
||||
var socket = null;
|
||||
var connecting = false;
|
||||
var failedCount = 0;
|
||||
|
||||
|
||||
|
||||
function connect(){
|
||||
clearTimeout(connecting);
|
||||
if (socket){
|
||||
if (socket.readyState === socket.OPEN){return;}
|
||||
try{
|
||||
socket.close();
|
||||
} catch(e){}
|
||||
}
|
||||
socket = new WebSocket("wss://api.vdo.ninja:443");
|
||||
|
||||
socket.onclose = function (){
|
||||
failedCount+=1;
|
||||
clearTimeout(connecting);
|
||||
connecting = setTimeout(function(){connect();},100*(failedCount-1));
|
||||
};
|
||||
|
||||
socket.onerror = function (){
|
||||
failedCount+=1;
|
||||
clearTimeout(connecting);
|
||||
connecting = setTimeout(function(){connect();},100*failedCount);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
failedCount = 0;
|
||||
try{
|
||||
socket.send(JSON.stringify({"join":WID}));
|
||||
} catch(e){
|
||||
connecting = setTimeout(function(){connect();},1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
function sendGuestCommand(target, action, value=null){
|
||||
sendMessage(JSON.stringify({"target":target, "action":action, "value":value}));
|
||||
}
|
||||
|
||||
function sendMessage(msg){
|
||||
if (socket.readyState !== socket.OPEN){
|
||||
console.log("not connected; msg didn't send");
|
||||
connect();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
socket.send(msg);
|
||||
} catch(e){
|
||||
connecting = setTimeout(function(){connect();},100);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function log(msg){
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
function ajax(data) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
log("AJAX MESSAGE SENT SUCCESSFULL");
|
||||
}
|
||||
};
|
||||
var action = false
|
||||
if ("action" in data){
|
||||
action=data['action'];
|
||||
}
|
||||
var value = "null"
|
||||
if ("value" in data){
|
||||
value=data['value'];
|
||||
}
|
||||
var apiid = false
|
||||
if ("apiid" in data){
|
||||
apiid=data['apiid'];
|
||||
}
|
||||
var target = "null";
|
||||
if ("target" in data){
|
||||
target=data['target'];
|
||||
}
|
||||
|
||||
if (!action || !apiid){
|
||||
alert("no action or api ID provided; request won't work");
|
||||
} else {
|
||||
var URL = "https://api.vdo.ninja/"+apiid+"/"+action+"/"+target+"/"+value;
|
||||
xhttp.open("GET", URL, true);
|
||||
xhttp.send();
|
||||
}
|
||||
}
|
||||
|
||||
function loadSelfCommands(){
|
||||
var commands = {}
|
||||
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.camera = function(value){sendMessage(JSON.stringify({"action":"camera","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.record = function(value){sendMessage(JSON.stringify({"action":"record","value":value}))};
|
||||
|
||||
commands.sayHello = function(value){sendMessage(JSON.stringify({"action":"sendChat","value":"Hello"}))};
|
||||
|
||||
var target_self = document.getElementById("target_self");
|
||||
|
||||
setTimeout(function(){
|
||||
var hr = document.createElement("hr");
|
||||
target_self.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These are Websocket-based requests";
|
||||
target_self.appendChild(h3);
|
||||
},0);
|
||||
|
||||
for (var k in commands) {
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />FALSE";
|
||||
button.onclick = function(){commands[k](false);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TOGGLE";
|
||||
button.onclick = function(){commands[k]("toggle");}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
}
|
||||
|
||||
log(k);
|
||||
} // list available commands to console
|
||||
|
||||
commands.reload = function(){sendMessage(JSON.stringify({"action":"reload","value":true}))};
|
||||
|
||||
commands.hangup = function(){sendMessage(JSON.stringify({"action":"hangup","value":true}))};
|
||||
|
||||
k = "reload";
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
k = "hangup";
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Rainbow<br />Puke";
|
||||
button.onclick = function(){sendMessage(JSON.stringify({"action":"forceKeyframe"}))}
|
||||
target_self.appendChild(button);
|
||||
},0);
|
||||
|
||||
|
||||
|
||||
var commands2 = {}
|
||||
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.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.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
|
||||
|
||||
setTimeout(function(){
|
||||
var hr = document.createElement("hr");
|
||||
target_self.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These are HTTP-based GET requests";
|
||||
target_self.appendChild(h3);
|
||||
},0);
|
||||
|
||||
for (var k in commands2) {
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands2[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />FALSE";
|
||||
button.onclick = function(){commands2[k](false);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TOGGLE";
|
||||
button.onclick = function(){commands2[k]("toggle");}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
}
|
||||
log(k);
|
||||
}
|
||||
|
||||
setTimeout(function(WID){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Rainbow<br />Puke"
|
||||
button.onclick = function(){ajax({"action":"forceKeyframe","apiid":WID})}
|
||||
target_self.appendChild(button);
|
||||
},0,WID);
|
||||
|
||||
|
||||
return commands;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function loadGuestCommands(guestid){
|
||||
var container = document.createElement("div");
|
||||
container.id = "guest_"+guestid+"_container";
|
||||
document.body.appendChild(container);
|
||||
|
||||
var hr = document.createElement("hr");
|
||||
container.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These target guest "+guestid+ " (if a director)";
|
||||
container.appendChild(h3);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "transfer popup";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forward");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "transfer to 'room321'";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forward", 'room321');};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 1";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene");}; /// SCENE 1 or specify a custom scene name as a value
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "mute in scene";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "muteScene");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "mute everywhere";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "mic");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "hang up";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "hangup");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "solo chat";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "soloChat");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remote speaker";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "speaker");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remote display";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "display");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "rainbow puke fix";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forceKeyframe");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "highlight";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "soloVideo");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 2";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 2);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 3";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 3);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 4";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 4);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 5";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 5);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 6";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 6);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = " scene 7";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 7);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 8";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 8);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 'test'";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 'test');}; // specifying a custom scene; it needs to be active for this to work..
|
||||
container.appendChild(button);
|
||||
|
||||
|
||||
var input = document.createElement("label");
|
||||
input.innerHTML = "mic volume:";
|
||||
container.appendChild(input);
|
||||
var input = document.createElement("input");
|
||||
input.type = "range";
|
||||
input.title = "volume";
|
||||
input.min = 0;
|
||||
input.max = 200;
|
||||
input.value = 100;
|
||||
input.onchange = function(){sendGuestCommand(guestid, "volume", this.value);};
|
||||
container.appendChild(input);
|
||||
|
||||
}
|
||||
|
||||
loadSelfCommands();
|
||||
loadGuestCommands(1);
|
||||
loadGuestCommands(2);
|
||||
loadGuestCommands(3);
|
||||
loadGuestCommands(4);
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="nes.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
html, body, pre, code, kbd, samp {
|
||||
font-family:"Press Start 2P";
|
||||
}
|
||||
|
||||
body{
|
||||
margin:1%;
|
||||
border:0;
|
||||
background-image: linear-gradient(to left, #e1c5d5, #ddc5da, #d8c6e0, #d1c7e5, #c8c9e9, #c1cded, #bad2f0, #b4d6f2, #b1ddf3, #b1e4f3, #b4eaf0, #bbf0ed);
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
button {
|
||||
margin:10px 3px;
|
||||
}
|
||||
|
||||
button, input, optgroup, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
button:active{
|
||||
background-color:#BBB;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header"></div>
|
||||
<div id="target_self"></div>
|
||||
<div id="guest_1_container"></div>
|
||||
<script>
|
||||
function generateStreamID(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||
for (var i = 0; i < 10; i++){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
console.error(errorMsg);
|
||||
console.error(lineNumber);
|
||||
console.error("Unhandeled Error occured"); //or any message
|
||||
return false;
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
return "Dude, are you sure you want to leave? Think of the kittens!"; // prevents accidental page reloads.
|
||||
}
|
||||
|
||||
var WID = "testVDON";
|
||||
if (urlParams.has("api")){
|
||||
WID = urlParams.get("api");
|
||||
} else if (urlParams.has("osc")){
|
||||
WID = urlParams.get("osc");
|
||||
} else if (urlParams.has("id")){
|
||||
WID = urlParams.get("id");
|
||||
} else if (urlParams.has("ID")){
|
||||
WID = urlParams.get("ID");
|
||||
} else if (urlParams.has("wid")){
|
||||
WID = urlParams.get("wid");
|
||||
} else {
|
||||
WID = generateStreamID(10);
|
||||
|
||||
var href = window.location.href;
|
||||
var arr = href.split('?');
|
||||
var newurl;
|
||||
if (arr.length > 1 && arr[1] !== '') {
|
||||
newurl = href + '&api=' + WID;
|
||||
} else {
|
||||
newurl = href + '?api=' + WID;
|
||||
}
|
||||
|
||||
window.history.pushState({path: newurl.toString()}, '', newurl.toString());
|
||||
|
||||
}
|
||||
|
||||
var path = "vdo.ninja"; //window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
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 += "<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>";
|
||||
|
||||
var socket = null;
|
||||
var connecting = false;
|
||||
var failedCount = 0;
|
||||
|
||||
|
||||
|
||||
function connect(){
|
||||
clearTimeout(connecting);
|
||||
if (socket){
|
||||
if (socket.readyState === socket.OPEN){return;}
|
||||
try{
|
||||
socket.close();
|
||||
} catch(e){}
|
||||
}
|
||||
socket = new WebSocket("wss://api.vdo.ninja:443");
|
||||
|
||||
socket.onclose = function (){
|
||||
failedCount+=1;
|
||||
clearTimeout(connecting);
|
||||
connecting = setTimeout(function(){connect();},100*(failedCount-1));
|
||||
};
|
||||
|
||||
socket.onerror = function (){
|
||||
failedCount+=1;
|
||||
clearTimeout(connecting);
|
||||
connecting = setTimeout(function(){connect();},100*failedCount);
|
||||
};
|
||||
|
||||
socket.onopen = function (){
|
||||
failedCount = 0;
|
||||
try{
|
||||
socket.send(JSON.stringify({"join":WID}));
|
||||
} catch(e){
|
||||
connecting = setTimeout(function(){connect();},1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
function sendGuestCommand(target, action, value=null){
|
||||
sendMessage(JSON.stringify({"target":target, "action":action, "value":value}));
|
||||
}
|
||||
|
||||
function sendMessage(msg){
|
||||
if (socket.readyState !== socket.OPEN){
|
||||
console.log("not connected; msg didn't send");
|
||||
connect();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
socket.send(msg);
|
||||
} catch(e){
|
||||
connecting = setTimeout(function(){connect();},100);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function log(msg){
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
function ajax(data) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
log("AJAX MESSAGE SENT SUCCESSFULL");
|
||||
}
|
||||
};
|
||||
var action = false
|
||||
if ("action" in data){
|
||||
action=data['action'];
|
||||
}
|
||||
var value = "null"
|
||||
if ("value" in data){
|
||||
value=data['value'];
|
||||
}
|
||||
var apiid = false
|
||||
if ("apiid" in data){
|
||||
apiid=data['apiid'];
|
||||
}
|
||||
var target = "null";
|
||||
if ("target" in data){
|
||||
target=data['target'];
|
||||
}
|
||||
|
||||
if (!action || !apiid){
|
||||
alert("no action or api ID provided; request won't work");
|
||||
} else {
|
||||
var URL = "https://api.vdo.ninja/"+apiid+"/"+action+"/"+target+"/"+value;
|
||||
xhttp.open("GET", URL, true);
|
||||
xhttp.send();
|
||||
}
|
||||
}
|
||||
|
||||
function loadSelfCommands(){
|
||||
var commands = {}
|
||||
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.camera = function(value){sendMessage(JSON.stringify({"action":"camera","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.record = function(value){sendMessage(JSON.stringify({"action":"record","value":value}))};
|
||||
|
||||
commands.sayHello = function(value){sendMessage(JSON.stringify({"action":"sendChat","value":"Hello"}))};
|
||||
|
||||
var target_self = document.getElementById("target_self");
|
||||
|
||||
setTimeout(function(){
|
||||
var hr = document.createElement("hr");
|
||||
target_self.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These are Websocket-based requests";
|
||||
target_self.appendChild(h3);
|
||||
},0);
|
||||
|
||||
for (var k in commands) {
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />FALSE";
|
||||
button.onclick = function(){commands[k](false);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TOGGLE";
|
||||
button.onclick = function(){commands[k]("toggle");}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
}
|
||||
|
||||
log(k);
|
||||
} // list available commands to console
|
||||
|
||||
commands.reload = function(){sendMessage(JSON.stringify({"action":"reload","value":true}))};
|
||||
|
||||
commands.hangup = function(){sendMessage(JSON.stringify({"action":"hangup","value":true}))};
|
||||
|
||||
k = "reload";
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
k = "hangup";
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Rainbow<br />Puke";
|
||||
button.onclick = function(){sendMessage(JSON.stringify({"action":"forceKeyframe"}))}
|
||||
target_self.appendChild(button);
|
||||
},0);
|
||||
|
||||
|
||||
|
||||
var commands2 = {}
|
||||
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.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.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
|
||||
|
||||
setTimeout(function(){
|
||||
var hr = document.createElement("hr");
|
||||
target_self.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These are HTTP-based GET requests";
|
||||
target_self.appendChild(h3);
|
||||
},0);
|
||||
|
||||
for (var k in commands2) {
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TRUE";
|
||||
button.onclick = function(){commands2[k](true);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />FALSE";
|
||||
button.onclick = function(){commands2[k](false);}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
|
||||
if (k=="mic" || k=="camera" || k=="record" || k=="speaker"){
|
||||
setTimeout(function(k){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = k + ":<br />TOGGLE";
|
||||
button.onclick = function(){commands2[k]("toggle");}
|
||||
target_self.appendChild(button);
|
||||
},0,k);
|
||||
}
|
||||
log(k);
|
||||
}
|
||||
|
||||
setTimeout(function(WID){
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "Rainbow<br />Puke"
|
||||
button.onclick = function(){ajax({"action":"forceKeyframe","apiid":WID})}
|
||||
target_self.appendChild(button);
|
||||
},0,WID);
|
||||
|
||||
|
||||
return commands;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function loadGuestCommands(guestid){
|
||||
var container = document.createElement("div");
|
||||
container.id = "guest_"+guestid+"_container";
|
||||
document.body.appendChild(container);
|
||||
|
||||
var hr = document.createElement("hr");
|
||||
container.appendChild(hr);
|
||||
var h3 = document.createElement("h3");
|
||||
h3.innerText = "These target guest "+guestid+ " (if a director)";
|
||||
container.appendChild(h3);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "transfer popup";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forward");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "transfer to 'room321'";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forward", 'room321');};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 1";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene");}; /// SCENE 1 or specify a custom scene name as a value
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "mute in scene";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "muteScene");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "mute everywhere";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "mic");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "hang up";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "hangup");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "solo chat";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "soloChat");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remote speaker";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "speaker");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "remote display";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "display");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "rainbow puke fix";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "forceKeyframe");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "highlight";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "soloVideo");};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 2";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 2);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 3";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 3);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 4";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 4);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 5";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 5);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 6";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 6);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = " scene 7";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 7);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 8";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 8);};
|
||||
container.appendChild(button);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = "scene 'test'";
|
||||
button.onclick = function(){sendGuestCommand(guestid, "addScene", 'test');}; // specifying a custom scene; it needs to be active for this to work..
|
||||
container.appendChild(button);
|
||||
|
||||
|
||||
var input = document.createElement("label");
|
||||
input.innerHTML = "mic volume:";
|
||||
container.appendChild(input);
|
||||
var input = document.createElement("input");
|
||||
input.type = "range";
|
||||
input.title = "volume";
|
||||
input.min = 0;
|
||||
input.max = 200;
|
||||
input.value = 100;
|
||||
input.onchange = function(){sendGuestCommand(guestid, "volume", this.value);};
|
||||
container.appendChild(input);
|
||||
|
||||
}
|
||||
|
||||
loadSelfCommands();
|
||||
loadGuestCommands(1);
|
||||
loadGuestCommands(2);
|
||||
loadGuestCommands(3);
|
||||
loadGuestCommands(4);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,135 +1,135 @@
|
||||
<html>
|
||||
<head><title>Rotated Scene</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:470px;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width: 100vh;
|
||||
height: 100vw;
|
||||
transform: rotate(90deg);
|
||||
transform-origin: 0 0;
|
||||
left: 100vw;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function clearStorage(){
|
||||
localStorage.clear();
|
||||
if (!session.cleanOutput){
|
||||
warnUser("The local storage and saved settings have been cleared", 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
var rotate = parseInt(urlParams.get("rotate")) || "90";
|
||||
|
||||
var sdfasd = decodeURIComponent(urlParams.get("link") || "") || getStorage("savedRotateLink") || "";
|
||||
|
||||
var linktoload = sdfasd || prompt("What URL would you like to load? (rotated)");
|
||||
setStorage("savedRotateLink", linktoload, 99999);
|
||||
|
||||
|
||||
|
||||
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.src = linktoload;
|
||||
|
||||
if (rotate=="180"){
|
||||
iframe.style.transform = "rotate(180deg)";
|
||||
iframe.style.width = "100vw";
|
||||
iframe.style.height = "100vh";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.position = "rotate(180deg)";
|
||||
iframe.style.left = "100vw";
|
||||
iframe.style.top = "100vh";
|
||||
} else if (rotate=="270"){
|
||||
iframe.style.transform = "rotate(270deg)";
|
||||
iframe.style.left = "0";
|
||||
iframe.style.top = "100vh";
|
||||
}
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Rotated Scene</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:470px;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width: 100vh;
|
||||
height: 100vw;
|
||||
transform: rotate(90deg);
|
||||
transform-origin: 0 0;
|
||||
left: 100vw;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function clearStorage(){
|
||||
localStorage.clear();
|
||||
if (!session.cleanOutput){
|
||||
warnUser("The local storage and saved settings have been cleared", 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
|
||||
(function(w) {
|
||||
w.URLSearchParams = w.URLSearchParams || function(searchString) {
|
||||
var self = this;
|
||||
searchString = searchString.replace("??", "?");
|
||||
self.searchString = searchString;
|
||||
self.get = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
var urlEdited = window.location.search.replace(/\?\?/g, "?");
|
||||
urlEdited = urlEdited.replace(/\?/g, "&");
|
||||
urlEdited = urlEdited.replace(/\&/, "?");
|
||||
|
||||
if (urlEdited !== window.location.search){
|
||||
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
|
||||
}
|
||||
var urlParams = new URLSearchParams(urlEdited);
|
||||
|
||||
var rotate = parseInt(urlParams.get("rotate")) || "90";
|
||||
|
||||
var sdfasd = decodeURIComponent(urlParams.get("link") || "") || getStorage("savedRotateLink") || "";
|
||||
|
||||
var linktoload = sdfasd || prompt("What URL would you like to load? (rotated)");
|
||||
setStorage("savedRotateLink", linktoload, 99999);
|
||||
|
||||
|
||||
|
||||
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.src = linktoload;
|
||||
|
||||
if (rotate=="180"){
|
||||
iframe.style.transform = "rotate(180deg)";
|
||||
iframe.style.width = "100vw";
|
||||
iframe.style.height = "100vh";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.position = "rotate(180deg)";
|
||||
iframe.style.left = "100vw";
|
||||
iframe.style.top = "100vh";
|
||||
} else if (rotate=="270"){
|
||||
iframe.style.transform = "rotate(270deg)";
|
||||
iframe.style.left = "0";
|
||||
iframe.style.top = "100vh";
|
||||
}
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,266 +1,266 @@
|
||||
<html>
|
||||
<head><title>Video with sensor overlayed data</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
|
||||
}
|
||||
|
||||
#overlay{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
text-align:right;
|
||||
position:absolute;
|
||||
top:100px;
|
||||
right:0;
|
||||
z-index: 10;
|
||||
color: white;
|
||||
font-size:300%;
|
||||
}
|
||||
|
||||
#canvas{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:20%;
|
||||
text-align:right;
|
||||
height:100px;
|
||||
position:absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="main">
|
||||
<div id="overlay"></div>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
function getColor(value) {
|
||||
var hue = (Math.abs(value*100+50)).toString(10);
|
||||
return ["hsl(", hue, ",100%,50%)"].join("");
|
||||
}
|
||||
var canvas = document.getElementById("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
var height = context.canvas.height;
|
||||
var width = context.canvas.width;
|
||||
canvas.history_accel = [];
|
||||
canvas.history_speed = [];
|
||||
var canvasNew = true
|
||||
|
||||
function plotData(speed, accel) {
|
||||
|
||||
|
||||
if (isNaN(speed)) {
|
||||
speed = 0;
|
||||
}
|
||||
if (isNaN(accel)) {
|
||||
accel = 0;
|
||||
}
|
||||
|
||||
canvas.history_accel.push(accel);
|
||||
canvas.history_speed.push(speed);
|
||||
|
||||
canvas.history_accel = canvas.history_accel.slice(-1 * canvas.width);
|
||||
canvas.history_speed = canvas.history_speed.slice(-1 * canvas.width);
|
||||
|
||||
var maxSpeed = Math.max(...canvas.history_speed);
|
||||
|
||||
var interval = 10;
|
||||
var target = canvas.target || (interval*1.5);
|
||||
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
|
||||
var yScale = height / canvas.target;
|
||||
context.clearRect(0, 0, width, height);
|
||||
var w = 1;
|
||||
var x = width - w;
|
||||
|
||||
|
||||
for (var i = 0; i<canvas.history_speed.length;i++){
|
||||
|
||||
var accel = canvas.history_accel[i];
|
||||
var speed = canvas.history_speed[i];
|
||||
|
||||
var val = (10-accel)/10;
|
||||
if (val>1){val=1;}
|
||||
else if (val<0){val=0;}
|
||||
var color = getColor(val);
|
||||
var y = height - speed * yScale;
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, w, height);
|
||||
context.fillStyle = "#DDD5";
|
||||
context.fillRect(x, y-2, w, 4);
|
||||
|
||||
if (y-5>0){
|
||||
context.fillStyle = "#FFF3";
|
||||
context.fillRect(x, y+2, w, 1);
|
||||
}
|
||||
|
||||
var imageData = context.getImageData(w, 0, x, height);
|
||||
context.putImageData(imageData, 0, 0);
|
||||
context.clearRect(x, 0, w, height);
|
||||
}
|
||||
|
||||
for (var tt = interval; tt<canvas.target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillStyle = "#0555";
|
||||
context.fillRect(0, y, width, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var val = (10-accel)/10;
|
||||
if (val>1){val=1;}
|
||||
else if (val<0){val=0;}
|
||||
var color = getColor(val);
|
||||
|
||||
var yScale = height / target;
|
||||
|
||||
var w = 1;
|
||||
var x = width - w;
|
||||
var y = height - speed * yScale;
|
||||
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, w, height);
|
||||
context.fillStyle = "#DDD5";
|
||||
context.fillRect(x, y-2, w, 4);
|
||||
|
||||
if (y-5>0){
|
||||
context.fillStyle = "#FFF3";
|
||||
context.fillRect(x, y+2, w, 1);
|
||||
}
|
||||
|
||||
context.fillStyle = "#0555";
|
||||
if (canvasNew){
|
||||
canvasNew = false;
|
||||
for (var tt = interval; tt<target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillRect(0, y, width, 1);
|
||||
}
|
||||
} else {
|
||||
for (var tt = interval; tt<target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillRect(x, y, w, 1);
|
||||
}
|
||||
}
|
||||
|
||||
var imageData = context.getImageData(w, 0, x, height);
|
||||
context.putImageData(imageData, 0, 0);
|
||||
context.clearRect(x, 0, w, height);
|
||||
|
||||
}
|
||||
|
||||
function loadIframe(url=false){
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="./";
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
|
||||
document.getElementById("container").appendChild(iframe);
|
||||
var outputWindow = document.getElementById("overlay");
|
||||
|
||||
var sensors = {};
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
if ("sensors" in e.data){
|
||||
//console.log(e.data.sensors);
|
||||
|
||||
var speed = 0;
|
||||
if (e.data.sensors.pos){
|
||||
speed = e.data.sensors.pos.speed;
|
||||
// e.data.sensors.pos.alt
|
||||
// e.data.sensors.pos.t
|
||||
}
|
||||
|
||||
var accel = 0;
|
||||
if (e.data.sensors.lin){
|
||||
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.z, 2);
|
||||
}
|
||||
if (accel){
|
||||
accel = Math.pow(accel,0.5);
|
||||
}
|
||||
|
||||
if (isNaN(accel)){
|
||||
accel = 0;
|
||||
}
|
||||
|
||||
plotData(speed, accel);
|
||||
|
||||
outputWindow.innerHTML = "";
|
||||
|
||||
speed = parseInt(speed*100)/100;
|
||||
outputWindow.innerHTML += "speed: "+speed+"m/s<br />";
|
||||
|
||||
accel = parseInt(accel*100)/100;
|
||||
outputWindow.innerHTML += "acceleration: " + accel + "m/s^2<br />";
|
||||
|
||||
//for (var key in e.data.sensors.lin) {
|
||||
// outputWindow.innerHTML += key + " lin: " + e.data.sensors.lin[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.acc) {
|
||||
// outputWindow.innerHTML += key + " acc: " + e.data.sensors.acc[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.mag) {
|
||||
// outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.ori) {
|
||||
// outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
|
||||
//}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadIframe("../"+window.location.search);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Video with sensor overlayed data</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
|
||||
}
|
||||
|
||||
#overlay{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
text-align:right;
|
||||
position:absolute;
|
||||
top:100px;
|
||||
right:0;
|
||||
z-index: 10;
|
||||
color: white;
|
||||
font-size:300%;
|
||||
}
|
||||
|
||||
#canvas{
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:20%;
|
||||
text-align:right;
|
||||
height:100px;
|
||||
position:absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="main">
|
||||
<div id="overlay"></div>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
function getColor(value) {
|
||||
var hue = (Math.abs(value*100+50)).toString(10);
|
||||
return ["hsl(", hue, ",100%,50%)"].join("");
|
||||
}
|
||||
var canvas = document.getElementById("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
var height = context.canvas.height;
|
||||
var width = context.canvas.width;
|
||||
canvas.history_accel = [];
|
||||
canvas.history_speed = [];
|
||||
var canvasNew = true
|
||||
|
||||
function plotData(speed, accel) {
|
||||
|
||||
|
||||
if (isNaN(speed)) {
|
||||
speed = 0;
|
||||
}
|
||||
if (isNaN(accel)) {
|
||||
accel = 0;
|
||||
}
|
||||
|
||||
canvas.history_accel.push(accel);
|
||||
canvas.history_speed.push(speed);
|
||||
|
||||
canvas.history_accel = canvas.history_accel.slice(-1 * canvas.width);
|
||||
canvas.history_speed = canvas.history_speed.slice(-1 * canvas.width);
|
||||
|
||||
var maxSpeed = Math.max(...canvas.history_speed);
|
||||
|
||||
var interval = 10;
|
||||
var target = canvas.target || (interval*1.5);
|
||||
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
|
||||
var yScale = height / canvas.target;
|
||||
context.clearRect(0, 0, width, height);
|
||||
var w = 1;
|
||||
var x = width - w;
|
||||
|
||||
|
||||
for (var i = 0; i<canvas.history_speed.length;i++){
|
||||
|
||||
var accel = canvas.history_accel[i];
|
||||
var speed = canvas.history_speed[i];
|
||||
|
||||
var val = (10-accel)/10;
|
||||
if (val>1){val=1;}
|
||||
else if (val<0){val=0;}
|
||||
var color = getColor(val);
|
||||
var y = height - speed * yScale;
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, w, height);
|
||||
context.fillStyle = "#DDD5";
|
||||
context.fillRect(x, y-2, w, 4);
|
||||
|
||||
if (y-5>0){
|
||||
context.fillStyle = "#FFF3";
|
||||
context.fillRect(x, y+2, w, 1);
|
||||
}
|
||||
|
||||
var imageData = context.getImageData(w, 0, x, height);
|
||||
context.putImageData(imageData, 0, 0);
|
||||
context.clearRect(x, 0, w, height);
|
||||
}
|
||||
|
||||
for (var tt = interval; tt<canvas.target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillStyle = "#0555";
|
||||
context.fillRect(0, y, width, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var val = (10-accel)/10;
|
||||
if (val>1){val=1;}
|
||||
else if (val<0){val=0;}
|
||||
var color = getColor(val);
|
||||
|
||||
var yScale = height / target;
|
||||
|
||||
var w = 1;
|
||||
var x = width - w;
|
||||
var y = height - speed * yScale;
|
||||
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, w, height);
|
||||
context.fillStyle = "#DDD5";
|
||||
context.fillRect(x, y-2, w, 4);
|
||||
|
||||
if (y-5>0){
|
||||
context.fillStyle = "#FFF3";
|
||||
context.fillRect(x, y+2, w, 1);
|
||||
}
|
||||
|
||||
context.fillStyle = "#0555";
|
||||
if (canvasNew){
|
||||
canvasNew = false;
|
||||
for (var tt = interval; tt<target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillRect(0, y, width, 1);
|
||||
}
|
||||
} else {
|
||||
for (var tt = interval; tt<target;tt+=interval){
|
||||
var y = parseInt(height - tt * yScale);
|
||||
context.fillRect(x, y, w, 1);
|
||||
}
|
||||
}
|
||||
|
||||
var imageData = context.getImageData(w, 0, x, height);
|
||||
context.putImageData(imageData, 0, 0);
|
||||
context.clearRect(x, 0, w, height);
|
||||
|
||||
}
|
||||
|
||||
function loadIframe(url=false){
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="./";
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
|
||||
document.getElementById("container").appendChild(iframe);
|
||||
var outputWindow = document.getElementById("overlay");
|
||||
|
||||
var sensors = {};
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
if ("sensors" in e.data){
|
||||
//console.log(e.data.sensors);
|
||||
|
||||
var speed = 0;
|
||||
if (e.data.sensors.pos){
|
||||
speed = e.data.sensors.pos.speed;
|
||||
// e.data.sensors.pos.alt
|
||||
// e.data.sensors.pos.t
|
||||
}
|
||||
|
||||
var accel = 0;
|
||||
if (e.data.sensors.lin){
|
||||
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.z, 2);
|
||||
}
|
||||
if (accel){
|
||||
accel = Math.pow(accel,0.5);
|
||||
}
|
||||
|
||||
if (isNaN(accel)){
|
||||
accel = 0;
|
||||
}
|
||||
|
||||
plotData(speed, accel);
|
||||
|
||||
outputWindow.innerHTML = "";
|
||||
|
||||
speed = parseInt(speed*100)/100;
|
||||
outputWindow.innerHTML += "speed: "+speed+"m/s<br />";
|
||||
|
||||
accel = parseInt(accel*100)/100;
|
||||
outputWindow.innerHTML += "acceleration: " + accel + "m/s^2<br />";
|
||||
|
||||
//for (var key in e.data.sensors.lin) {
|
||||
// outputWindow.innerHTML += key + " lin: " + e.data.sensors.lin[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.acc) {
|
||||
// outputWindow.innerHTML += key + " acc: " + e.data.sensors.acc[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.mag) {
|
||||
// outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
|
||||
//}
|
||||
//for (var key in e.data.sensors.ori) {
|
||||
// outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
|
||||
//}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadIframe("../"+window.location.search);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,280 +1,280 @@
|
||||
<html>
|
||||
<head><title>Sensor and video stream access example</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: rgb(222,242,253);
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
margin:10px;
|
||||
width:640px;
|
||||
height:320px;
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
canvas{
|
||||
width:100%;
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
<input placeholder="Enter a VDO.Ninja View URL here" id="viewlink" style="display:block;" onchange="loadIframe();"/>
|
||||
<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);">
|
||||
<label for="vert">FOA-Vertical</label>
|
||||
<input type="range" id="vert" name="vert" value="50" title="50" min="30" max="70" onchange="updateVer(this);">
|
||||
<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 />
|
||||
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>
|
||||
|
||||
<script>
|
||||
// https://www.camerafv5.com/devices/manufacturers/google/pixel_4a_sunfish_1/ ; pixel 4a specs
|
||||
var horFOA = 49.6;
|
||||
var verFOA = 63.3;
|
||||
var drawDelay = 110;
|
||||
function updateHor(hor){
|
||||
horFOA = parseInt(hor.value);
|
||||
hor.title = horFOA;
|
||||
}
|
||||
function updateVer(ver){
|
||||
verFOA = parseInt(ver.value);
|
||||
ver.title = verFOA;
|
||||
}
|
||||
function updateDelay(time){
|
||||
drawDelay = parseInt(time.value);
|
||||
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.
|
||||
|
||||
var canvas = document.getElementById("canvas");
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.imageSmoothingEnabled= false;
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
console.log(iframesrc);
|
||||
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
|
||||
document.getElementById("canvas").style.display="block";
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="./";
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
|
||||
iframeContainer.appendChild(iframe);
|
||||
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var videos = iframe.contentWindow.document.querySelectorAll("video");
|
||||
var sensors = {};
|
||||
|
||||
function drawFrame(vid){
|
||||
try {
|
||||
if (sensors.mag){ // androids may not support this.
|
||||
var angle = 1.5 * Math.PI - Math.atan2(sensors.mag.y,sensors.mag.x);
|
||||
var startPixel = (angle / ( 2 * Math.PI)) * 1920;
|
||||
var endPixel = (verFOA/360) * 1920 + startPixel;
|
||||
} else if (sensors.ori){
|
||||
var angle = sensors.ori.a;
|
||||
var frontToBack = sensors.ori.b;
|
||||
var leftToRight = sensors.ori.g;
|
||||
|
||||
var startPixel = Math.floor((angle / 360) * 1920);
|
||||
var width = Math.floor((verFOA/360) * 1920);
|
||||
var height = vid.videoHeight*(width/vid.videoWidth);
|
||||
|
||||
var h_offset = Math.floor(((frontToBack+(verFOA/2))/180 * 1080)-540);
|
||||
var w_offset = Math.floor((leftToRight+horFOA)/180 * 1920);
|
||||
}
|
||||
|
||||
setTimeout(function(a1,a2,a3,a4,a5){
|
||||
|
||||
ctx.filter = 'blur(4px)';
|
||||
ctx.drawImage(a1,a2,a3,a4,a5);
|
||||
|
||||
ctx.filter = "none";
|
||||
ctx.drawImage(a1,a2,a3,a4,a5);
|
||||
|
||||
}, drawDelay, vid, startPixel-w_offset, h_offset, width, height);
|
||||
} catch(e){console.error(e);}
|
||||
};
|
||||
|
||||
setInterval(function(){
|
||||
if (videos.length){
|
||||
if ("UUID" in sensors){
|
||||
if (videos[0].id !== "videosource_"+sensors.UUID){
|
||||
videos = iframe.contentWindow.document.querySelectorAll("video#videosource_"+sensors.UUID);
|
||||
}
|
||||
if (videos.length){
|
||||
drawFrame(videos[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},100);
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
if ("stats" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
//console.log(e.data.stats);
|
||||
|
||||
|
||||
var out = "<br />total_inbound_connections:"+e.data.stats.total_inbound_connections;
|
||||
out += "<br />total_outbound_connections:"+e.data.stats.total_outbound_connections;
|
||||
|
||||
for (var streamID in e.data.stats.inbound_stats){
|
||||
out += "<br /><br /><b>streamID:</b> "+streamID+"<br />";
|
||||
out += printValues(e.data.stats.inbound_stats[streamID]);
|
||||
}
|
||||
|
||||
outputWindow.innerHTML = out;
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("gotChat" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = e.data.gotChat.msg;
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "child-page-action: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
console.log(e.data.action);
|
||||
|
||||
if (e.data.action == "new-view-connection"){
|
||||
setTimeout(function(){
|
||||
videos = iframe.contentWindow.document.querySelectorAll("video");
|
||||
console.log(videos);
|
||||
},500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "child-page-action: streamIDs<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("loudness" in e.data){
|
||||
//console.log(e.data);
|
||||
if (document.getElementById("loudness")){
|
||||
outputWindow = document.getElementById("loudness");
|
||||
} else {
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
outputWindow.id = "loudness";
|
||||
}
|
||||
outputWindow.innerHTML = "child-page-action: loudness<br />";
|
||||
for (var key in e.data.loudness) {
|
||||
outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px black";
|
||||
|
||||
}
|
||||
|
||||
if ("sensors" in e.data){
|
||||
sensors = e.data.sensors;
|
||||
if (document.getElementById("sensors")){
|
||||
outputWindow = document.getElementById("sensors");
|
||||
} else {
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
outputWindow.id = "sensors";
|
||||
console.log(sensors);
|
||||
}
|
||||
outputWindow.innerHTML = "child-page-action: sensors<br /><br />";
|
||||
|
||||
for (var key in e.data.sensors.lin) {
|
||||
outputWindow.innerHTML += key + " linear: " + e.data.sensors.lin[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.acc) {
|
||||
outputWindow.innerHTML += key + " acceleration: " + e.data.sensors.acc[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.gyro) {
|
||||
outputWindow.innerHTML += key + " gyro: " + e.data.sensors.gyro[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.mag) {
|
||||
outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.ori) {
|
||||
outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
|
||||
}
|
||||
outputWindow.style.border="1px black";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function printValues( obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out +="<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
out +="<b>"+key+"</b>: "+obj[key]+"<br />";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Sensor and video stream access example</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: rgb(222,242,253);
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
margin:10px;
|
||||
width:640px;
|
||||
height:320px;
|
||||
}
|
||||
#viewlink {
|
||||
width:400px;
|
||||
}
|
||||
#container {
|
||||
display:block;
|
||||
padding:0px;
|
||||
}
|
||||
input{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
button{
|
||||
padding:5px;
|
||||
margin:5px;
|
||||
}
|
||||
canvas{
|
||||
width:100%;
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
<input placeholder="Enter a VDO.Ninja View URL here" id="viewlink" style="display:block;" onchange="loadIframe();"/>
|
||||
<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);">
|
||||
<label for="vert">FOA-Vertical</label>
|
||||
<input type="range" id="vert" name="vert" value="50" title="50" min="30" max="70" onchange="updateVer(this);">
|
||||
<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 />
|
||||
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>
|
||||
|
||||
<script>
|
||||
// https://www.camerafv5.com/devices/manufacturers/google/pixel_4a_sunfish_1/ ; pixel 4a specs
|
||||
var horFOA = 49.6;
|
||||
var verFOA = 63.3;
|
||||
var drawDelay = 110;
|
||||
function updateHor(hor){
|
||||
horFOA = parseInt(hor.value);
|
||||
hor.title = horFOA;
|
||||
}
|
||||
function updateVer(ver){
|
||||
verFOA = parseInt(ver.value);
|
||||
ver.title = verFOA;
|
||||
}
|
||||
function updateDelay(time){
|
||||
drawDelay = parseInt(time.value);
|
||||
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.
|
||||
|
||||
var canvas = document.getElementById("canvas");
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.imageSmoothingEnabled= false;
|
||||
var iframe = document.createElement("iframe");
|
||||
var iframeContainer = document.createElement("div");
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
console.log(iframesrc);
|
||||
document.getElementById("viewlink").parentNode.removeChild(document.getElementById("viewlink"));
|
||||
document.getElementById("canvas").style.display="block";
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="./";
|
||||
}
|
||||
|
||||
iframe.src = iframesrc;
|
||||
|
||||
iframeContainer.appendChild(iframe);
|
||||
|
||||
document.getElementById("container").appendChild(iframeContainer);
|
||||
|
||||
var videos = iframe.contentWindow.document.querySelectorAll("video");
|
||||
var sensors = {};
|
||||
|
||||
function drawFrame(vid){
|
||||
try {
|
||||
if (sensors.mag){ // androids may not support this.
|
||||
var angle = 1.5 * Math.PI - Math.atan2(sensors.mag.y,sensors.mag.x);
|
||||
var startPixel = (angle / ( 2 * Math.PI)) * 1920;
|
||||
var endPixel = (verFOA/360) * 1920 + startPixel;
|
||||
} else if (sensors.ori){
|
||||
var angle = sensors.ori.a;
|
||||
var frontToBack = sensors.ori.b;
|
||||
var leftToRight = sensors.ori.g;
|
||||
|
||||
var startPixel = Math.floor((angle / 360) * 1920);
|
||||
var width = Math.floor((verFOA/360) * 1920);
|
||||
var height = vid.videoHeight*(width/vid.videoWidth);
|
||||
|
||||
var h_offset = Math.floor(((frontToBack+(verFOA/2))/180 * 1080)-540);
|
||||
var w_offset = Math.floor((leftToRight+horFOA)/180 * 1920);
|
||||
}
|
||||
|
||||
setTimeout(function(a1,a2,a3,a4,a5){
|
||||
|
||||
ctx.filter = 'blur(4px)';
|
||||
ctx.drawImage(a1,a2,a3,a4,a5);
|
||||
|
||||
ctx.filter = "none";
|
||||
ctx.drawImage(a1,a2,a3,a4,a5);
|
||||
|
||||
}, drawDelay, vid, startPixel-w_offset, h_offset, width, height);
|
||||
} catch(e){console.error(e);}
|
||||
};
|
||||
|
||||
setInterval(function(){
|
||||
if (videos.length){
|
||||
if ("UUID" in sensors){
|
||||
if (videos[0].id !== "videosource_"+sensors.UUID){
|
||||
videos = iframe.contentWindow.document.querySelectorAll("video#videosource_"+sensors.UUID);
|
||||
}
|
||||
if (videos.length){
|
||||
drawFrame(videos[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},100);
|
||||
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
if ("stats" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
//console.log(e.data.stats);
|
||||
|
||||
|
||||
var out = "<br />total_inbound_connections:"+e.data.stats.total_inbound_connections;
|
||||
out += "<br />total_outbound_connections:"+e.data.stats.total_outbound_connections;
|
||||
|
||||
for (var streamID in e.data.stats.inbound_stats){
|
||||
out += "<br /><br /><b>streamID:</b> "+streamID+"<br />";
|
||||
out += printValues(e.data.stats.inbound_stats[streamID]);
|
||||
}
|
||||
|
||||
outputWindow.innerHTML = out;
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("gotChat" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = e.data.gotChat.msg;
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("action" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "child-page-action: "+e.data.action+"<br />";
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
console.log(e.data.action);
|
||||
|
||||
if (e.data.action == "new-view-connection"){
|
||||
setTimeout(function(){
|
||||
videos = iframe.contentWindow.document.querySelectorAll("video");
|
||||
console.log(videos);
|
||||
},500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("streamIDs" in e.data){
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.innerHTML = "child-page-action: streamIDs<br />";
|
||||
for (var key in e.data.streamIDs) {
|
||||
outputWindow.innerHTML += "streamID: " + key + ", label:"+e.data.streamIDs[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
}
|
||||
|
||||
if ("loudness" in e.data){
|
||||
//console.log(e.data);
|
||||
if (document.getElementById("loudness")){
|
||||
outputWindow = document.getElementById("loudness");
|
||||
} else {
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
outputWindow.id = "loudness";
|
||||
}
|
||||
outputWindow.innerHTML = "child-page-action: loudness<br />";
|
||||
for (var key in e.data.loudness) {
|
||||
outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n";
|
||||
}
|
||||
outputWindow.style.border="1px black";
|
||||
|
||||
}
|
||||
|
||||
if ("sensors" in e.data){
|
||||
sensors = e.data.sensors;
|
||||
if (document.getElementById("sensors")){
|
||||
outputWindow = document.getElementById("sensors");
|
||||
} else {
|
||||
var outputWindow = document.createElement("div");
|
||||
outputWindow.style.border="1px dotted black";
|
||||
iframeContainer.appendChild(outputWindow);
|
||||
outputWindow.id = "sensors";
|
||||
console.log(sensors);
|
||||
}
|
||||
outputWindow.innerHTML = "child-page-action: sensors<br /><br />";
|
||||
|
||||
for (var key in e.data.sensors.lin) {
|
||||
outputWindow.innerHTML += key + " linear: " + e.data.sensors.lin[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.acc) {
|
||||
outputWindow.innerHTML += key + " acceleration: " + e.data.sensors.acc[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.gyro) {
|
||||
outputWindow.innerHTML += key + " gyro: " + e.data.sensors.gyro[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.mag) {
|
||||
outputWindow.innerHTML += key + " magnet: " + e.data.sensors.mag[key] + "<br />";
|
||||
}
|
||||
for (var key in e.data.sensors.ori) {
|
||||
outputWindow.innerHTML += key + " orientation: " + e.data.sensors.ori[key] + "<br />";
|
||||
}
|
||||
outputWindow.style.border="1px black";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function printValues( obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out +="<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
out +="<b>"+key+"</b>: "+obj[key]+"<br />";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,185 +1,185 @@
|
||||
<html>
|
||||
<head><title>SocialStream + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#container2{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left:0;
|
||||
display:none;
|
||||
z-index:2;
|
||||
}
|
||||
#container1{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left:0;
|
||||
display:none;
|
||||
}
|
||||
iframe{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
}
|
||||
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
}
|
||||
#container1{
|
||||
}
|
||||
iframe{
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
}
|
||||
#container1{
|
||||
}
|
||||
iframe{
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container2"></div>
|
||||
<div id="container1" ></div>
|
||||
<div id="selectChatSource">
|
||||
<h1>Which social integration are you adding?</h1>
|
||||
|
||||
|
||||
</div>
|
||||
<div id="clean">
|
||||
<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 the SocialStream URL" id="social" type="text" />
|
||||
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("SocialStreamChatLink")){
|
||||
document.getElementById("social").value = getStorage("SocialStreamChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaSocialStreamURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaSocialStreamURL");
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var room2 = document.getElementById("social").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader";
|
||||
}
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
|
||||
setStorage("SocialStreamChatLink", room2);
|
||||
|
||||
setStorage("vdoNinjaSocialStreamURL", room1);
|
||||
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>SocialStream + Video</title>
|
||||
<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" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#container2{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left:0;
|
||||
display:none;
|
||||
z-index:2;
|
||||
}
|
||||
#container1{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left:0;
|
||||
display:none;
|
||||
}
|
||||
iframe{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
}
|
||||
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
}
|
||||
#container1{
|
||||
}
|
||||
iframe{
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
}
|
||||
#container1{
|
||||
}
|
||||
iframe{
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container2"></div>
|
||||
<div id="container1" ></div>
|
||||
<div id="selectChatSource">
|
||||
<h1>Which social integration are you adding?</h1>
|
||||
|
||||
|
||||
</div>
|
||||
<div id="clean">
|
||||
<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 the SocialStream URL" id="social" type="text" />
|
||||
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("SocialStreamChatLink")){
|
||||
document.getElementById("social").value = getStorage("SocialStreamChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaSocialStreamURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaSocialStreamURL");
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var room2 = document.getElementById("social").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader";
|
||||
}
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
|
||||
setStorage("SocialStreamChatLink", room2);
|
||||
|
||||
setStorage("vdoNinjaSocialStreamURL", room1);
|
||||
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,172 +1,172 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>VDON Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
padding: 0em;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
ul li {
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
margin:0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
}
|
||||
var room="";
|
||||
if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
}
|
||||
|
||||
var password="";
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var log = document.body.getElementsByTagName("ul")[0];
|
||||
var entry = document.createElement('li');
|
||||
if (type){
|
||||
type = "<i>"+type+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
|
||||
//setTimeout(function(entry){ // hide message after 60 seconds
|
||||
// entry.innerHTML="";
|
||||
// entry.remove();
|
||||
// },60000,entry);
|
||||
|
||||
log.appendChild(entry);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
<ul></ul>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>VDON Chat Overlay</title>
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
src: url('fonts/Cousine-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0 10px;
|
||||
height:100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
overflow:hidden;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
color: white;
|
||||
font-family: Cousine, monospace;
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1em;
|
||||
letter-spacing: 0.0em;
|
||||
text-transform: uppercase;
|
||||
padding: 0em;
|
||||
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
ul li {
|
||||
background-color: black;
|
||||
padding: 8px 8px 0px 8px;
|
||||
margin:0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color:white;
|
||||
font-size:1.2em;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
function (searchString) {
|
||||
var self = this;
|
||||
self.searchString = searchString;
|
||||
self.get = function (name) {
|
||||
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||
self.searchString
|
||||
);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
})(window);
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
function loadIframe() {
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
|
||||
var view= "";
|
||||
if (urlParams.has("view")) {
|
||||
view = "&view="+(urlParams.get("view") || "");
|
||||
}
|
||||
var room="";
|
||||
if (urlParams.has("room")) {
|
||||
room = "&room="+urlParams.get("room");
|
||||
}
|
||||
|
||||
var password="";
|
||||
if (urlParams.has("password")) {
|
||||
password = "&password="+urlParams.get("password");
|
||||
}
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
var srcString = "./?novideo&noaudio&label=chatOverlay&scene"+room+view+password;
|
||||
|
||||
iframe.src = srcString;
|
||||
iframe.style.width="0";
|
||||
iframe.style.height="0";
|
||||
iframe.style.border="0";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
|
||||
console.log(e);
|
||||
if ("gotChat" in e.data){
|
||||
logData(e.data.gotChat.label,e.data.gotChat.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printValues(obj) {
|
||||
var out = "";
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === "object") {
|
||||
out += "<br />";
|
||||
out += printValues(obj[key]);
|
||||
} else {
|
||||
if (key.startsWith("_")) {
|
||||
} else {
|
||||
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function logData(type, data) {
|
||||
var log = document.body.getElementsByTagName("ul")[0];
|
||||
var entry = document.createElement('li');
|
||||
if (type){
|
||||
type = "<i>"+type+"</i>";
|
||||
}
|
||||
entry.innerHTML = type + data;
|
||||
|
||||
//setTimeout(function(entry){ // hide message after 60 seconds
|
||||
// entry.innerHTML="";
|
||||
// entry.remove();
|
||||
// },60000,entry);
|
||||
|
||||
log.appendChild(entry);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="loadIframe();">
|
||||
<ul></ul>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,289 +1,289 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
html {
|
||||
border:0;
|
||||
margin:0;
|
||||
outline:0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=), none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
body {
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
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+ */
|
||||
font-size: 2em;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
border:0;
|
||||
outline:0;
|
||||
}
|
||||
|
||||
button.glyphicon-button:focus,
|
||||
button.glyphicon-button:active:focus,
|
||||
button.glyphicon-button.active:focus,
|
||||
button.glyphicon-button.focus,
|
||||
button.glyphicon-button:active.focus,
|
||||
button.glyphicon-button.active.focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 100px);
|
||||
transform: rotate(0deg);
|
||||
transform-origin: 0 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.gobutton {
|
||||
font-size:min(30px, 2vw);
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #6aab23;
|
||||
display: flex;
|
||||
border-radius: 0px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #5ca70b, 0 2px 0px #6aab23;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
margin: min(1vw, 10px) 0 ;
|
||||
}
|
||||
.details{
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #555;
|
||||
display: flex;
|
||||
border-radius: 0px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #444, 0 2px 0px #555;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
}
|
||||
#header{
|
||||
width:100%;
|
||||
background-color: #101520;
|
||||
}
|
||||
.changeText {
|
||||
font-size: max(1vw, 10px)
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
padding: min(1vw, 10px);
|
||||
font-weight: bold;
|
||||
background: white;
|
||||
border: 4px solid white;
|
||||
box-shadow: 0px 30px 40px -32px #6aab23, 0 2px 0px #6aab23;
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
transition: all 0.2s linear;
|
||||
box-sizing: border-box;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin: min(1vw, 10px) 0;
|
||||
}
|
||||
|
||||
.changeText:focus {
|
||||
outline: none;
|
||||
}
|
||||
select.changetext{
|
||||
padding: .1vw;
|
||||
}
|
||||
|
||||
.container{
|
||||
width:100%;
|
||||
top:0;
|
||||
position:absolute;
|
||||
left:0;
|
||||
margin: auto auto;
|
||||
height: 70px;
|
||||
}
|
||||
label {
|
||||
font: white;
|
||||
font-size: 1vw;
|
||||
color: white;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
-webkit-appearance:none;
|
||||
width:30px;
|
||||
height:30px;
|
||||
background:white;
|
||||
border-radius:5px;
|
||||
border:2px solid #555;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type='checkbox']:checked {
|
||||
background: #1A1;
|
||||
}
|
||||
#audioOutput, #lastUrls {
|
||||
font-size: calc(16px + 0.3vw);
|
||||
width: 730px;
|
||||
height: 100%;
|
||||
flex: 20;
|
||||
border-radius: 10px;
|
||||
padding: min(1vw, 10px);
|
||||
background: #eaeaea;
|
||||
cursor:pointer;
|
||||
}
|
||||
label[for="audioOutput"] {
|
||||
font-size: min(30px, 2vw);
|
||||
color: #FE53BB;
|
||||
text-shadow: 0px 0px 30px #fe53bb;
|
||||
padding-right: 10px;
|
||||
}
|
||||
label[for="changeText"] {
|
||||
font-size: min(30px, 2.5vw);
|
||||
color: #00F6FF;
|
||||
text-shadow: 0px 0px 30px #00f6ff;
|
||||
padding-right: 10px;
|
||||
margin:auto auto;
|
||||
}
|
||||
|
||||
label[for="lastUrls"] {
|
||||
font-size: min(min(30px, 2vw), 2vw);
|
||||
color: #1a1;
|
||||
text-shadow: 0px 0px 30px #1a1;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div#audioOutputContainer, #history {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
margin: 4em;
|
||||
}
|
||||
|
||||
#messageDiv {
|
||||
font-size: .7em;
|
||||
color: #DDD;
|
||||
transition: all 0.5s linear;
|
||||
font-style: italic;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.urlInput {
|
||||
padding: 0 0 4vh 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
label[for="audioOutput"], label[for="lastUrls"] {
|
||||
font-size: min(30px, 2vw);
|
||||
}
|
||||
|
||||
#warning4mac, #electronVersion {
|
||||
background: #8500f7;
|
||||
box-shadow: 0px 0px 50px 10px #8500f7ab, inset 0px 0px 10px 2px #8d08ffba;
|
||||
border: 2px solid #8500f7;
|
||||
border-radius: 10px;
|
||||
width: 90%;
|
||||
padding:min(1vw, 10px);
|
||||
margin:0 auto;
|
||||
color:white;
|
||||
font-size: 1.40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#warning4mac a, #electronVersion a {
|
||||
color:white;
|
||||
}
|
||||
|
||||
ul#lastUrls {
|
||||
list-style: none;
|
||||
background: #101520;
|
||||
color: white;
|
||||
padding: min(1vw, 10px);
|
||||
}
|
||||
|
||||
ul#lastUrls li {
|
||||
padding: 5px 0px;
|
||||
}
|
||||
ul#lastUrls li:nth-child(even) {
|
||||
background-color: #182031;
|
||||
}
|
||||
|
||||
.inputCombo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#version{
|
||||
margin: 0 auto;
|
||||
font-size: 30%;
|
||||
display: inline-block;
|
||||
color: #000A;
|
||||
}
|
||||
h3 {
|
||||
color: #b0e3ff;
|
||||
}
|
||||
.hidden{
|
||||
display:none;
|
||||
opacity:0;
|
||||
visibility:none;
|
||||
width:0;
|
||||
height:0
|
||||
}
|
||||
.hidebutton{
|
||||
font-size:min(30px, 2vw);
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #ab236a;
|
||||
display: flex;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #a70b5c, 0 2px 0px #ab236a;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
margin: min(1vw, 5px) 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="container">
|
||||
|
||||
|
||||
</div>
|
||||
<script>
|
||||
location.href = './teleprompter.html';
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
html {
|
||||
border:0;
|
||||
margin:0;
|
||||
outline:0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=), none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
body {
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
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+ */
|
||||
font-size: 2em;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
border:0;
|
||||
outline:0;
|
||||
}
|
||||
|
||||
button.glyphicon-button:focus,
|
||||
button.glyphicon-button:active:focus,
|
||||
button.glyphicon-button.active:focus,
|
||||
button.glyphicon-button.focus,
|
||||
button.glyphicon-button:active.focus,
|
||||
button.glyphicon-button.active.focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 100px);
|
||||
transform: rotate(0deg);
|
||||
transform-origin: 0 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.gobutton {
|
||||
font-size:min(30px, 2vw);
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #6aab23;
|
||||
display: flex;
|
||||
border-radius: 0px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #5ca70b, 0 2px 0px #6aab23;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
margin: min(1vw, 10px) 0 ;
|
||||
}
|
||||
.details{
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #555;
|
||||
display: flex;
|
||||
border-radius: 0px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #444, 0 2px 0px #555;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
}
|
||||
#header{
|
||||
width:100%;
|
||||
background-color: #101520;
|
||||
}
|
||||
.changeText {
|
||||
font-size: max(1vw, 10px)
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
padding: min(1vw, 10px);
|
||||
font-weight: bold;
|
||||
background: white;
|
||||
border: 4px solid white;
|
||||
box-shadow: 0px 30px 40px -32px #6aab23, 0 2px 0px #6aab23;
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
transition: all 0.2s linear;
|
||||
box-sizing: border-box;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin: min(1vw, 10px) 0;
|
||||
}
|
||||
|
||||
.changeText:focus {
|
||||
outline: none;
|
||||
}
|
||||
select.changetext{
|
||||
padding: .1vw;
|
||||
}
|
||||
|
||||
.container{
|
||||
width:100%;
|
||||
top:0;
|
||||
position:absolute;
|
||||
left:0;
|
||||
margin: auto auto;
|
||||
height: 70px;
|
||||
}
|
||||
label {
|
||||
font: white;
|
||||
font-size: 1vw;
|
||||
color: white;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
-webkit-appearance:none;
|
||||
width:30px;
|
||||
height:30px;
|
||||
background:white;
|
||||
border-radius:5px;
|
||||
border:2px solid #555;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type='checkbox']:checked {
|
||||
background: #1A1;
|
||||
}
|
||||
#audioOutput, #lastUrls {
|
||||
font-size: calc(16px + 0.3vw);
|
||||
width: 730px;
|
||||
height: 100%;
|
||||
flex: 20;
|
||||
border-radius: 10px;
|
||||
padding: min(1vw, 10px);
|
||||
background: #eaeaea;
|
||||
cursor:pointer;
|
||||
}
|
||||
label[for="audioOutput"] {
|
||||
font-size: min(30px, 2vw);
|
||||
color: #FE53BB;
|
||||
text-shadow: 0px 0px 30px #fe53bb;
|
||||
padding-right: 10px;
|
||||
}
|
||||
label[for="changeText"] {
|
||||
font-size: min(30px, 2.5vw);
|
||||
color: #00F6FF;
|
||||
text-shadow: 0px 0px 30px #00f6ff;
|
||||
padding-right: 10px;
|
||||
margin:auto auto;
|
||||
}
|
||||
|
||||
label[for="lastUrls"] {
|
||||
font-size: min(min(30px, 2vw), 2vw);
|
||||
color: #1a1;
|
||||
text-shadow: 0px 0px 30px #1a1;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div#audioOutputContainer, #history {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
margin: 4em;
|
||||
}
|
||||
|
||||
#messageDiv {
|
||||
font-size: .7em;
|
||||
color: #DDD;
|
||||
transition: all 0.5s linear;
|
||||
font-style: italic;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.urlInput {
|
||||
padding: 0 0 4vh 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
label[for="audioOutput"], label[for="lastUrls"] {
|
||||
font-size: min(30px, 2vw);
|
||||
}
|
||||
|
||||
#warning4mac, #electronVersion {
|
||||
background: #8500f7;
|
||||
box-shadow: 0px 0px 50px 10px #8500f7ab, inset 0px 0px 10px 2px #8d08ffba;
|
||||
border: 2px solid #8500f7;
|
||||
border-radius: 10px;
|
||||
width: 90%;
|
||||
padding:min(1vw, 10px);
|
||||
margin:0 auto;
|
||||
color:white;
|
||||
font-size: 1.40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#warning4mac a, #electronVersion a {
|
||||
color:white;
|
||||
}
|
||||
|
||||
ul#lastUrls {
|
||||
list-style: none;
|
||||
background: #101520;
|
||||
color: white;
|
||||
padding: min(1vw, 10px);
|
||||
}
|
||||
|
||||
ul#lastUrls li {
|
||||
padding: 5px 0px;
|
||||
}
|
||||
ul#lastUrls li:nth-child(even) {
|
||||
background-color: #182031;
|
||||
}
|
||||
|
||||
.inputCombo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#version{
|
||||
margin: 0 auto;
|
||||
font-size: 30%;
|
||||
display: inline-block;
|
||||
color: #000A;
|
||||
}
|
||||
h3 {
|
||||
color: #b0e3ff;
|
||||
}
|
||||
.hidden{
|
||||
display:none;
|
||||
opacity:0;
|
||||
visibility:none;
|
||||
width:0;
|
||||
height:0
|
||||
}
|
||||
.hidebutton{
|
||||
font-size:min(30px, 2vw);
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: #ab236a;
|
||||
display: flex;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 15px -10px #a70b5c, 0 2px 0px #ab236a;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 min(1vw, 10px);
|
||||
margin: min(1vw, 5px) 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="container">
|
||||
|
||||
|
||||
</div>
|
||||
<script>
|
||||
location.href = 'teleprompter.html';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,22 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color:#0000;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
img {
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src='../media/vdoNinja_logo_full.png'>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color:#0000;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
img {
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src='../media/vdoNinja_logo_full.png'>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,358 +1,358 @@
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#000;
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
color:white;
|
||||
overscroll-behavior: contain;
|
||||
overflow: hidden;
|
||||
display:block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px 2px;
|
||||
width:calc(100vw - 60px);
|
||||
font-size: min(4vw, 20px);
|
||||
margin:10px 0 30px 0;
|
||||
z-index: 1000;
|
||||
color:black;
|
||||
display:block;
|
||||
|
||||
}
|
||||
#clean{
|
||||
max-width:100%;
|
||||
width: 90vw;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px 3px 30px 3px;
|
||||
color:white;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: min(10vw, 28px);
|
||||
color:black;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
#controlbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #0000;
|
||||
height: max(7vh, 50px);
|
||||
width: 100%;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
}
|
||||
#container2{
|
||||
background-color:#000;
|
||||
}
|
||||
#container1{
|
||||
transition: all ease 0.4ms;
|
||||
}
|
||||
#controlbar button{
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
color: white;
|
||||
box-shadow: inset 0 0 20px 7px #FFF7;
|
||||
font-size: 1.0em;
|
||||
border-radius: 19px;
|
||||
max-width: 20%;
|
||||
margin: 0;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.pressed {
|
||||
background-color: #A00!important;
|
||||
}
|
||||
|
||||
.loading {
|
||||
width: 100%!important; height:100%!important; position: absolute; top: 0; right:unset;left:0;opacity:0%; animation: fadeIn 3s;
|
||||
}
|
||||
.fullwindow {
|
||||
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 {
|
||||
0% { opacity: 0; }
|
||||
10% { opacity: 0; }
|
||||
50% { opacity: 0.2; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
width:100%;height:100%;display:none;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 50vh; display:none; top: 0; right: 0%; position: absolute;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
width:60vw;height:100%;display:none;
|
||||
z-index:5;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw; height:100%; max-height: calc(100vh - 100px); display:none; position: fixed; top: 0; right: -10vw;
|
||||
}
|
||||
|
||||
}
|
||||
.hide{
|
||||
width: 1px!important;
|
||||
height: 1px!important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container2" ></div>
|
||||
<div id="container1" class="loading"></div>
|
||||
<div id="controlbar" >
|
||||
<button id="hidepreview">Hide Preview</button>
|
||||
<button id="mutemic">Mute Mic</button>
|
||||
<button id="mutevideo">Mute Video</button>
|
||||
<button id="togglesettings">Settings</button>
|
||||
<button id="hangup">Hangup</button>
|
||||
</div>
|
||||
<div id="clean">
|
||||
<h1>Use VDO.Ninja and Twitch chat at the same time</h1>
|
||||
VDO.Ninja Stream ID or URL:
|
||||
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
|
||||
Twitch Username or URL:
|
||||
<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="clearInput()" style="background-color: #f4cccc;margin:10px 0px 10px 10vh;padding:10px;">CLEAR</button>
|
||||
<br /><br /><br />
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("twitchChatLink")){
|
||||
document.getElementById("twitch").value = getStorage("twitchChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaTwitchURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaTwitchURL");
|
||||
}
|
||||
function clearInput(){
|
||||
var confirmit = confirm("Are you sure you want to clear the input fields and local storage?");
|
||||
if (confirmit){
|
||||
removeStorage("twitchChatLink");
|
||||
removeStorage("vdoNinjaTwitchURL");
|
||||
document.getElementById("viewlink").value = "";
|
||||
document.getElementById("twitch").value = "";
|
||||
}
|
||||
}
|
||||
|
||||
var iframe = null;
|
||||
function sendSelfCommand(action, value=null){
|
||||
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
|
||||
}
|
||||
|
||||
var injectCSS = `
|
||||
#controlButtons{
|
||||
display:none!important;
|
||||
}
|
||||
`;
|
||||
|
||||
injectCSS = encodeURIComponent(btoa(injectCSS));
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var twitch = document.getElementById("twitch").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
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}`;
|
||||
|
||||
iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
|
||||
document.getElementById("container1").appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.warn(e.data);
|
||||
|
||||
if ("action" in e.data){
|
||||
if (e.data.action === "seeding-started"){
|
||||
document.getElementById("controlbar").style.display="inline-flex";
|
||||
document.getElementById("container1").classList.remove("loading");
|
||||
}
|
||||
|
||||
if (e.data.action === "settings-menu-state"){
|
||||
if (e.data.value==true){
|
||||
togglesettings.dataset.value = "true";
|
||||
togglesettings.classList.add("pressed");
|
||||
document.getElementById("container1").classList.add("fullwindow");
|
||||
} else {
|
||||
togglesettings.dataset.value = "false";
|
||||
togglesettings.classList.remove("pressed");
|
||||
document.getElementById("container1").classList.remove("fullwindow");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setStorage("twitchChatLink", room2);
|
||||
|
||||
setStorage("vdoNinjaTwitchURL", room1);
|
||||
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe2 = document.createElement("iframe");
|
||||
iframe2.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe2.src = room2;
|
||||
document.getElementById("container2").appendChild(iframe2);
|
||||
},1000);
|
||||
|
||||
}
|
||||
|
||||
var hangup = document.getElementById("hangup");
|
||||
hangup.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"hangup":true}, '*');
|
||||
}
|
||||
|
||||
var togglesettings = document.getElementById("togglesettings");
|
||||
togglesettings.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"toggleSettings":"toggle"}, '*');
|
||||
}
|
||||
|
||||
var mutemic = document.getElementById("mutemic");
|
||||
mutemic.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Un-Mute Mic";
|
||||
sendSelfCommand("mic",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Mute Mic";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("mic",true);
|
||||
}
|
||||
}
|
||||
|
||||
var mutevideo = document.getElementById("mutevideo");
|
||||
mutevideo.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Un-Mute camera";
|
||||
sendSelfCommand("camera",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Mute Camera";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("camera",true);
|
||||
}
|
||||
}
|
||||
|
||||
var hidepreview = document.getElementById("hidepreview");
|
||||
hidepreview.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Show Preview";
|
||||
document.getElementById("container1").classList.add("hide");
|
||||
document.getElementById("container2").classList.add("fullwindow");
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Hide Preview";
|
||||
this.dataset.value = "true";
|
||||
document.getElementById("container1").classList.remove("hide");
|
||||
document.getElementById("container2").classList.remove("fullwindow");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#000;
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
color:white;
|
||||
overscroll-behavior: contain;
|
||||
overflow: hidden;
|
||||
display:block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px 2px;
|
||||
width:calc(100vw - 60px);
|
||||
font-size: min(4vw, 20px);
|
||||
margin:10px 0 30px 0;
|
||||
z-index: 1000;
|
||||
color:black;
|
||||
display:block;
|
||||
|
||||
}
|
||||
#clean{
|
||||
max-width:100%;
|
||||
width: 90vw;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
h1{
|
||||
color: white;
|
||||
font-family: verdana;
|
||||
margin: 10px 3px 30px 3px;
|
||||
color:white;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: min(10vw, 28px);
|
||||
color:black;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
#controlbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #0000;
|
||||
height: max(7vh, 50px);
|
||||
width: 100%;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
}
|
||||
#container2{
|
||||
background-color:#000;
|
||||
}
|
||||
#container1{
|
||||
transition: all ease 0.4ms;
|
||||
}
|
||||
#controlbar button{
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
color: white;
|
||||
box-shadow: inset 0 0 20px 7px #FFF7;
|
||||
font-size: 1.0em;
|
||||
border-radius: 19px;
|
||||
max-width: 20%;
|
||||
margin: 0;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.pressed {
|
||||
background-color: #A00!important;
|
||||
}
|
||||
|
||||
.loading {
|
||||
width: 100%!important; height:100%!important; position: absolute; top: 0; right:unset;left:0;opacity:0%; animation: fadeIn 3s;
|
||||
}
|
||||
.fullwindow {
|
||||
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 {
|
||||
0% { opacity: 0; }
|
||||
10% { opacity: 0; }
|
||||
50% { opacity: 0.2; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
width:100%;height:100%;display:none;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 50vh; display:none; top: 0; right: 0%; position: absolute;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
width:60vw;height:100%;display:none;
|
||||
z-index:5;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw; height:100%; max-height: calc(100vh - 100px); display:none; position: fixed; top: 0; right: -10vw;
|
||||
}
|
||||
|
||||
}
|
||||
.hide{
|
||||
width: 1px!important;
|
||||
height: 1px!important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container2" ></div>
|
||||
<div id="container1" class="loading"></div>
|
||||
<div id="controlbar" >
|
||||
<button id="hidepreview">Hide Preview</button>
|
||||
<button id="mutemic">Mute Mic</button>
|
||||
<button id="mutevideo">Mute Video</button>
|
||||
<button id="togglesettings">Settings</button>
|
||||
<button id="hangup">Hangup</button>
|
||||
</div>
|
||||
<div id="clean">
|
||||
<h1>Use VDO.Ninja and Twitch chat at the same time</h1>
|
||||
VDO.Ninja Stream ID or URL:
|
||||
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
|
||||
Twitch Username or URL:
|
||||
<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="clearInput()" style="background-color: #f4cccc;margin:10px 0px 10px 10vh;padding:10px;">CLEAR</button>
|
||||
<br /><br /><br />
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function removeStorage(cname){
|
||||
localStorage.removeItem(cname);
|
||||
}
|
||||
|
||||
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
|
||||
var now = new Date();
|
||||
var item = {
|
||||
value: cvalue,
|
||||
expiry: now.getTime() + (hours * 60 * 60 * 1000),
|
||||
};
|
||||
try{
|
||||
localStorage.setItem(cname, JSON.stringify(item));
|
||||
}catch(e){errorlog(e);}
|
||||
}
|
||||
|
||||
function getStorage(cname) {
|
||||
try {
|
||||
var itemStr = localStorage.getItem(cname);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
return;
|
||||
}
|
||||
if (!itemStr) {
|
||||
return "";
|
||||
}
|
||||
var item = JSON.parse(itemStr);
|
||||
var now = new Date();
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(cname);
|
||||
return "";
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
if (getStorage("twitchChatLink")){
|
||||
document.getElementById("twitch").value = getStorage("twitchChatLink");
|
||||
}
|
||||
if (getStorage("vdoNinjaTwitchURL")){
|
||||
document.getElementById("viewlink").value = getStorage("vdoNinjaTwitchURL");
|
||||
}
|
||||
function clearInput(){
|
||||
var confirmit = confirm("Are you sure you want to clear the input fields and local storage?");
|
||||
if (confirmit){
|
||||
removeStorage("twitchChatLink");
|
||||
removeStorage("vdoNinjaTwitchURL");
|
||||
document.getElementById("viewlink").value = "";
|
||||
document.getElementById("twitch").value = "";
|
||||
}
|
||||
}
|
||||
|
||||
var iframe = null;
|
||||
function sendSelfCommand(action, value=null){
|
||||
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
|
||||
}
|
||||
|
||||
var injectCSS = `
|
||||
#controlButtons{
|
||||
display:none!important;
|
||||
}
|
||||
`;
|
||||
|
||||
injectCSS = encodeURIComponent(btoa(injectCSS));
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var roomname = document.getElementById("viewlink").value;
|
||||
var twitch = document.getElementById("twitch").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
if (roomname.startsWith("https://")){
|
||||
var room1 = roomname;
|
||||
} else {
|
||||
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}`;
|
||||
|
||||
iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
|
||||
document.getElementById("container1").appendChild(iframe);
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
|
||||
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.warn(e.data);
|
||||
|
||||
if ("action" in e.data){
|
||||
if (e.data.action === "seeding-started"){
|
||||
document.getElementById("controlbar").style.display="inline-flex";
|
||||
document.getElementById("container1").classList.remove("loading");
|
||||
}
|
||||
|
||||
if (e.data.action === "settings-menu-state"){
|
||||
if (e.data.value==true){
|
||||
togglesettings.dataset.value = "true";
|
||||
togglesettings.classList.add("pressed");
|
||||
document.getElementById("container1").classList.add("fullwindow");
|
||||
} else {
|
||||
togglesettings.dataset.value = "false";
|
||||
togglesettings.classList.remove("pressed");
|
||||
document.getElementById("container1").classList.remove("fullwindow");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setStorage("twitchChatLink", room2);
|
||||
|
||||
setStorage("vdoNinjaTwitchURL", room1);
|
||||
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe2 = document.createElement("iframe");
|
||||
iframe2.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe2.src = room2;
|
||||
document.getElementById("container2").appendChild(iframe2);
|
||||
},1000);
|
||||
|
||||
}
|
||||
|
||||
var hangup = document.getElementById("hangup");
|
||||
hangup.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"hangup":true}, '*');
|
||||
}
|
||||
|
||||
var togglesettings = document.getElementById("togglesettings");
|
||||
togglesettings.onclick = function(){
|
||||
iframe.contentWindow.postMessage({"toggleSettings":"toggle"}, '*');
|
||||
}
|
||||
|
||||
var mutemic = document.getElementById("mutemic");
|
||||
mutemic.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Un-Mute Mic";
|
||||
sendSelfCommand("mic",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Mute Mic";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("mic",true);
|
||||
}
|
||||
}
|
||||
|
||||
var mutevideo = document.getElementById("mutevideo");
|
||||
mutevideo.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Un-Mute camera";
|
||||
sendSelfCommand("camera",false);
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Mute Camera";
|
||||
this.dataset.value = "true";
|
||||
sendSelfCommand("camera",true);
|
||||
}
|
||||
}
|
||||
|
||||
var hidepreview = document.getElementById("hidepreview");
|
||||
hidepreview.onclick = function(){
|
||||
if (this.dataset.value!=="false"){
|
||||
this.dataset.value = "false";
|
||||
this.classList.add("pressed");
|
||||
this.innerText = "Show Preview";
|
||||
document.getElementById("container1").classList.add("hide");
|
||||
document.getElementById("container2").classList.add("fullwindow");
|
||||
} else {
|
||||
this.classList.remove("pressed");
|
||||
this.innerText = "Hide Preview";
|
||||
this.dataset.value = "true";
|
||||
document.getElementById("container1").classList.remove("hide");
|
||||
document.getElementById("container2").classList.remove("fullwindow");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,121 +1,121 @@
|
||||
<html>
|
||||
<head><title>Video with sensor overlayed data</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
|
||||
}
|
||||
|
||||
#overlay{
|
||||
top: 0;
|
||||
border: 0;
|
||||
margin: auto;
|
||||
padding: 5% 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
z-index: 10;
|
||||
color: white;
|
||||
font-size: 10vh;
|
||||
width: 100%;
|
||||
background-color: #000000a3;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
font-family: monospace;
|
||||
text-shadow: 2px 2px black;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="main">
|
||||
<div id="overlay" class="hidden"></div>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
|
||||
function loadIframe(url=false){
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="../";
|
||||
}
|
||||
|
||||
var params = window.location.search || "";
|
||||
|
||||
if (params.startsWith("?")){
|
||||
params = params.slice(1);
|
||||
iframesrc = iframesrc + "&" + params
|
||||
} else {
|
||||
iframesrc = iframesrc + params
|
||||
}
|
||||
|
||||
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.src = iframesrc;
|
||||
|
||||
document.getElementById("container").appendChild(iframe);
|
||||
var outputWindow = document.getElementById("overlay");
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
var waiting = null;
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.warn(e.data);
|
||||
if ("action" in e.data){
|
||||
|
||||
if (e.data.action == "joining-room"){
|
||||
outputWindow.innerHTML = "JOINING ROOM";
|
||||
waiting = setTimeout(function(){
|
||||
outputWindow.innerHTML = "Waiting for the director to join";
|
||||
outputWindow.classList.remove("hidden");
|
||||
},1000);
|
||||
} else if (e.data.action == "director-connected"){
|
||||
clearTimeout(waiting);
|
||||
outputWindow.innerHTML = "";
|
||||
outputWindow.classList.add("hidden");
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadIframe("../"+window.location.search);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<head><title>Video with sensor overlayed data</title>
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #0000;
|
||||
}
|
||||
iframe {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
|
||||
}
|
||||
|
||||
#overlay{
|
||||
top: 0;
|
||||
border: 0;
|
||||
margin: auto;
|
||||
padding: 5% 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
z-index: 10;
|
||||
color: white;
|
||||
font-size: 10vh;
|
||||
width: 100%;
|
||||
background-color: #000000a3;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
font-family: monospace;
|
||||
text-shadow: 2px 2px black;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="main">
|
||||
<div id="overlay" class="hidden"></div>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
|
||||
function loadIframe(url=false){
|
||||
|
||||
if (url){
|
||||
var iframesrc = url;
|
||||
} else {
|
||||
var iframesrc = document.getElementById("viewlink").value;
|
||||
}
|
||||
|
||||
if (iframesrc==""){
|
||||
iframesrc="../";
|
||||
}
|
||||
|
||||
var params = window.location.search || "";
|
||||
|
||||
if (params.startsWith("?")){
|
||||
params = params.slice(1);
|
||||
iframesrc = iframesrc + "&" + params
|
||||
} else {
|
||||
iframesrc = iframesrc + params
|
||||
}
|
||||
|
||||
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.src = iframesrc;
|
||||
|
||||
document.getElementById("container").appendChild(iframe);
|
||||
var outputWindow = document.getElementById("overlay");
|
||||
|
||||
//////////// LISTEN FOR EVENTS
|
||||
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
var waiting = null;
|
||||
|
||||
eventer(messageEvent, function (e) {
|
||||
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
|
||||
console.warn(e.data);
|
||||
if ("action" in e.data){
|
||||
|
||||
if (e.data.action == "joining-room"){
|
||||
outputWindow.innerHTML = "JOINING ROOM";
|
||||
waiting = setTimeout(function(){
|
||||
outputWindow.innerHTML = "Waiting for the director to join";
|
||||
outputWindow.classList.remove("hidden");
|
||||
},1000);
|
||||
} else if (e.data.action == "director-connected"){
|
||||
clearTimeout(waiting);
|
||||
outputWindow.innerHTML = "";
|
||||
outputWindow.classList.add("hidden");
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadIframe("../"+window.location.search);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,133 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>WebHID Demo</title>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: black;
|
||||
border: none;
|
||||
color: #00FF00;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 4px 2px;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#connected {
|
||||
font-size: 24px;
|
||||
max-height:700px;
|
||||
overflow-y:scroll
|
||||
}
|
||||
#disconnectButton {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>STREAMDECK DEMO</h1>
|
||||
<img src="../media/streamdeck.png" /><br />
|
||||
<input class="button" type="button" id="connectButton" value="Connect" />
|
||||
<input class="button" type="button" id="disconnectButton" style="display:none" value="Disconnect" />
|
||||
<div id="connected" style>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const connectButton = document.getElementById("connectButton");
|
||||
const disconnectButton = document.getElementById("disconnectButton");
|
||||
const connect = document.getElementById("connect");
|
||||
const deviceButtonPressed = document.getElementById("deviceButtonPressed");
|
||||
var lastState = false;
|
||||
//productId: 0x0060,
|
||||
//class: models_1.StreamDeckOriginal,
|
||||
|
||||
//productId: 0x0063,
|
||||
//class: models_1.StreamDeckMini,
|
||||
|
||||
//productId: 0x006c,
|
||||
//class: models_1.StreamDeckXL,
|
||||
|
||||
//productId: 0x006d,
|
||||
//class: models_1.StreamDeckOriginalV2,
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
let devices = await navigator.hid.getDevices();
|
||||
devices.forEach(device => {
|
||||
console.log(`HID: ${device.productName}`);
|
||||
});
|
||||
});
|
||||
|
||||
function handleInputReport(e) {
|
||||
console.log(e.device.productName + ": got input report " + e.reportId);
|
||||
console.log(new Uint8Array(e.data.buffer));
|
||||
var data = new Uint8Array(e.data.buffer);
|
||||
if (lastState!==false){
|
||||
for (var i=0;i<data.length;i++){
|
||||
|
||||
if (parseInt(data[i])!=data[i]){continue;}
|
||||
if (lastState[i]!==data[i]){
|
||||
if (data[i]){
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
|
||||
} else {
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Released"+document.getElementById("connected").innerHTML;
|
||||
}
|
||||
} else {
|
||||
if (data[i]){
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastState = data;
|
||||
}
|
||||
|
||||
let device;
|
||||
|
||||
connectButton.onclick = async () => {
|
||||
navigator.hid.requestDevice({
|
||||
filters: [{ vendorId: 0x0fd9}] // elgato?
|
||||
}).then((devices)=>{
|
||||
console.log(devices);
|
||||
|
||||
device = devices[0];
|
||||
|
||||
console.log(`HID connected: ${device.productName}`);
|
||||
document.getElementById("connected").innerHTML = "<br />Connected" +document.getElementById("connected").innerHTML;
|
||||
document.getElementById("disconnectButton").style.display = "inline-block";
|
||||
device.addEventListener("inputreport", handleInputReport);
|
||||
|
||||
//device.sendReport(outputReportId, outputReport).then(() => {
|
||||
// console.log("Sent output report " + outputReportId);
|
||||
//});
|
||||
|
||||
if (!device.opened){
|
||||
device.open().then(()=>{
|
||||
window.addEventListener("onbeforeunload", async () => {
|
||||
await device.close();
|
||||
});
|
||||
}).catch(function(err){console.error(err);});
|
||||
}
|
||||
}).catch(function(err){console.error(err);});
|
||||
|
||||
};
|
||||
|
||||
disconnectButton.onclick = async () => {
|
||||
await device.close();
|
||||
|
||||
//connected.style.display = "none";
|
||||
//connectButton.style.display = "initial";
|
||||
disconnectButton.style.display = "none";
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>WebHID Demo</title>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: black;
|
||||
border: none;
|
||||
color: #00FF00;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 4px 2px;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#connected {
|
||||
font-size: 24px;
|
||||
max-height:700px;
|
||||
overflow-y:scroll
|
||||
}
|
||||
#disconnectButton {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>STREAMDECK DEMO</h1>
|
||||
<img src="../media/streamdeck.png" /><br />
|
||||
<input class="button" type="button" id="connectButton" value="Connect" />
|
||||
<input class="button" type="button" id="disconnectButton" style="display:none" value="Disconnect" />
|
||||
<div id="connected" style>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const connectButton = document.getElementById("connectButton");
|
||||
const disconnectButton = document.getElementById("disconnectButton");
|
||||
const connect = document.getElementById("connect");
|
||||
const deviceButtonPressed = document.getElementById("deviceButtonPressed");
|
||||
var lastState = false;
|
||||
//productId: 0x0060,
|
||||
//class: models_1.StreamDeckOriginal,
|
||||
|
||||
//productId: 0x0063,
|
||||
//class: models_1.StreamDeckMini,
|
||||
|
||||
//productId: 0x006c,
|
||||
//class: models_1.StreamDeckXL,
|
||||
|
||||
//productId: 0x006d,
|
||||
//class: models_1.StreamDeckOriginalV2,
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
let devices = await navigator.hid.getDevices();
|
||||
devices.forEach(device => {
|
||||
console.log(`HID: ${device.productName}`);
|
||||
});
|
||||
});
|
||||
|
||||
function handleInputReport(e) {
|
||||
console.log(e.device.productName + ": got input report " + e.reportId);
|
||||
console.log(new Uint8Array(e.data.buffer));
|
||||
var data = new Uint8Array(e.data.buffer);
|
||||
if (lastState!==false){
|
||||
for (var i=0;i<data.length;i++){
|
||||
|
||||
if (parseInt(data[i])!=data[i]){continue;}
|
||||
if (lastState[i]!==data[i]){
|
||||
if (data[i]){
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
|
||||
} else {
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Released"+document.getElementById("connected").innerHTML;
|
||||
}
|
||||
} else {
|
||||
if (data[i]){
|
||||
document.getElementById("connected").innerHTML = "<br />Button "+(i+1)+" Pressed"+document.getElementById("connected").innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastState = data;
|
||||
}
|
||||
|
||||
let device;
|
||||
|
||||
connectButton.onclick = async () => {
|
||||
navigator.hid.requestDevice({
|
||||
filters: [{ vendorId: 0x0fd9}] // elgato?
|
||||
}).then((devices)=>{
|
||||
console.log(devices);
|
||||
|
||||
device = devices[0];
|
||||
|
||||
console.log(`HID connected: ${device.productName}`);
|
||||
document.getElementById("connected").innerHTML = "<br />Connected" +document.getElementById("connected").innerHTML;
|
||||
document.getElementById("disconnectButton").style.display = "inline-block";
|
||||
device.addEventListener("inputreport", handleInputReport);
|
||||
|
||||
//device.sendReport(outputReportId, outputReport).then(() => {
|
||||
// console.log("Sent output report " + outputReportId);
|
||||
//});
|
||||
|
||||
if (!device.opened){
|
||||
device.open().then(()=>{
|
||||
window.addEventListener("onbeforeunload", async () => {
|
||||
await device.close();
|
||||
});
|
||||
}).catch(function(err){console.error(err);});
|
||||
}
|
||||
}).catch(function(err){console.error(err);});
|
||||
|
||||
};
|
||||
|
||||
disconnectButton.onclick = async () => {
|
||||
await device.close();
|
||||
|
||||
//connected.style.display = "none";
|
||||
//connectButton.style.display = "initial";
|
||||
disconnectButton.style.display = "none";
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,129 +1,129 @@
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
color:black;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
width:100%;height:100%;display:none;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 50vh; display:none; float:left; position: fixed; top: 0; right: 0%;
|
||||
}
|
||||
iframe{
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
width:60vw;height:100%;display:none;
|
||||
z-index:5;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 80vh; display:none; float:left; position: fixed; top: 0; right: -10vw;
|
||||
}
|
||||
iframe{
|
||||
max-width:60vw;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div id="container2"></div>
|
||||
<div id="container1" ></div>
|
||||
<div id="clean">
|
||||
<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" />
|
||||
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
|
||||
<div id="viewLink">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function updateLink(event){
|
||||
var streamid = document.getElementById("vdonlink").value;
|
||||
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
var viewLink = "https://"+path+"/?view="+streamid;
|
||||
document.getElementById("viewLink").innerHTML = "View link is: "+viewLink;
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var streamid = document.getElementById("vdonlink").value;
|
||||
var youtube = document.getElementById("youtube").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
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 iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<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 content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<style>
|
||||
body{
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color:#003;
|
||||
width:100%;
|
||||
height:100%;
|
||||
color:white;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
margin:0;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
||||
input{
|
||||
padding:10px;
|
||||
width:80%;
|
||||
font-size:1.2em;
|
||||
z-index: 1000;
|
||||
color:black;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (orientation:portrait) {
|
||||
#container2{
|
||||
width:100%;height:100%;display:none;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 50vh; display:none; float:left; position: fixed; top: 0; right: 0%;
|
||||
}
|
||||
iframe{
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation:landscape) {
|
||||
#container2{
|
||||
width:60vw;height:100%;display:none;
|
||||
z-index:5;
|
||||
}
|
||||
#container1{
|
||||
width: 50vw;height: 80vh; display:none; float:left; position: fixed; top: 0; right: -10vw;
|
||||
}
|
||||
iframe{
|
||||
max-width:60vw;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div id="container2"></div>
|
||||
<div id="container1" ></div>
|
||||
<div id="clean">
|
||||
<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" />
|
||||
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
|
||||
<div id="viewLink">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Announce the new orientation number
|
||||
// alert(window.orientation);
|
||||
}, false);
|
||||
|
||||
function updateLink(event){
|
||||
var streamid = document.getElementById("vdonlink").value;
|
||||
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
var viewLink = "https://"+path+"/?view="+streamid;
|
||||
document.getElementById("viewLink").innerHTML = "View link is: "+viewLink;
|
||||
}
|
||||
|
||||
function loadIframes(url=false){
|
||||
|
||||
var streamid = document.getElementById("vdonlink").value;
|
||||
var youtube = document.getElementById("youtube").value;
|
||||
|
||||
document.getElementById("clean").parentNode.removeChild(document.getElementById("clean"));
|
||||
document.getElementById("container1").style.display="inline-block";
|
||||
document.getElementById("container2").style.display="inline-block";
|
||||
|
||||
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
|
||||
path = path.replace("/examples","");
|
||||
|
||||
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 iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room1;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container1").appendChild(iframeContainer);
|
||||
|
||||
setTimeout(function(){
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
|
||||
iframe.src = room2;
|
||||
var iframeContainer = document.createElement("div");
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.getElementById("container2").appendChild(iframeContainer);
|
||||
},3000);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
@ -1,182 +1,182 @@
|
||||
|
||||
<html>
|
||||
<head><style>
|
||||
span{margin:10px 0 0 0;display:block;}
|
||||
body {
|
||||
|
||||
background-color:#cdf;
|
||||
padding:0;
|
||||
width;100%;height:100%
|
||||
}
|
||||
|
||||
input{padding:5px;}
|
||||
|
||||
button {margin:10px 3px;}
|
||||
|
||||
#stream{
|
||||
display:block;
|
||||
}
|
||||
|
||||
</style></head>
|
||||
<body id="main" style="margin:5%;"
|
||||
<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>
|
||||
<div id="devices">
|
||||
<div class="select">
|
||||
<label for="videoSource">Video source: </label><select id="videoSource"></select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<label for="audioSource">Audio source: </label><select id="audioSource"></select>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="fullwindow()">FULL WINDOW</button>
|
||||
<script>
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
console.error(errorMsg);
|
||||
console.error(lineNumber);
|
||||
console.error("Unhandeled Error occured"); //or any message
|
||||
return false;
|
||||
};
|
||||
|
||||
function fullwindow(){
|
||||
videoElement.style.width="100%";
|
||||
videoElement.style.padding= "0";
|
||||
videoElement.style.margin="0";
|
||||
videoElement.style.height="100%";
|
||||
videoElement.style.zIndex="5";
|
||||
videoElement.style.position = "absolute";
|
||||
videoElement.style.top="0px";
|
||||
videoElement.style.left="0px";
|
||||
document.getElementById("main").style.overflow = "hidden";
|
||||
videoElement.style.overflow = "hidden"
|
||||
document.getElementById("main").style.backgroundColor="#000";
|
||||
videoElement.style.cursor="none";
|
||||
document.getElementById("main").style.cursor="none";
|
||||
}
|
||||
|
||||
var videoElement = document.getElementById("video");
|
||||
var gotDev = false;
|
||||
async function gotDevices() {
|
||||
if (gotDev){return;}
|
||||
gotDev=true;
|
||||
await navigator.mediaDevices.getUserMedia({audio:true, video:true}); // is needed to ask for permissinos.
|
||||
navigator.mediaDevices.enumerateDevices().then((deviceInfos)=>{
|
||||
for (let i = 0; i !== deviceInfos.length; ++i) {
|
||||
var deviceInfo = deviceInfos[i];
|
||||
var option = document.createElement("option");
|
||||
option.value = deviceInfo.deviceId;
|
||||
|
||||
if (deviceInfo.kind === "audioinput") {
|
||||
option.text = deviceInfo.label || "microphone " + (audioSelect.length + 1);
|
||||
audioSelect.appendChild(option);
|
||||
if (option.text.startsWith("CABLE")){
|
||||
option.selected =true;
|
||||
}
|
||||
} else if (deviceInfo.kind === "videoinput") {
|
||||
option.text = deviceInfo.label || "camera " + (videoSelect.length + 1);
|
||||
if (option.text.startsWith("NewTek")){
|
||||
continue;
|
||||
}
|
||||
videoSelect.appendChild(option);
|
||||
if (option.text.startsWith("OBS")){
|
||||
option.selected =true;
|
||||
}
|
||||
}
|
||||
}
|
||||
getStream();
|
||||
});
|
||||
}
|
||||
|
||||
function getStream() {
|
||||
if (window.stream) {
|
||||
window.stream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
log("TRack stopping");
|
||||
});
|
||||
}
|
||||
|
||||
const constraints = {
|
||||
audio: {
|
||||
deviceId: { exact: audioSelect.value },
|
||||
echoCancellation : false,
|
||||
autoGainControl : false,
|
||||
noiseSuppression : false
|
||||
},
|
||||
video: {
|
||||
deviceId: { exact: videoSelect.value },
|
||||
width: { min: 1280, ideal: 1920, max: 1920 },
|
||||
height: { min: 720, ideal: 1080, max: 1080 }
|
||||
}
|
||||
};
|
||||
return navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then(gotStream)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
|
||||
function gotStream(stream) {
|
||||
if (window.stream) {
|
||||
window.stream = stream; // make stream available to console
|
||||
videoElement.srcObject = stream;
|
||||
var senders = session.pc.getSenders();
|
||||
videoElement.srcObject.getVideoTracks().forEach((track)=>{
|
||||
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?
|
||||
if (sender.track) {
|
||||
if (sender.track && sender.track.kind == "video") {
|
||||
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
|
||||
track.enabled = notCensored;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (added==false){
|
||||
session.pc.addTrack(track);
|
||||
log("ADDED NOT REPLACED?");
|
||||
}
|
||||
});
|
||||
|
||||
videoElement.srcObject.getAudioTracks().forEach((track)=>{
|
||||
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?
|
||||
if (sender.track) {
|
||||
if (sender.track && sender.track.kind == "audio") {
|
||||
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
|
||||
track.enabled = notCensored;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (added==false){
|
||||
session.pc.addTrack(track);
|
||||
log("ADDED NOT REPLACED?");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.stream = stream; // make stream available to console
|
||||
videoElement.srcObject = stream;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var audioSelect = document.querySelector("select#audioSource");
|
||||
var videoSelect = document.querySelector("select#videoSource");
|
||||
audioSelect.onchange = getStream;
|
||||
videoSelect.onchange = getStream;
|
||||
gotDevices();
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<html>
|
||||
<head><style>
|
||||
span{margin:10px 0 0 0;display:block;}
|
||||
body {
|
||||
|
||||
background-color:#cdf;
|
||||
padding:0;
|
||||
width;100%;height:100%
|
||||
}
|
||||
|
||||
input{padding:5px;}
|
||||
|
||||
button {margin:10px 3px;}
|
||||
|
||||
#stream{
|
||||
display:block;
|
||||
}
|
||||
|
||||
</style></head>
|
||||
<body id="main" style="margin:5%;"
|
||||
<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>
|
||||
<div id="devices">
|
||||
<div class="select">
|
||||
<label for="videoSource">Video source: </label><select id="videoSource"></select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<label for="audioSource">Audio source: </label><select id="audioSource"></select>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="fullwindow()">FULL WINDOW</button>
|
||||
<script>
|
||||
window.onerror = function backupErr(errorMsg, url=false, lineNumber=false) {
|
||||
console.error(errorMsg);
|
||||
console.error(lineNumber);
|
||||
console.error("Unhandeled Error occured"); //or any message
|
||||
return false;
|
||||
};
|
||||
|
||||
function fullwindow(){
|
||||
videoElement.style.width="100%";
|
||||
videoElement.style.padding= "0";
|
||||
videoElement.style.margin="0";
|
||||
videoElement.style.height="100%";
|
||||
videoElement.style.zIndex="5";
|
||||
videoElement.style.position = "absolute";
|
||||
videoElement.style.top="0px";
|
||||
videoElement.style.left="0px";
|
||||
document.getElementById("main").style.overflow = "hidden";
|
||||
videoElement.style.overflow = "hidden"
|
||||
document.getElementById("main").style.backgroundColor="#000";
|
||||
videoElement.style.cursor="none";
|
||||
document.getElementById("main").style.cursor="none";
|
||||
}
|
||||
|
||||
var videoElement = document.getElementById("video");
|
||||
var gotDev = false;
|
||||
async function gotDevices() {
|
||||
if (gotDev){return;}
|
||||
gotDev=true;
|
||||
await navigator.mediaDevices.getUserMedia({audio:true, video:true}); // is needed to ask for permissinos.
|
||||
navigator.mediaDevices.enumerateDevices().then((deviceInfos)=>{
|
||||
for (let i = 0; i !== deviceInfos.length; ++i) {
|
||||
var deviceInfo = deviceInfos[i];
|
||||
var option = document.createElement("option");
|
||||
option.value = deviceInfo.deviceId;
|
||||
|
||||
if (deviceInfo.kind === "audioinput") {
|
||||
option.text = deviceInfo.label || "microphone " + (audioSelect.length + 1);
|
||||
audioSelect.appendChild(option);
|
||||
if (option.text.startsWith("CABLE")){
|
||||
option.selected =true;
|
||||
}
|
||||
} else if (deviceInfo.kind === "videoinput") {
|
||||
option.text = deviceInfo.label || "camera " + (videoSelect.length + 1);
|
||||
if (option.text.startsWith("NewTek")){
|
||||
continue;
|
||||
}
|
||||
videoSelect.appendChild(option);
|
||||
if (option.text.startsWith("OBS")){
|
||||
option.selected =true;
|
||||
}
|
||||
}
|
||||
}
|
||||
getStream();
|
||||
});
|
||||
}
|
||||
|
||||
function getStream() {
|
||||
if (window.stream) {
|
||||
window.stream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
log("TRack stopping");
|
||||
});
|
||||
}
|
||||
|
||||
const constraints = {
|
||||
audio: {
|
||||
deviceId: { exact: audioSelect.value },
|
||||
echoCancellation : false,
|
||||
autoGainControl : false,
|
||||
noiseSuppression : false
|
||||
},
|
||||
video: {
|
||||
deviceId: { exact: videoSelect.value },
|
||||
width: { min: 1280, ideal: 1920, max: 1920 },
|
||||
height: { min: 720, ideal: 1080, max: 1080 }
|
||||
}
|
||||
};
|
||||
return navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then(gotStream)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
|
||||
function gotStream(stream) {
|
||||
if (window.stream) {
|
||||
window.stream = stream; // make stream available to console
|
||||
videoElement.srcObject = stream;
|
||||
var senders = session.pc.getSenders();
|
||||
videoElement.srcObject.getVideoTracks().forEach((track)=>{
|
||||
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?
|
||||
if (sender.track) {
|
||||
if (sender.track && sender.track.kind == "video") {
|
||||
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
|
||||
track.enabled = notCensored;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (added==false){
|
||||
session.pc.addTrack(track);
|
||||
log("ADDED NOT REPLACED?");
|
||||
}
|
||||
});
|
||||
|
||||
videoElement.srcObject.getAudioTracks().forEach((track)=>{
|
||||
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?
|
||||
if (sender.track) {
|
||||
if (sender.track && sender.track.kind == "audio") {
|
||||
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
|
||||
track.enabled = notCensored;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (added==false){
|
||||
session.pc.addTrack(track);
|
||||
log("ADDED NOT REPLACED?");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.stream = stream; // make stream available to console
|
||||
videoElement.srcObject = stream;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var audioSelect = document.querySelector("select#audioSource");
|
||||
var videoSelect = document.querySelector("select#videoSource");
|
||||
audioSelect.onchange = getStream;
|
||||
videoSelect.onchange = getStream;
|
||||
gotDevices();
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,167 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebRTC File Sharing</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
/* Container Styles */
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
h1 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 36px;
|
||||
color: #333333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* File Selector Styles */
|
||||
#file-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#file-selector label {
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#file-selector input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-selector button {
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#file-selector button:hover {
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
|
||||
/* Progress Container Styles */
|
||||
#progress-container {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#progress-bar-inner {
|
||||
height: 100%;
|
||||
background-color: #4CAF50;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#connections {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* Input Styles */
|
||||
input[type="file"] {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dddddd;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
button {
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebRTC File Sharing</h1>
|
||||
|
||||
<div id="file-selector">
|
||||
<label for="file-input">Select a file:</label>
|
||||
<input type="file" id="file-input" />
|
||||
<button id="share-button">Share</button>
|
||||
</div>
|
||||
|
||||
<div id="progress-container" style="display:none">
|
||||
<div id="progress-bar">
|
||||
<div id="progress-bar-inner"></div>
|
||||
</div>
|
||||
<div id="status">Connecting...</div>
|
||||
<div id="connections">0 connections</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var fileInput = document.getElementById("file-input");
|
||||
var shareButton = document.getElementById("share-button");
|
||||
var progressContainer = document.getElementById("progress-container");
|
||||
var progressBarInner = document.getElementById("progress-bar-inner");
|
||||
var status = document.getElementById("status");
|
||||
var connections = document.getElementById("connections");
|
||||
var connectionCount = 0;
|
||||
|
||||
shareButton.addEventListener("click", function() {
|
||||
if (fileInput.files.length > 0) {
|
||||
// Display progress bar and status message
|
||||
progressContainer.style.display = "block";
|
||||
status.innerText = "Connecting...";
|
||||
|
||||
// TODO: Use WebRTC to share the file with other users
|
||||
|
||||
// Update progress bar and status messages
|
||||
progressBarInner.style.width = "100%";
|
||||
status.innerText = "File shared successfully.";
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Use WebRTC to display the number of connections, progress, and speed
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebRTC File Sharing</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
/* Container Styles */
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
h1 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 36px;
|
||||
color: #333333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* File Selector Styles */
|
||||
#file-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#file-selector label {
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#file-selector input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-selector button {
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#file-selector button:hover {
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
|
||||
/* Progress Container Styles */
|
||||
#progress-container {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#progress-bar-inner {
|
||||
height: 100%;
|
||||
background-color: #4CAF50;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#connections {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* Input Styles */
|
||||
input[type="file"] {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dddddd;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
button {
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebRTC File Sharing</h1>
|
||||
|
||||
<div id="file-selector">
|
||||
<label for="file-input">Select a file:</label>
|
||||
<input type="file" id="file-input" />
|
||||
<button id="share-button">Share</button>
|
||||
</div>
|
||||
|
||||
<div id="progress-container" style="display:none">
|
||||
<div id="progress-bar">
|
||||
<div id="progress-bar-inner"></div>
|
||||
</div>
|
||||
<div id="status">Connecting...</div>
|
||||
<div id="connections">0 connections</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var fileInput = document.getElementById("file-input");
|
||||
var shareButton = document.getElementById("share-button");
|
||||
var progressContainer = document.getElementById("progress-container");
|
||||
var progressBarInner = document.getElementById("progress-bar-inner");
|
||||
var status = document.getElementById("status");
|
||||
var connections = document.getElementById("connections");
|
||||
var connectionCount = 0;
|
||||
|
||||
shareButton.addEventListener("click", function() {
|
||||
if (fileInput.files.length > 0) {
|
||||
// Display progress bar and status message
|
||||
progressContainer.style.display = "block";
|
||||
status.innerText = "Connecting...";
|
||||
|
||||
// TODO: Use WebRTC to share the file with other users
|
||||
|
||||
// Update progress bar and status messages
|
||||
progressBarInner.style.width = "100%";
|
||||
status.innerText = "File shared successfully.";
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Use WebRTC to display the number of connections, progress, and speed
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -31,7 +31,7 @@
|
||||
<script src='libs/glfx.js'></script>
|
||||
|
||||
<!-- INCLUDE DEMO SCRIPT -->
|
||||
<script src="./main.js"></script>
|
||||
<script src="main.js"></script>
|
||||
|
||||
<!-- INCLUDE ADDDRAGEVENTLISTENER.JS -->
|
||||
<script src='../../../helpers/addDragEventListener.js'></script>
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 367 KiB After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |