mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-14 15:18:28 +00:00
merge with master
This commit is contained in:
commit
b14ebeffb9
@ -20,6 +20,8 @@ Adding that iframe to the DOM will reveal a simple page accessing for a user to
|
||||
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
|
||||
|
||||
More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/obsninja/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
|
||||
|
||||
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.
|
||||
|
||||
@ -26,7 +26,7 @@ MacOS users will face some challenges in using OBS 25/26, but there are workarou
|
||||
This repo contains software for OBS.Ninja, including the HTML landing page for its Electron Capture app offering. A sample config file and instructions for setting up a TURN server (video relay server), is also provided. You may also find the Wiki for the project in this repo, which contains added information on how to use the software.
|
||||
|
||||
## How to Deploy this Repo:
|
||||
To use, just download and host the files on a HTTPS-enabled webserver. You may want to hide the .html extensions within your HTTP server as well, else the generated links will not work. See [here](https://github.com/steveseguin/obsninja/blob/master/install.md) for added details, although I don't really recommend it.
|
||||
To use, just download and host the files on a HTTPS-enabled webserver. You may want to hide the .html extensions within your HTTP server as well, else the generated links will not work. See [here](https://github.com/steveseguin/obsninja/blob/master/install.md) for added details and alternative install options.
|
||||
|
||||
Directions on how to deploy a TURN server are listed in the turnserver.md file. You may wish to do so, although not all use cases will not need one. Only about 10% of remote guests, those often connected via 4G LTE, will require a TURN server. While OBS.Ninja does host some TURN servers, they are quite expensive to operate and not really for private deployment use. If you are deploying your own version of OBS.Ninja, I'd ask you use your own TURN servers instead.
|
||||
|
||||
|
||||
264
examples/iframe.outbound-stats.html
Normal file
264
examples/iframe.outbound-stats.html
Normal file
@ -0,0 +1,264 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>OBS.Ninja IFRAME Outgoing Stats Example</title>
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./images/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./images/favicon-16x16.png" />
|
||||
<link rel="icon" href="./images/favicon.ico" />
|
||||
<link itemprop="thumbnailUrl" href="./images/obsNinja_logo_full.png" />
|
||||
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: rgb(20, 25, 38);
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
margin: 2px auto;
|
||||
padding: 0;
|
||||
display: block;
|
||||
margin: 10px;
|
||||
width: 640px;
|
||||
height: 320px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row controls" style="margin-bottom:15px;border-bottom:1px solid black;">
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control" style="width:95%;margin:10px auto;" placeholder="Enter an OBS.Ninja View URL here" value="" id="viewlink" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="row">
|
||||
<div class="col-2"></div>
|
||||
<div class="col-10">
|
||||
<button type="button" class="btn btn-primary" style="margin:10px 0;width:calc(90% + 15px);margin-left:5px;" id="btnStart">Start</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row output">
|
||||
<div class="col-7" id="source">
|
||||
<iframe style="margin:0 auto;" allow="autoplay;camera;microphone" src=""></iframe>
|
||||
</div>
|
||||
<div class="col-5" id="sourcecontrols">
|
||||
<div class="row text-light" style="margin-top:15px;">
|
||||
<div class="col">
|
||||
<p>This example will show all connections to the stream generated from this page using statistics gathered using the <a href="https://github.com/steveseguin/obsninja/blob/master/IFRAME.md">iFrame API</a>.</p>
|
||||
<p>Click start to generate a stream using the OBS.Ninja URL shown. If you use the example URL shown, you can <a id="aView" href="" target="_blank">click here</a> to connect to this stream as a viewer in a new window/tab, this will then show in the table below. Expired connections will be removed after a short delay.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top:5px;">
|
||||
<div style="padding-top:10px;" class="col-4 text-right font-weight-bold text-light">Audio:</div>
|
||||
<div class="col-8">
|
||||
<button type="button" class="btn btn-sm btn-secondary" style="width:45%;" id="btnMuteAudio">Disable</button>
|
||||
<button type="button" class="btn btn-sm btn-success" style="width:45%;" id="btnUnMuteAudio">Enabled</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="padding-top:10px;" class="col-4 text-right font-weight-bold text-light">Video:</div>
|
||||
<div class="col-8">
|
||||
<button type="button" class="btn btn-sm btn-secondary" style="width:45%;" id="btnMuteVidio">Disable</button>
|
||||
<button type="button" class="btn btn-sm btn-success" style="width:45%;" id="btnUnMuteVidio">Enabled</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="padding-top:10px;" class="col-4 text-right font-weight-bold text-light">Stats:</div>
|
||||
<div class="col-8">
|
||||
<button type="button" class="btn btn-sm btn-secondary" style="width:45%;" id="btnStatsAuto">Auto Refresh Off</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" style="width:45%;" id="btnStatsRefresh">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="padding-top:5px;" class="col-6 text-right font-weight-bold text-light">Outbound Connections:</div>
|
||||
<div style="padding-top:5px;" class="col-6 font-weight-bold text-light" id="divTotalConnections">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table id="viewers" style="margin-top:15px;" class="table table-hover text-center table-striped table-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="align-middle">Label</th>
|
||||
<th scope="col" class="align-middle">Added</th>
|
||||
<th scope="col" class="align-middle">Quality Limit Reason</th>
|
||||
<th scope="col" class="align-middle">Resolution</th>
|
||||
<th scope="col" class="align-middle">Platform</th>
|
||||
<th scope="col" class="align-middle">Encoder</th>
|
||||
<th scope="col" class="align-middle">User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
var autorefresh = false;
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
eventer(messageEvent, function(e) {
|
||||
//Check message is coming from our iframe, otherwise we don't care
|
||||
if (e.source != $('#source iframe')[0].contentWindow) return;
|
||||
|
||||
if ("stats" in e.data) {
|
||||
var now = new Date(); //Used for "Added" column and to remove stale viewers
|
||||
for (var viewer in e.data.stats.outbound_stats) {
|
||||
//Check to see if a row exists for this viewier, if not then its a new viewer and we should create a row
|
||||
if ($("#obsn_viewer_" + viewer).length == 0) {
|
||||
var h = now.getHours();
|
||||
var m = now.getMinutes();
|
||||
var s = now.getSeconds();
|
||||
$('#viewers tbody').append('<tr id="obsn_viewer_' + viewer + '"><th class="obsn_viewer_label" scope="row"></th><td class="obsn_viewer_added">' + ("0" + h).slice(-2) + ':' + ("0" + m).slice(-2) + ':' + ("0" + s).slice(-2) + '</td><td class="obsn_viewer_qlr"></td><td class="obsn_viewer_resolution"></td><td class="obsn_viewer_platform"></td><td class="obsn_viewer_encoder"></td><td class="obsn_viewer_useragent"></td></tr>');
|
||||
}
|
||||
//Insert/update stats
|
||||
//Initially objects can be available but without any attributes, check they exist and ignore till the basics are available
|
||||
if (e.data.stats.outbound_stats[viewer] == undefined) continue;
|
||||
if (e.data.stats.outbound_stats[viewer].info == undefined) continue;
|
||||
//Checking these exist as not all attributes are available straight away when stats are created
|
||||
if (e.data.stats.outbound_stats[viewer].info.label != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_label').text(e.data.stats.outbound_stats[viewer].info.label);
|
||||
}
|
||||
if (e.data.stats.outbound_stats[viewer].quality_Limitation_Reason != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_qlr').text(e.data.stats.outbound_stats[viewer].quality_Limitation_Reason);
|
||||
}
|
||||
if (e.data.stats.outbound_stats[viewer].resolution != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_resolution').text(e.data.stats.outbound_stats[viewer].resolution);
|
||||
}
|
||||
if (e.data.stats.outbound_stats[viewer].info.platform != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_platform').text(e.data.stats.outbound_stats[viewer].info.platform);
|
||||
}
|
||||
if (e.data.stats.outbound_stats[viewer].encoder != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_encoder').text(e.data.stats.outbound_stats[viewer].encoder);
|
||||
}
|
||||
if (e.data.stats.outbound_stats[viewer].info.useragent != undefined) {
|
||||
$("#obsn_viewer_" + viewer).find('.obsn_viewer_useragent').text(e.data.stats.outbound_stats[viewer].info.useragent);
|
||||
}
|
||||
$("#obsn_viewer_" + viewer).data('last', now.getTime()); //Used below to remove old viewers
|
||||
}
|
||||
//Mark and then remove viewers who have not been seen for a while
|
||||
$('#viewers tbody tr').each(function(el) {
|
||||
if (parseInt($(this).data('last')) < (now.getTime() - 10000)) { //10 seconds
|
||||
$(this).remove();
|
||||
} else if (parseInt($(this).data('last')) < (now.getTime())) { //Mark viewer in red to show they have disappeared, note that it takes a few seconds for this to happen
|
||||
$(this).addClass('bg-danger');
|
||||
} else { //Viewer is there, make sure they're not marked as missing
|
||||
$(this).removeClass('bg-danger');
|
||||
}
|
||||
});
|
||||
$('#divTotalConnections').text(e.data.stats.total_outbound_connections);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#btnMuteAudio').on('click', function() {
|
||||
$(this).addClass('btn-success').removeClass('btn-secondary').text('Disabled');
|
||||
$('#btnUnMuteAudio').removeClass('btn-success').addClass('btn-secondary').text('Enable');
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"mic": false
|
||||
}, '*');
|
||||
});
|
||||
$('#btnUnMuteAudio').on('click', function() {
|
||||
$(this).addClass('btn-success').removeClass('btn-secondary').text('Enabled');
|
||||
$('#btnMuteAudio').removeClass('btn-success').addClass('btn-secondary').text('Disable');
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"mic": true
|
||||
}, '*');
|
||||
});
|
||||
$('#btnMuteVidio').on('click', function() {
|
||||
$(this).addClass('btn-success').removeClass('btn-secondary').text('Disabled');
|
||||
$('#btnUnMuteVidio').removeClass('btn-success').addClass('btn-secondary').text('Enable');
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"camera": false
|
||||
}, '*');
|
||||
});
|
||||
$('#btnUnMuteVidio').on('click', function() {
|
||||
$(this).addClass('btn-success').removeClass('btn-secondary').text('Enabled');
|
||||
$('#btnMuteVidio').removeClass('btn-success').addClass('btn-secondary').text('Disable');
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"camera": true
|
||||
}, '*');
|
||||
});
|
||||
|
||||
$('#btnStatsAuto').on('click', function() {
|
||||
if (autorefresh) {
|
||||
autorefresh = false;
|
||||
$('#btnStatsAuto').removeClass('btn-success').addClass('btn-secondary').text('Auto Refresh Off');
|
||||
} else {
|
||||
autorefresh = true;
|
||||
$('#btnStatsAuto').addClass('btn-success').removeClass('btn-secondary').text('Auto Refresh On');
|
||||
}
|
||||
});
|
||||
$('#btnStatsRefresh').on('click', function() {
|
||||
$(this).addClass('btn-success').removeClass('btn-secondary').attr('disabled', true);
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"getStats": true
|
||||
}, '*');
|
||||
setTimeout(function() {
|
||||
$('#btnStatsRefresh').addClass('btn-secondary').removeClass('btn-success').attr('disabled', false);
|
||||
}, 700);
|
||||
});
|
||||
|
||||
$('#btnStart').on('click', function() {
|
||||
//Reset buttons as currently we can't check the state of these properties
|
||||
$('#btnMuteAudio,#btnMuteVidio').removeClass('btn-success').addClass('btn-secondary').text('Disable');
|
||||
$('#btnUnMuteAudio,#btnUnMuteVidio').addClass('btn-success').removeClass('btn-secondary').text('Enabled');
|
||||
//Update the iframe source from the input field, yup, that simple
|
||||
$('#source iframe').attr('src', $('#viewlink').val());
|
||||
//Start autorefresh of stats
|
||||
autorefresh = true;
|
||||
$('#btnStatsAuto').addClass('btn-success').removeClass('btn-secondary').text('Auto Refresh On');
|
||||
});
|
||||
//Start checking for stats
|
||||
setInterval(function() {
|
||||
if (autorefresh == false) return;
|
||||
$('#source iframe')[0].contentWindow.postMessage({
|
||||
"getStats": true
|
||||
}, '*');
|
||||
}, 1000);
|
||||
//Add in random ID and password strings to URL's, the below is purely for the purposes of this example
|
||||
var pushid = makeid();
|
||||
var password = makeid();
|
||||
var baseUrl = "https://obs.ninja/";
|
||||
$('#aView').attr('href', baseUrl + '?view=' + pushid + '&password=' + password + '&label=Test_Link');
|
||||
$('#viewlink').val(baseUrl + '?push=' + pushid + '&password=' + password + '&autostart&turn=false&fps=25&maxbitrate=1000&cleanoutput&audiobitrate=32&aec=0&denoise=0&webcam');
|
||||
});
|
||||
//This function is purely used to generate random push id and password strings for the purposes of this example
|
||||
function makeid() {
|
||||
var result = '';
|
||||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
var charactersLength = characters.length;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -10,7 +10,7 @@ Those reasons are: wanting custom branding; contributing as a developer to the
|
||||
|
||||
For a subset of those users even, they might still have better options available.
|
||||
|
||||
I offer https://rtc.ninja for those looking for a brand-free experience already. 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.
|
||||
I offer https://rtc.ninja for those looking for a brand-free experience already. 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.)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user