16 Commits
18.1 ... 18.2

Author SHA1 Message Date
Steve Seguin
3e30724e8c Update index.html 2021-06-20 12:33:23 -04:00
Steve Seguin
5e2249c8b9 Update install.md
branding fixes
2021-06-18 17:49:28 -04:00
Steve Seguin
b366469e59 Update install.md
updated to include context isolation, to allow for ffmpeg.js to work:
details:
https://marketplace.zoom.us/docs/sdk/native-sdks/web/advanced/web-isolation
2021-06-18 17:48:05 -04:00
Steve Seguin
a125421357 Merge pull request #877 from steveseguin/update-convert-ui
tweak the /convert UI
2021-06-17 13:20:47 -04:00
Joel Calado
9db09a6278 tweak the /convert UI 2021-06-17 18:03:57 +01:00
Steve Seguin
d87d04a4d4 Merge pull request #874 from steveseguin/18.2-beta
v18.2 beta
2021-06-15 04:54:39 -04:00
Steve Seguin
0154565589 Update IFRAME.md 2021-06-10 12:06:44 -04:00
Steve Seguin
6912a98867 Update IFRAME.md 2021-06-10 12:05:54 -04:00
Steve Seguin
dc26a23ff9 v18.2 beta 2021-06-07 12:51:24 -04:00
Steve Seguin
be28fbde1b Merge pull request #872 from steveseguin/improve-electron-page-GO-button
Add files via upload
2021-06-05 17:44:00 -04:00
Joel Calado
b32d040ea2 Add files via upload 2021-06-05 22:40:51 +01:00
Steve Seguin
4010bd220e Add files via upload 2021-06-05 17:26:11 -04:00
Steve Seguin
b37fcf855d Update README.md 2021-06-04 17:43:38 -04:00
Steve Seguin
d798c6bd14 Update README.md 2021-06-04 17:43:03 -04:00
Steve Seguin
717c3aa7cc Update README.md 2021-06-04 17:39:22 -04:00
Steve Seguin
e424cf3c5c Update README.md 2021-06-02 12:46:05 -04:00
8 changed files with 567 additions and 379 deletions

View File

@@ -1,38 +1,38 @@
## OBS.Ninja - iFrame API documentation
## VDO.Ninja - iFrame API documentation
OBS.Ninja (OBSN) is offers here a simple and free solution to quickly enable real-time video streaming in their websites. OBSN wishes to make live video streaming development accessible to any developer, even novices, yet still remain flexible and powerful.
VDO.Ninja (VDON) is offers here a simple and free solution to quickly enable real-time video streaming in their websites. VDON wishes to make live video streaming development accessible to any developer, even novices, yet still remain flexible and powerful.
While OBS.Ninja does offer source-code to customize the application and UI at a low level, this isn't for beginners and it is rather hard to maintain. As well, due to the complexity of video streaming in the web, typical approaches for offering API access isn't quite feasible either.
While VDO.Ninja does offer source-code to customize the application and UI at a low level, this isn't for beginners and it is rather hard to maintain. As well, due to the complexity of video streaming in the web, typical approaches for offering API access isn't quite feasible either.
The solution decided on isn't an SDK framework, but rather the use of embeddable _IFrames_ and a corresponding bi-directional iframe API. An [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) allows us to embed a webpage inside a webpage, including OBS.Ninja into your own website.
The solution decided on isn't an SDK framework, but rather the use of embeddable _IFrames_ and a corresponding bi-directional iframe API. An [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) allows us to embed a webpage inside a webpage, including VDO.Ninja into your own website.
Modern web browsers allow the parent website to communicate with the child webpage, giving a high-level of control to a developer, while also abstracting the complex code and hosting requirements. Functionality, we can make an OBSN video stream act much like an HTML video element tag, where you can issue commands like play, pause, or change video sources with ease.
Modern web browsers allow the parent website to communicate with the child webpage, giving a high-level of control to a developer, while also abstracting the complex code and hosting requirements. Functionality, we can make an VDON video stream act much like an HTML video element tag, where you can issue commands like play, pause, or change video sources with ease.
Creating an OBSN iframe can be done in HTML or programmatically with Javascript like so:
Creating an VDON iframe can be done in HTML or programmatically with Javascript like so:
```
const iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone";
iframe.allowtransparency = "false";
iframe.src = "https://obs.ninja/?webcam";
iframe.src = "https://vdo.ninja/?webcam";
```
You can also make an OBS.Ninja without Javascript, using just HTML, like
You can also make an VDO.Ninja without Javascript, using just HTML, like
`<iframe allow="autoplay;camera;microphone" src="https://obs.ninja/?view=vhX5PYg&cleanoutput&transparent"></iframe>`
`<iframe allow="autoplay;camera;microphone" src="https://vdo.ninja/?view=vhX5PYg&cleanoutput&transparent"></iframe>`
Adding that iframe to the DOM will reveal a simple page accessing for a user to select and share their webcam. For a developer wishing to access a remote guest's stream, this makes the ingestion of that stream into production software like OBS Studios very easy. The level of customization and control opens up opportunities, such as a pay-to-join audience option for a streaming interactive broadcast experience.
An example of how this API is used by OBS.Ninja is with its Internet Speedtest, which has two OBS.Ninja IFrames on a single page. One iframe feeds video to the other iframe, and the speed at which it does this is a measure of the system's performance. Detailed stats of the connection are made available to the parent window, which displays the results.
https://obs.ninja/speedtest
An example of how this API is used by VDO.Ninja is with its Internet Speedtest, which has two VDO.Ninja IFrames on a single page. One iframe feeds video to the other iframe, and the speed at which it does this is a measure of the system's performance. Detailed stats of the connection are made available to the parent window, which displays the results.
https://vdo.ninja/speedtest
More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/obsninja/tree/master/examples
More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/vdoninja/tree/master/examples
A sandbox of options is available at this page, too: https://obs.ninja/iframe You can enter an OBS.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/obsninja/blob/master/iframe.html
A sandbox of options is available at this page, too: https://vdo.ninja/iframe You can enter an VDO.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/vdoninja/blob/master/iframe.html
One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe _src_ URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control OBS.Ninja, while the former is used to initiate the instance to a desired state.
One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe _src_ URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control VDO.Ninja, while the former is used to initiate the instance to a desired state.
For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/obsninja/wiki/Advanced-Settings
For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/vdoninja/wiki/Advanced-Settings
Some of the more interesting ones primarily for iframe users might include:
@@ -56,7 +56,7 @@ Some of the more interesting ones primarily for iframe users might include:
- Send/Recieve a chat message to other connected guests
- Get notified when there is a video connection
As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file:
As for the actually details for methods and options available to dynamically control child VDON iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file:
```js
let button = document.createElement("button");
@@ -228,7 +228,7 @@ button.onclick = () => {
};
iframeContainer.appendChild(button);
// As for listening events, where the parent listens for responses or events from the OBSN child frame:
// As for listening events, where the parent listens for responses or events from the VDON child frame:
// ////////// LISTEN FOR EVENTS
@@ -298,10 +298,8 @@ eventer(messageEvent, function (e) {
});
```
This OBS.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete.
This VDO.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete.
Regarding versioning, I currently host past versions of OBS.Ninja, so using those past versions can ensure some level of consistency and expectation. https://obs.ninja/v12/ for example is the version 12 hosted version. The active and main production version of OBSN of course undergoes constant updates, and while I try to maintain backwards compatibility with changes to the API, it is still early days and changes might happen.
Please feel free to follow me in the OBS.Ninja Discord channel, where I post news about updates and listen to requests. The upcoming version of OBS.Ninja is also often hosted at https://obs.ninja/beta, where you can explore new features and help crush any unexpected bugs.
Please feel free to follow me in the VDO.Ninja Discord channel (discord.vdo.ninja) where I post news about updates and listen to requests. The upcoming version of VDO.Ninja is also often hosted at https://vdo.ninja/beta, where you can explore new features and help crush any unexpected bugs.
-steve

View File

@@ -14,10 +14,10 @@ VDO.Ninja is designed to allow content creators to produce real-time live shows
VDO.Ninja is freely available to use as a managed service over at https://vdo.ninja.
For live support, please join our discord at https://discord.obs.ninja
Please see the sub-reddit added info: https://reddit.com/r/obsninja
Please see the sub-reddit added info: https://reddit.com/r/vdoninja
Also check out the FAQ for more info: https://github.com/steveseguin/vdoninja/wiki
<img src="https://user-images.githubusercontent.com/2575698/94018108-34b1de00-fd7e-11ea-8c7d-df001253b60d.png" height="300" />
<img src="https://user-images.githubusercontent.com/2575698/120865595-56de3b80-c55c-11eb-8b98-60c59ae0f904.png" height="300" />
## How to use
I demo the basic usage of VDO.Ninja on YouTube: https://www.youtube.com/watch?v=6R_sQKxFAhg
@@ -47,7 +47,7 @@ A design goal of VDO.Ninja is to be serverless and we are 99% of the way there.
## Issues? problems? Not working?
Please see the sub-reddit for more support: https://reddit.com/r/obsninja
Please see the sub-reddit for more support: https://reddit.com/r/vdoninja
Also check out the FAQ for common answers: https://github.com/steveseguin/vdoninja/wiki
@@ -55,7 +55,7 @@ If urgent, join me on discord: https://discord.gg/EksyhGA or email me at steve@s
## Related Projects
### VDO.Ninja's Electron Capture:
A better way to perform "Window Capturing" on desktop if OBS Browser Sources fails you. A downloadable tool designed to enhance OBS.Ninja.
A better way to perform "Window Capturing" on desktop if OBS Browser Sources fails you. A downloadable tool designed to enhance VDO.Ninja.
https://github.com/steveseguin/electroncapture
### CAPTION.Ninja

View File

@@ -1,157 +1,230 @@
<head>
<link rel="stylesheet" href="./main.css?ver=39" />
<style>
body {
}
input {padding:10px;}
h3 { margin-top:10px; padding:5px;}
hr {
margin: 20px 0;
}
video{
max-width:640px;
max-height:360px;
padding:20px;
}
audio{
max-width:640px;
max-height:360px;
padding:20px;
}
</style>
</head>
<body style='color:white'>
<video id="player" controls style="display:none"></video>
<audio id="player2" controls style="display:none"></audio>
<div id="info">
<h1>Web-based Media Conversion Tools</h1>
<hr>
<h3>Transcodes WebM files to MP4 files with a fixed 1280x720 resolution. (very slow!)</h3><br />
<small><p>This tool performs the following action in your browser: <i> fmpeg -i input.webm -vf scale=1280:720 output.mp4</i></p></small>
<input type="file" id="uploader" title="Convert WebM to MP4">
<hr>
<h3>Remuxes MKV files to MP4 files without transcoding.</h3> </p><br /><small><i> fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</i></small>
<br /><input type="file" id="uploader2" accept=".mkv" title="Convert MKV to MP4">
<hr>
<h3>Remuxes WebM files to MP4 files without transcoding (attempts to force high resolutions, also)</h3>
<input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
<hr>
<h3>Remuxes WebM to Audio-only files (opus or wav)</h3>
<input type="file" id="uploader4" accept=".webm" title="Convert WebM to OPUS">
<hr>
</div>
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.9.6/dist/ffmpeg.min.js"></script>
<script>
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];
document.getElementById('uploader').style.display="none";
document.getElementById('uploader2').style.display="none";
document.getElementById('uploader3').style.display="none";
document.getElementById('info').innerText = "Transcoding file... this will take a while";
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('info').innerText = "Operation Done. Play video or download it.";
}
const transmux = async ({ target: { files } }) => {
const { name } = files[0];
document.getElementById('uploader').style.display="none";
document.getElementById('uploader2').style.display="none";
document.getElementById('uploader3').style.display="none";
document.getElementById('info').innerText = "Transcoding file... this will take a while";
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('info').innerText = "Operation Done. Play video or download it.";
}
const force1080 = async ({ target: { files } }) => {
const { name } = files[0];
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('info').innerText = "Tweaking file... this will take a moment";
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('info').innerText = "Operation Done. Play video or download it.";
}
const convertToAudioOnly = async ({ target: { files } }) => {
const { name } = files[0];
document.getElementById('info').innerText = "Transcoding file... this will take a while";
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");
<link rel="stylesheet" href="./main.css?ver=39" />
<style>
.container {
max-width: 80%;
width: fit-content;
margin: 0 auto;
}
video.style.display="block";
document.getElementById('info').innerText = "Operation Done. Play audio or download it.";
}
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>
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;
}
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 to MP4 (fixed 1280x720 resolution) <span class='warning'>(very slow!)</span></h2>
<div>
<small>The same as: fmpeg -i input.webm -vf scale=1280:720 output.mp4</small>
<input type="file" id="uploader" title="Convert WebM to MP4">
</div>
</div>
<div class="card">
<h2>MKV to MP4 (no transcoding)</h2>
<div>
<small>The same as: fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</small>
<input type="file" id="uploader2" accept=".mkv" title="Convert MKV to MP4">
</div>
</div>
<div class="card">
<h2>WebM MP4 files (no transcoding, attempts to force high resolutions)</h2>
<div>
<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>
<input type="file" id="uploader4" accept=".webm" title="Convert WebM to OPUS">
</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 src="https://unpkg.com/@ffmpeg/ffmpeg@0.9.6/dist/ffmpeg.min.js"></script>
<script>
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];
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];
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];
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];
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>

View File

@@ -44,24 +44,21 @@
outline: none !important;
}
#gobutton{
padding: 1em;
font-size: 1em;
margin: auto auto;
height: 100%;
font-family: system-ui;
#gobutton {
font-size: 20px;
font-weight: bold;
border: 2px solid #6aab23;
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;
position: relative;
right: 2px;
top: 1px;
color:white;
cursor:pointer;
box-shadow: 0 12px 15px -10px #5ca70b, 0 2px 0px #6aab23;
color: white;
cursor: pointer;
box-sizing: border-box;
align-items: center;
padding: 0 1em;
}
#header{
width:100%;
@@ -73,13 +70,15 @@
width: 100%;
padding: 1em;
font-weight: bold;
font-family: system-ui;
background: white;
border-bottom: 4px solid #6aab23;
box-shadow: 0px 30px 40px -32px #6aab23;
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;
}
input#changeText:focus {
@@ -267,37 +266,46 @@
background-color: #182031;
}
#inputCombo {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-grow: 1;
}
</style>
</head>
<body >
<div id="header" style="-webkit-app-region: drag;color:#6f6f6f;font-size:40px; line-height: 40px; padding: 20px; letter-spacing: 3; font-weight: bold;">OBS.Ninja</div>
<div class="container" >
<div id="header" style="-webkit-app-region: drag;color:#6f6f6f;font-size:40px; line-height: 40px; padding: 20px; letter-spacing: 3; font-weight: bold;">Electron Capture</div>
<div class="container" >
<div id='warning4mac' style="display:none;"> ✨ Great News! OBS v26.1.2 <a href="https://github.com/obsproject/obs-browser/issues/209#issuecomment-748683083">now supports</a> VDO.Ninja without needing the Electron Capture app! 🥳</div>
<div id="electronVersion" style="display:none;">✨ Great News! <a href="https://github.com/steveseguin/electroncapture/releases/latest">Electron Capture <span id="currentElectronVersion"></span></a> is now available!<br>Update yours today to stay up-to-date with security patches.</div>
<div id="messageDiv" style='display:block'><br /></div>
<div class="container">
<div id="urlInput" title="Put the link you want to load here">
<label for="changeText">
<i class="las la-play"></i>
</label>
<div id="inputCombo">
<input type="text" id="changeText" class="inputfield" value="http://vdo.ninja/?view=" onchange="modURL" onkeyup="enterPressed(event, gohere);" />
<button onclick="gohere();" id="gobutton">GO</button>
</div>
</div>
<div id="audioOutputContainer" title="This option will only work with the official vdo.ninja domain">
<label for="audioOutput"><i class="las la-headphones"></i></label><select id="audioOutput"></select>
</div>
<div id="history" title="History of past links used. You can clear this history using the button to the left">
<label for="lastUrls" onclick="resetHistory()">
<i class="las la-history"></i>
</label>
<select id="lastUrls" onchange="setUrl()"></select>
</div>
<div id='warning4mac' style="display:none;"> ✨ Great News! OBS v26.1.2 <a href="https://github.com/obsproject/obs-browser/issues/209#issuecomment-748683083">now supports</a> OBS.Ninja without needing the Electron Capture app! 🥳</div>
<div id="electronVersion" style="display:none;">✨ Great News! <a href="https://github.com/steveseguin/electroncapture/releases/latest">Electron Capture <span id="currentElectronVersion"></span></a> is now available!<br>Update yours today to stay up-to-date with security patches.</div>
<div id="messageDiv" style='display:block'><br /></div>
<div class="container">
<div id="urlInput" title="Put the link you want to load here">
<label for="changeText">
<i class="las la-play"></i>
</label>
<input type="text" id="changeText" class="inputfield" value="http://obs.ninja/?view=" onchange="modURL" onkeyup="enterPressed(event, gohere);" />
<button onclick="gohere();" id="gobutton">GO</button>
</div>
<div id="audioOutputContainer" title="This option will only work with the official obs.ninja domain">
<label for="audioOutput"><i class="las la-headphones"></i></label><select id="audioOutput"></select>
</div>
<div id="history" title="History of past links used. You can clear this history using the button to the left">
<label for="lastUrls" onclick="resetHistory()">
<i class="las la-history"></i>
</label>
<select id="lastUrls" onchange="setUrl()"></select>
</div>
</div>
</div>
</div>
<script>
@@ -522,8 +530,8 @@ function updateURLParameter(url, param, paramVal){
if (urlParams.has('name')){
var name = urlParams.get('name');
if (name!="OBSNinja"){
document.getElementById('changeText').value = "https://obs.ninja/?view="+name;
if (name!="OBSNinja" && name!="VDONinja"){
document.getElementById('changeText').value = "https://vdo.ninja/?view="+name;
}
}
@@ -551,7 +559,9 @@ function gohere(){
addUrlToHistory(document.getElementById('changeText').value);
localStorage.setItem('lastUrls', JSON.stringify(lastUrls));
var url = modURL();
if (!(document.getElementById('changeText').value.includes("obs.ninja")) && (document.getElementById('changeText').value.includes("http")) && (document.getElementById('changeText').value.includes("&sink"))){
if ((document.getElementById('changeText').value.includes("obs.ninja")) && (document.getElementById('changeText').value.includes("http")) && (document.getElementById('changeText').value.includes("&sink"))){
alert("Notice: OBS.Ninja has been replaced by VDO.Ninja.\n\nPlease update your links accordingly for audio output to work correctly.");
} else if (!(document.getElementById('changeText').value.includes(window.location.hostname)) && (document.getElementById('changeText').value.includes("http")) && (document.getElementById('changeText').value.includes("&sink"))){
alert("Notice: The &sink command is domain specific.\nVisit https://YOURDOMAIN.com/electron instead.");
}
window.location = url;

View File

@@ -177,7 +177,7 @@
<span
id="helpbutton"
title="Show Help Info"
onclick="warnUser('For support, please browse https://reddit.com/r/obsninja or join the live chat on Discord at https://discord.obs.ninja.\n\nThe Docs also contains many help guides and advanced settings, located at https://docs.obs.ninja.\n\nTo access the video stats menu, hold CTRL (command) and Left-Click on a video. Most video issues can be fixed by using Wired Internet instead of Wi-Fi.')"
onclick="warnUser('For support, please browse https://reddit.com/r/obsninja or join the live chat on Discord at https://discord.obs.ninja.\n\nThe Docs also contains many help guides and advanced settings, located at https://docs.vdo.ninja.\n\nTo access the video stats menu, hold CTRL (command) and Left-Click on a video. Most video issues can be fixed by using Wired Internet instead of Wi-Fi.')"
style="cursor: pointer; display:none;"
alt="How to Use This with OBS"
>
@@ -604,7 +604,7 @@
</div>
</div>
<div>See the
<a style="text-decoration: none; color: blue;" target="_blank" href="https://docs.obs.ninja/advanced-settings">documentation</a> for more options and info.<br /><br />
<a style="text-decoration: none; color: blue;" target="_blank" href="https://docs.vdo.ninja/advanced-settings">documentation</a> for more options and info.<br /><br />
Try out the advanced <a style="text-decoration: none; color: blue;" target="_blank" href="https://invite.obs.ninja/">invite generator</a> here also.
</div>
</div>
@@ -714,22 +714,22 @@
</i>
<br />
<li>
If you have <a href="https://docs.obs.ninja/common-errors-and-known-issues/video-is-pixelated">"pixel smearing"</a> or corrupted video, try adding <i>&codec=h264</i> or <i>&codec=vp9</i> to the OBS view link. Using Wi-Fi will make the issue worse.
If you have <a href="https://docs.vdo.ninja/common-errors-and-known-issues/video-is-pixelated">"pixel smearing"</a> or corrupted video, try adding <i>&codec=h264</i> or <i>&codec=vp9</i> to the OBS view link. Using Wi-Fi will make the issue worse.
</li>
<li>
A list of less common issues can <a href="https://docs.obs.ninja/common-errors-and-known-issues/known-issues-browser-bugs-and-more">be found here</a>.
A list of less common issues can <a href="https://docs.vdo.ninja/common-errors-and-known-issues/known-issues-browser-bugs-and-more">be found here</a>.
</li>
<br />
<h4 style="color:#daad09;">
👋 👀 Welcome to our new domain! We've started to rebrand to VDO.Ninja. 📼 Nothing else is changing.
👋 👀 Welcome to our new domain! We've started to rebrand to VDO.Ninja. 📼 Nothing else is changing and we're staying 100% free.
</h4>
<br />
🌻 Site Updated on May 31st. The new <a href="https://docs.obs.ninja/release-notes/v18">v18 release notes are here</a>. If new issues occur, the previous version can also be <a href="https://obs.ninja/v17/">found here</a>.
🌻 Site Updated on June 6th. The new <a href="https://docs.vdo.ninja/release-notes/v18">v18 release notes are here</a>. If new issues occur, the previous version can also be <a href="https://obs.ninja/v17/">found here</a>.
<br />
<br />
<h3>
🛠 For support, see the <a href="https://www.reddit.com/r/OBSNinja/">sub-reddit <i class="lab la-reddit-alien"></i></a> or join the <a href="https://discord.gg/T4xpQVv">Discord <i class="lab la-discord"></i></a>. The <a href="https://docs.obs.ninja/">documentation is here</a> and my personal email is <i>steve@seguin.email</i>
🛠 For support, see the <a href="https://www.reddit.com/r/OBSNinja/">sub-reddit <i class="lab la-reddit-alien"></i></a> or join the <a href="https://discord.gg/T4xpQVv">Discord <i class="lab la-discord"></i></a>. The <a href="https://docs.vdo.ninja/">documentation is here</a> and my personal email is <i>steve@seguin.email</i>
</h3>
</span>
@@ -785,7 +785,7 @@
<li>Adding &showonly=SOME_OBS_VIRTUALCAM to the guest invite links allows for only a single video to be seen by the guests; this can be output of the OBS Virtual Camera for example</li>
<br />
For advanced URL options and parameters, <a href="https://docs.obs.ninja/advanced-settings">see the Wiki.</a>
For advanced URL options and parameters, <a href="https://docs.vdo.ninja/advanced-settings">see the Wiki.</a>
</font>
</div>
</div>
@@ -902,12 +902,14 @@
<span class="slider"></span>
</label>
<span data-translate="hide-setting-buttons">Hide settings button</span>
<Br />
<label class="switch" title="The guest won't have access to changing camera settings or screenshare">
<label class="switch" title="The guest's self-video preview will appear tiny in the top right">
<input type="checkbox" data-param="&mini" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<span data-translate="mini-self-preview">Mini self-preview</span>
<Br />
<label class="switch" title="Allow the guests to pick a virtual backscreen effect">
<input type="checkbox" data-param="&effects" onchange="updateLink(1,this);">
@@ -1576,8 +1578,8 @@
<i class="las la-hand-paper"></i>
</div>
<audio id="testtone" style="display:none;" preload="none">
<source src="tone.mp3" type="audio/mpeg">
<source src="tone.ogg" type="audio/ogg">
<source src="./media/tone.mp3" type="audio/mpeg">
<source src="./media/tone.ogg" type="audio/ogg">
</audio>
<div class="gone" >
<!-- This image is used when dragging elements -->
@@ -1588,7 +1590,7 @@
<div id="screenPopup" class="popup-screen">
<button onclick="getById('screenPopup').style.display='none';margin:0;padding:0;">Close Window</button>
<div>See the
<a style="text-decoration: none; color: blue;" target="_blank" href="https://docs.obs.ninja/advanced-settings">documentation</a> for more options and info.
<a style="text-decoration: none; color: blue;" target="_blank" href="https://docs.vdo.ninja/advanced-settings">documentation</a> for more options and info.
</div>
</div>
@@ -1638,7 +1640,7 @@
}
var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js.
session.version = "18.1";
session.version = "18.2";
session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed
session.defaultPassword = "someEncryptionKey123"; // Change this password if self-deploying for added security/privacy
@@ -1719,7 +1721,7 @@
<script type="text/javascript" id="main-js" src="./main.js" data-translation="blank"></script>
<script type="text/javascript" crossorigin="anonymous" id="mixer-js" src="./mixer.js?ver=2"></script>
-->
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=210"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=226"></script>
<script type="text/javascript">
setTimeout(function(){ // lazy load
var script = document.createElement('script');

View File

@@ -13,25 +13,24 @@ For those looking for a brand-free experience already with a different domain na
- https://chromicam.com
- https://invite.cam (via URL obfuscation option)
- https://ltt.ninja
- https://obsn.me
- https://rtc.ninja
- https://vmix.ninja
- https://webrtc.party
- https://callin.ninja
- https://auxiliary.live (backup hosted)
- https://backup.obs.ninja (fully backup hosted)
- https://backup.vdo.ninja (fully backup hosted)
There is also an isolated version specificly designed for use in mainland China, hosted at https://insecure.cam in Hong Kong AWS.
You can also point your domain to the OBS.Ninja IP address (provided on request), which will also rebrand the site automatically to match your domain name. (Requires Cloudflare as DNS server and proxy, Flexible SSL cert on, and HTTPs always on - all free.)
You can also point your domain to the VDO.Ninja IP address (provided on request), which will also rebrand the site automatically to match your domain name. (Requires Cloudflare as DNS server and proxy, Flexible SSL cert on, and HTTPs always on - all free.)
For those wanting a private TURN server setup, you can load the settings for those via the URL parameters. If infrequently needing a private TURN, this is a great solution. You can also use URL forwarding services to load up a customized link to OBS.Ninja, with URL parameters already included, such as https://invite.mypersonaldomain.com , which might secretly resolve to https://obs.ninja/?room=myRoom&hash=3423&label or such.
For those wanting a private TURN server setup, you can load the settings for those via the URL parameters. If infrequently needing a private TURN, this is a great solution. You can also use URL forwarding services to load up a customized link to VDO.Ninja, with URL parameters already included, such as https://invite.mypersonaldomain.com , which might secretly resolve to https://vdo.ninja/?room=myRoom&hash=3423&label or such.
OBS.Ninja also supports IFRAMES, so you can embed OBS.Ninja into your website and customize it via both URL parameters, but also via the IFRAME API. You can insert custom CSS styles with this method, giving OBS.Ninja quite a bit of flare.
VDO.Ninja also supports IFRAMES, so you can embed VDO.Ninja into your website and customize it via both URL parameters, but also via the IFRAME API. You can insert custom CSS styles with this method, giving VDO.Ninja quite a bit of flare.
See more on IFRAMES here: https://github.com/steveseguin/obsninja/blob/master/IFRAME.md
See more on IFRAMES here: https://github.com/steveseguin/vdo.ninja/blob/master/IFRAME.md
Understanding clearly why you need to deploy any code or server is important. Maintaining updated deployed code can be quite hard, as OBS.Ninja updates frequently, so there are good reasons to consider an IFRAME approach instead. Feature requests there are welcomed.
Understanding clearly why you need to deploy any code or server is important. Maintaining updated deployed code can be quite hard, as VDO.Ninja updates frequently, so there are good reasons to consider an IFRAME approach instead. Feature requests there are welcomed.
That all aside, please continue:
@@ -39,7 +38,7 @@ That all aside, please continue:
There's a community-created video tutorial on setting up here; https://youtu.be/8sDMwBIlgwE Otherwise, read on.
I use Cloudflare with Flexible SSL enabled and HTTP Rewrites. If you do not use Cloudflare, you will need to deploy SSL certificates onto your website. You will also have to have Cloudflare or whatever DNS provider you have, point your domain name to the IP address of your webserver. OBS.Ninja REQUIRES a domain name and SSL.
I use Cloudflare with Flexible SSL enabled and HTTP Rewrites. If you do not use Cloudflare, you will need to deploy SSL certificates onto your website. You will also have to have Cloudflare or whatever DNS provider you have, point your domain name to the IP address of your webserver. VDO.Ninja REQUIRES a domain name and SSL.
For webservers, I use NGINX on a Ubuntu server; smaller the better. I rely on Cloudflare to provide caching and SSL, so my installation of NGINX is pretty simple.
```
@@ -55,46 +54,52 @@ An example NGINX config file that "hides" the file extensions is as follows. Up
listen 80;
listen [::]:80;
server_name obs.ninja;
server_name vdo.ninja;
root /var/www/html/obs.ninja;
root /var/www/html/vdo.ninja;
index index.html;
location ~ ^/([^/]+)/([^/?]+)$ {
root /var/www/html/obs.ninja;
root /var/www/html/vdo.ninja;
try_files /$1/$2 /$1/$2.html /$1/$2/ /$2 /$2/ /$1/index.html;
add_header Access-Control-Allow-Origin *;
add_header 'cross-origin-resource-policy' '*';
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
}
location / {
if ($request_uri ~ ^/(.*)\.html$) {
return 302 /$1;
}
try_files $uri $uri.html $uri/ /index.html;
add_header Access-Control-Allow-Origin *;
if ($request_uri ~ ^/(.*)\.html$) {
return 302 /$1;
}
add_header 'cross-origin-resource-policy' '*';
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
try_files $uri $uri.html $uri/ /index.html;
add_header Access-Control-Allow-Origin *;
}
}
```
You'll want to deploy (copy/clone) the GitHub OBS.Ninja files into your NGINX web folder, that is specified in your NGINX config file. Update the NGINX config file to match your domain and and folder, etc. Restart NGINX after.
You'll want to deploy (copy/clone) the GitHub VDO.Ninja files into your NGINX web folder, that is specified in your NGINX config file. Update the NGINX config file to match your domain and and folder, etc. Restart NGINX after.
As for the TURN server, it can run on a single or dual-core computer. It doesn't take much to host many users -- it mainly just needs a good internet connection. Most users will not need a TURN server, but since OBS.Ninja handles many different types of users, the TURN server is there as a failsafe for those occasional problem users. I'm assuming you know why you need and want a TURN server -- if not, you may not actually need one.
As for the TURN server, it can run on a single or dual-core computer. It doesn't take much to host many users -- it mainly just needs a good internet connection. Most users will not need a TURN server, but since VDO.Ninja handles many different types of users, the TURN server is there as a failsafe for those occasional problem users. I'm assuming you know why you need and want a TURN server -- if not, you may not actually need one.
A guide and sample config file for the turn server is here:
https://github.com/steveseguin/obsninja/blob/master/turnserver.md
https://github.com/steveseguin/vdo.ninja/blob/master/turnserver.md
If deploying to GCP or AWS, you might need to make some tweaks to the IP address values to include the internet local IP as well as the external. Please see online guides no setting up a TURN server for your particular setup. Setups will vary.
Once you have your TURN server setup, you can update the index.html of the OBS.Ninja code. Nightly or official releases should be fine to pull. You probably will want to uncomment the lines linked below once deployed, adjusting the default values to your liking and updating the server location address and credentials of your TURN server (if you deployed one that is). Unless your TURN server also provides STUN capabilities, you may want to also use the Google STUN servers, so uncomment that stuff too.
Once you have your TURN server setup, you can update the index.html of the VDO.Ninja code. Nightly or official releases should be fine to pull. You probably will want to uncomment the lines linked below once deployed, adjusting the default values to your liking and updating the server location address and credentials of your TURN server (if you deployed one that is). Unless your TURN server also provides STUN capabilities, you may want to also use the Google STUN servers, so uncomment that stuff too.
https://github.com/steveseguin/obsninja/blob/df6c147311b9e7d19659ddbb1799d6598f59aa0d/index.html#L644
https://github.com/steveseguin/vdo.ninja/blob/df6c147311b9e7d19659ddbb1799d6598f59aa0d/index.html#L644
A newly deployed code deployment should work without any changes to the index.html file. The code needs to be constantly kept up to date though, as after a few months it may become deprecated and stop working. This is the reality of deploying OBS.Ninja -- you will need to update it every few months for it to continue to function well. Keep this in mind when making changes to the OBS.Ninja source code, as heavy custom changes will make updating harder to do. The fewer the changes the better.
A newly deployed code deployment should work without any changes to the index.html file. The code needs to be constantly kept up to date though, as after a few months it may become deprecated and stop working. This is the reality of deploying VDO.Ninja -- you will need to update it every few months for it to continue to function well. Keep this in mind when making changes to the VDO.Ninja source code, as heavy custom changes will make updating harder to do. The fewer the changes the better.
My suggestion? Limit changes to images and perhaps the translation files (maybe add a new one); these are good starting points. If making changes to the main.css style sheet or index.html file, you should be mostly okay too, since these files are designed to be changed; I try to keep that in mind when updating the code at least. Making changes to other files though is strongly not recommend and in some cases discouraged. If you find a bug or need to make a change to other files, it might be best to make a Pull Request with the desired changes and hope it gets adopted into the main codebase.
Final note: I haven't provided here instructions on deploying STUN services or a private handshake server; the OBS.Ninja handshake server code is currently not provided, but access to it as a service is freely accessible for private deployments.
Final note: I haven't provided here instructions on deploying STUN services or a private handshake server; the VDO.Ninja handshake server code is currently not provided, but access to it as a service is freely accessible for private deployments.
Regards,
Steve

336
main.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long