60 Commits
16.3 ... 16.4

Author SHA1 Message Date
Steve Seguin
6b56b5ea33 Merge pull request #775 from steveseguin/v16-beta
V16.4 Release
2021-03-03 01:34:20 -05:00
Steve Seguin
41e1b4dc02 v16.4 update 2021-03-03 01:32:41 -05:00
Steve Seguin
a22388a24b Add files via upload 2021-03-01 04:15:39 -05:00
Steve Seguin
10ef9193b0 Merge pull request #762 from steveseguin/master
sync jcalado changes with my own
2021-02-26 21:41:11 -05:00
Steve Seguin
554af144f2 Add files via upload 2021-02-26 21:38:58 -05:00
Steve Seguin
36ae6c0f1e Update install.md 2021-02-25 01:29:31 -05:00
Steve Seguin
b949986182 Merge pull request #745 from jcalado/alert-modal
alert modal
2021-02-24 05:37:12 -05:00
Steve Seguin
b36b52ad8b Merge pull request #746 from steveseguin/v16-beta
V16 Release
2021-02-24 05:34:55 -05:00
Steve Seguin
de4f981455 v16.3 2021-02-24 05:33:44 -05:00
Joel Calado
eae305913f alert modal 2021-02-24 08:45:46 +00:00
Steve Seguin
14c13b02de Merge pull request #742 from jcalado/offline-notification
"no network connection" alert
2021-02-23 17:54:12 -05:00
Joel Calado
5bb7bbdd5c respect cleanOutput
if cleanOutput is set don't alert. Log to console instead.
2021-02-23 22:52:55 +00:00
Joel Calado
ea1895c129 "no network connection" alert 2021-02-23 22:34:59 +00:00
Steve Seguin
80295a7a9a Merge pull request #741 from TychoWerner/patch-1
Updated a couple of translations for the Dutch language
2021-02-23 16:21:42 -05:00
TychoWerner
ec1ec458aa More changes :) 2021-02-23 22:18:03 +01:00
TychoWerner
6b6e2b5a3f Updated a couple of translations for the Dutch language 2021-02-23 22:05:30 +01:00
Steve Seguin
a67e585eb1 v16.3 preflight 2021-02-22 17:01:19 -05:00
Steve Seguin
c6690fcdac Merge pull request #723 from filiptronicek/master
Typo
2021-02-17 22:39:12 -05:00
Steve Seguin
1f2c193956 Merge pull request #724 from theprincy/patch-7
Update it.json
2021-02-17 22:38:29 -05:00
Notelseit.com
1ba4d61486 Update it.json 2021-02-17 21:24:45 +01:00
Filip Troníček
e1f7ccacfe Fix seconary -> secondary 2021-02-17 09:55:35 +00:00
Steve Seguin
e366bdfa4c Merge pull request #722 from steveseguin/master
beginnings of v16.3; merging master.
2021-02-16 19:07:30 -05:00
Steve Seguin
62292e71ea Add files via upload 2021-02-16 19:04:01 -05:00
Steve Seguin
06379c1f1f Add files via upload 2021-02-16 19:03:51 -05:00
Steve Seguin
b471096842 Update install.md 2021-02-10 12:16:42 -05:00
Steve Seguin
85b336bafd Update README.md 2021-02-10 12:11:43 -05:00
Steve Seguin
fae0b094ac Update README.md 2021-02-10 10:54:52 -05:00
Joel Calado
a028a753e8 Merge pull request #714 from filiptronicek/patch-1
Add Wiki links to the README
2021-02-10 09:50:53 +00:00
Filip Troníček
a24e5ae667 Add Wiki links to the README
Just update the README with more hyperlinks. It's more convenient
2021-02-10 10:43:57 +01:00
Joel Calado
0f7b09526d Merge pull request #711 from steveseguin/fix_devices
Fix /devices multiple video inputs bug
2021-02-08 21:23:23 +00:00
Joel Calado
f335813c5e Update devices.html
fixes a bug where you could select multiple video devices

changes from device-id strings to sanitized device names
2021-02-08 21:16:14 +00:00
Joel Calado
f62f31329d Update devices.html
sanitize device names function
2021-02-08 21:15:17 +00:00
Joel Calado
cbc7971fa5 Update devices.html 2021-02-08 21:14:47 +00:00
Joel Calado
838acf07ac Update devices.html
change text
2021-02-08 21:14:41 +00:00
Joel Calado
864969ebae Update devices.html
add some tips to the top notice
2021-02-08 21:14:21 +00:00
Joel Calado
474e142908 tweak /devices CSS 2021-02-08 21:13:51 +00:00
Joel Calado
e5a7b29a46 Merge pull request #707 from filiptronicek/devices-refactor
Devices refactor
2021-02-08 17:58:19 +00:00
Filip Troníček
9cc109c254 Update target because of the arrow function 2021-02-08 17:21:40 +01:00
Filip Troníček
8338573c10 Template string output 2021-02-08 17:19:28 +01:00
Filip Troníček
46b00b1ce1 First ES6 refactor
- Eqeqeq
- const and let
- arrow functions
2021-02-08 17:14:27 +01:00
Steve Seguin
d2dd662d10 Merge pull request #703 from filiptronicek/patch-1
Add hyperlink to turnserver
2021-02-08 11:07:25 -05:00
Steve Seguin
c89fcc0064 Merge pull request #704 from filiptronicek/patch-2
Add comments to the commands
2021-02-08 11:07:13 -05:00
Steve Seguin
8d24e23232 Merge pull request #705 from filiptronicek/patch-3
JS syntax highlighting
2021-02-08 11:07:00 -05:00
Steve Seguin
474155e0d9 Merge pull request #706 from filiptronicek/patch-4
Update link style in License
2021-02-08 11:06:16 -05:00
Steve Seguin
166f933953 Merge pull request #694 from duncanbarnes/master
Feature: &rooms
2021-02-08 11:05:33 -05:00
Filip Troníček
43aa080bce Update link style in License
The license contains a link, which links to the raw file with no markdown features at all, I think if it is markdown, it should be displayed as such.
2021-02-08 16:52:28 +01:00
Filip Troníček
4e273219f8 Format the JS and comment a plaintext comment 2021-02-08 15:57:02 +01:00
Filip Troníček
1c3d45ae6f Const -> let because of re-declaration issues 2021-02-08 15:55:26 +01:00
Filip Troníček
c7a801c0e0 Template strings 2021-02-08 15:52:27 +01:00
Filip Troníček
8c4c1cd040 Arrow functions as onclick handlers 2021-02-08 15:52:15 +01:00
Filip Troníček
c487bed906 Invalid assignment to const 2021-02-08 15:45:46 +01:00
Filip Troníček
e762910907 Eqeqeq 2021-02-08 15:44:40 +01:00
Filip Troníček
f0cfa1effd Format IFRAME.md 2021-02-08 15:44:04 +01:00
Filip Troníček
8ee5be2960 Var -> const 2021-02-08 15:43:13 +01:00
Filip Troníček
1934097875 JS syntax highlighting
- add syntax highlighting in IFRAME.md
- update syntax to ES6
2021-02-08 15:40:48 +01:00
Filip Troníček
4e922c13aa Fix the incorrect placement of UDP and TCP 2021-02-08 10:55:27 +01:00
Filip Troníček
17403d816b Add comments to the commands
Add some explanations for the commands so that the user is better aware of what he is currently running.
2021-02-08 10:38:08 +01:00
Filip Troníček
944e619f0a Add hyperlink to turnserver 2021-02-08 10:12:30 +01:00
Duncan Barnes
d6d4d93dab Feature: &rooms
Permits a list of rooms to be defined for room transfer operations. Buttons are added to the control bar, when selected and a transfer button is pushed the user will be transfered straight to that room rather than the room name having to be entered.
2021-02-06 17:32:31 +00:00
Steve Seguin
5c7648d86a Create turnserver3.conf 2021-02-04 11:11:46 -05:00
21 changed files with 1773 additions and 656 deletions

456
IFRAME.md
View File

@@ -1,217 +1,303 @@
## OBS.Ninja - iFrame API documentation
## OBS.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.
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.
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 OBS.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.
Creating an OBSN iframe can be done in HTML or programmatically with Javascript like so:
var iframe = document.createElement("iframe");
iframe.allow="autoplay;camera;microphone";
iframe.allowtransparency="false"
iframe.src = "https://obs.ninja/?webcam";
```js
const iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone";
iframe.allowtransparency = "false";
iframe.src = "https://obs.ninja/?webcam";
```
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.
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
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
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.
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.
For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/obsninja/wiki/Advanced-Settings
Some of the more interesting ones primarily for iframe users might include:
- &webcam
- &screenshare
- &videodevice=1 or 0
- &audiodevice=1 or 0
- &autostart
- &chroma
- &transparency
-
As for API, allow for dynamic messaging, below are examples of the options available:
- &webcam
- &screenshare
- &videodevice=1 or 0
- &audiodevice=1 or 0
- &autostart
- &chroma
- &transparency
- As for API, allow for dynamic messaging, below are examples of the options available:
- Mute Speaker
- Mute Mic
- Disconnect
- Change Video Bitrate
- Reload the page
- Change the volume
- Request detailed connection stats
- Access the loudness level of the audio
- Send/Recieve a chat message to other connected guests
- Get notified when there is a video connection
- Mute Speaker
- Mute Mic
- Disconnect
- Change Video Bitrate
- Reload the page
- Change the volume
- Request detailed connection stats
- Access the loudness level of the audio
- 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:
```js
let button = document.createElement("button");
button.innerHTML = "Mute Speaker";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mute": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Un-Mute Speaker";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mute": false
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Toggle Speaker";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mute": "toggle"
}, '*');
}
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Mute Mic";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mic": false
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Un-Mute Mic";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mic": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Toggle Mic";
button.onclick = () => {
iframe.contentWindow.postMessage({
"mic": "toggle"
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Disconnect";
button.onclick = () => {
iframe.contentWindow.postMessage({
"close": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Low Bitrate";
button.onclick = () => {
iframe.contentWindow.postMessage({
"bitrate": 30
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "High Bitrate";
button.onclick = () => {
iframe.contentWindow.postMessage({
"bitrate": 5000
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Default Bitrate";
button.onclick = () => {
iframe.contentWindow.postMessage({
"bitrate": -1
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Reload";
button.onclick = () => {
iframe.contentWindow.postMessage({
"reload": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "50% Volume";
button.onclick = () => {
iframe.contentWindow.postMessage({
"volume": 0.5
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "100% Volume";
button.onclick = () => {
iframe.contentWindow.postMessage({
"volume": 1.0
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Request Stats";
button.onclick = () => {
iframe.contentWindow.postMessage({
"getStats": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Request Loudness Levels";
button.onclick = () => {
iframe.contentWindow.postMessage({
"getLoudness": true
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Stop Sending Loudness Levels";
button.onclick = () => {
iframe.contentWindow.postMessage({
"getLoudness": false
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "Say Hello";
button.onclick = () => {
iframe.contentWindow.postMessage({
"sendChat": "Hello!"
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "previewWebcam()";
button.onclick = () => {
iframe.contentWindow.postMessage({
"function": "previewWebcam"
}, '*');
};
iframeContainer.appendChild(button);
button = document.createElement("button");
button.innerHTML = "CLOSE IFRAME";
button.onclick = () => {
iframeContainer.parentNode.removeChild(iframeContainer);
};
iframeContainer.appendChild(button);
// As for listening events, where the parent listens for responses or events from the OBSN child frame:
// ////////// LISTEN FOR EVENTS
const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
const eventer = window[eventMethod];
const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) {
if (e.source !== iframe.contentWindow) {
return
} // reject messages send from other iframes
if ("stats" in e.data) {
const outputWindow = document.createElement("div");
let out = `<br />total_inbound_connections:${
e.data.stats.total_inbound_connections
}`;
out += `<br />total_outbound_connections:${
e.data.stats.total_outbound_connections
}`;
for (const 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) {
const outputWindow = document.createElement("div");
outputWindow.innerHTML = e.data.gotChat.msg;
outputWindow.style.border = "1px dotted black";
iframeContainer.appendChild(outputWindow);
}
if ("action" in e.data) {
const outputWindow = document.createElement("div");
outputWindow.innerHTML = `child-page-action: ${
e.data.action
}<br />`;
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 {
const outputWindow = document.createElement("div");
outputWindow.style.border = "1px dotted black";
iframeContainer.appendChild(outputWindow);
outputWindow.id = "loudness";
}
outputWindow.innerHTML = "child-page-action: loudness<br />";
for (const key in e.data.loudness) {
outputWindow.innerHTML += `${key} Loudness: ${
e.data.loudness[key]
}\n`;
}
outputWindow.style.border = "1px black";
}
});
```
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:
var button = document.createElement("button");
button.innerHTML = "Mute Speaker";
button.onclick = function(){iframe.contentWindow.postMessage({"mute":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Un-Mute Speaker";
button.onclick = function(){iframe.contentWindow.postMessage({"mute":false}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Toggle Speaker";
button.onclick = function(){iframe.contentWindow.postMessage({"mute":"toggle"}, '*');}
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Mute Mic";
button.onclick = function(){iframe.contentWindow.postMessage({"mic":false}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Un-Mute Mic";
button.onclick = function(){iframe.contentWindow.postMessage({"mic":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Toggle Mic";
button.onclick = function(){iframe.contentWindow.postMessage({"mic":"toggle"}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Disconnect";
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Low Bitrate";
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "High Bitrate";
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":5000}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Default Bitrate";
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Reload";
button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "50% Volume";
button.onclick = function(){iframe.contentWindow.postMessage({"volume":0.5}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "100% Volume";
button.onclick = function(){iframe.contentWindow.postMessage({"volume":1.0}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Request Stats";
button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Request Loudness Levels";
button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Stop Sending Loudness Levels";
button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Say Hello";
button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "previewWebcam()";
button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "CLOSE IFRAME";
button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);};
iframeContainer.appendChild(button);
As for listening events, where the parent listens for responses or events from the OBSN child frame:
//////////// 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 ("stats" in e.data){
var outputWindow = document.createElement("div");
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);
}
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";
}
});
This OBS.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.
-steve

View File

@@ -1,5 +1,5 @@
The OBS.Ninja source repository is governed by the GNU AFFERO GENERAL PUBLIC LICENSE. (AGPL-3.0)
That AGPL-3.0 licence can be found here: https://raw.githubusercontent.com/steveseguin/obsninja/master/AGPLv3.md
That AGPL-3.0 licence can be found here: [AGPLv3.md](https://github.com/steveseguin/obsninja/blob/master/AGPLv3.md)
In essence, OBS.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.

View File

@@ -20,10 +20,10 @@ And Here is another video series touching on some more advanced settings: https:
Check the subreddit for added use cases, advanced features, and support. Advanced features includes high-quality audio modes, custom video resolutions, and more.
MacOS users will face some challenges in using OBS 25/26, but there are workarounds. Please see the subreddit or the Wiki.
MacOS users will face some challenges in using OBS 25/26, but there are workarounds. Please see the subreddit or [the Wiki](https://github.com/steveseguin/obsninja/wiki).
## What's in this repo?
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.
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](https://github.com/steveseguin/obsninja/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 and alternative install options.
@@ -33,7 +33,7 @@ Directions on how to deploy a TURN server are listed in the turnserver.md file.
## Server side / API software?
Since OBS.Ninja uses peer-2-peer technology, video connections are made directly between viewer and publisher in 90% of cases. Hosting a TURN server yourself may help improve performance, but less than 1% of users will see any benefit of this. Details on how to deploy a TURN server are provided. For those capable of hosting their own TURN server, that would be appreciated if possible, as TURN servers are the only real cost incurred by OBS.Ninja at present. (other than time, of course)
Other than TURN servers, OBS.Ninja also uses public STUN servers and a hosted handshake server. These are used to facilitate the initial setup of peer connections and are generally not required after a peer connection is established. These servers are free to access and use, even for private deployments.
Other than TURN servers, OBS.Ninja also uses public STUN servers and a hosted handshake server. These are used to facilitate the initial setup of peer connections and are generally not required after a peer connection is established. These servers are free to access and use, even for private deployments. The handshake server's code is currently not available, so basic access to the Internet is still required to use OBS.Ninja even with a private deployment.
Development builds of OBS.Ninja may include debugging software, but in-production releases have this removed. Double check to ensure "console.re" debugging is disabled before deployment, just to be safe.
@@ -63,7 +63,7 @@ https://steves.app/
A browser-based studio solution and simplified alternative to OBS, with built-in OBS.Ninja functionality. It is a server-based approach to group interactions and live production. Steve Seguin is affiliated with StageTEn, yet StageTEN is not affiliated with OBS.Ninja.
## Privacy
I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: turnserver.md
I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: [turnserver.md](turnserver.md)
https://obs.ninja may unavoidably use cookies that are exempt from EU laws of requiring notice of their use; they are exempt as they are required and necessary for the technical functioning of the web service. Our webserver is cached by Cloudflare and it provides denial of server protection for the users of OBS.Ninja.

View File

@@ -9,6 +9,7 @@ h1 {
padding:10px;
background-color:#457b9d;
color:white;
border-bottom: 2px solid #3b6a87;
}
.device {
@@ -18,6 +19,9 @@ h1 {
font-size: 1rem;
padding: 10px;
position: relative;
user-select: none;
background: #d0d0d0;
border-radius: 4px;
}
.device.selected {
@@ -55,12 +59,13 @@ h1 {
}
.notice {
background-color: orange;
background-color: #fff18c;
margin: 10px;
padding: 20px 20px;
font-weight: bold;
font-size: 1.2em;
text-align: center;
line-height: 1.4em;
}
.notice a {
@@ -89,7 +94,7 @@ h1 {
left: 10%;
color: white;
overflow-wrap: anywhere;
background: #141926;
background: #2c3754;
padding: 20px;
box-shadow: 0px 0px 10px 5px #00000047;
border: 1px solid #333c52;

View File

@@ -22,7 +22,8 @@
<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.
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>.
@@ -41,51 +42,56 @@
</div>
</div>
<div id="sharedDevices" style="display: none">
<span>Click to copy. Send this to your host:</span>
<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>
var list = [];
var url = new URL(document.location.origin);
var audioInputDevices = [];
const list = [];
const url = new URL(document.location.origin);
const audioInputDevices = [];
function isAudioInput(value) {
return value.kind == "audioinput";
return value.kind === "audioinput";
}
function isAudioOutput(value) {
return value.kind == "audiooutput";
return value.kind === "audiooutput";
}
function isVideoInput(value) {
return value.kind == "videoinput";
return value.kind === "videoinput";
}
function sanitizeDeviceName(deviceName) {
return String(deviceName).toLowerCase().replace(/[\W]+/g, "_");
}
function addDevice(element) {
var info = element.getElementsByClassName("device-id")[0];
var type = info.dataset.deviceType;
const type = element.dataset.deviceType;
const device = sanitizeDeviceName(element.querySelector('span').innerText);
if (type == "audioinput") {
setAudioSearchParams(info);
if (type === "audioinput") {
setAudioSearchParams(element);
}
if (type == "videoinput") {
setVideoSearchParams(info);
if (type === "videoinput") {
setVideoSearchParams(element);
}
if (type == "audiooutput") {
if (type === "audiooutput") {
return;
}
/*
Allows for multiple audio devices to be selected
Will be output as a comma separated string to &ad
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.parentNode.className == "device selected") {
if (info.className === "device selected") {
// Remove device from list of selected devices
var index = audioInputDevices.indexOf(info.innerText);
const index = audioInputDevices.indexOf(device);
if (index !== -1) {
audioInputDevices.splice(index, 1);
}
@@ -95,12 +101,13 @@
element.className = "device";
// If no audio devices remained, just remove the param completely
if (audioInputDevices.length == 0) {
if (audioInputDevices.length === 0) {
url.searchParams.delete("ad");
}
} else {
// Device is unselected
audioInputDevices.push(info.innerText);
audioInputDevices.push(device);
url.searchParams.set("ad", audioInputDevices.join(","));
element.className = "device selected";
}
@@ -111,20 +118,27 @@
*/
function setVideoSearchParams(info) {
// Device was already selected
if (info.parentNode.className == "device selected") {
info.parentNode.className = "device";
if (info.className === "device selected") {
element.className = "device";
// Set the url param to the devices that are left
url.searchParams.set("vd", info.innerText);
url.searchParams.set("vd", device);
element.className = "device";
// If no devices remained, just remove the param completely
if (audioInputDevices.length == 0) {
if (audioInputDevices.length === 0) {
url.searchParams.delete("vd");
}
} else {
// Device is unselected
url.searchParams.set("vd", info.innerText);
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";
}
}
@@ -139,40 +153,35 @@
}
function prettyPrint(json, element) {
var output = "<div class='prettyJson two-col'>";
var nestedObjs;
let output = "<div class='prettyJson two-col'>";
let nestedObjs;
Object.entries(json)
.sort()
.forEach(([key, value]) => {
output += "<div class='device' onclick='addDevice(this)'>";
output +=
"<span class='device-name'>" +
value.label +
"</span><span class='device-id' data-device-type='" +
value.kind +
"'>" +
value.deviceId +
"</span>";
output += "</div>";
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 = function () {
this.select();
document.getElementById("devicesUrl").onclick = (e) => {
e.target.select();
document.execCommand("copy");
};
navigator.mediaDevices
.enumerateDevices()
.then(function (devices) {
devices.forEach(function (device) {
.then((devices) => {
devices.forEach((device) => {
console.log(
device.kind + ": " + device.label + " id = " + device.deviceId
`${device.kind}: ${device.label} id = ${device.deviceId}`
);
list.push(device);
});
@@ -180,8 +189,8 @@
prettyPrint(devices.filter(isAudioOutput), "audioOutputs");
prettyPrint(devices.filter(isVideoInput), "videoInputs");
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
.catch((err) => {
console.log(`${err.name}: ${err.message}`);
});
</script>
</body>

View File

@@ -177,6 +177,16 @@ function loadIframe(){ // this is pretty important if you want to avoid camera
button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Start Recording";
button.onclick = function(){iframe.contentWindow.postMessage({"record":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Stop Recording";
button.onclick = function(){iframe.contentWindow.postMessage({"record":false}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Say Hello";
button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');};

View File

@@ -55,7 +55,7 @@
}
</style>
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
<link rel="stylesheet" href="./main.css?ver=46" />
<link rel="stylesheet" href="./main.css?ver=52" />
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/adapter.min.js"></script>
</head>
<body id="main" class="hidden">
@@ -66,19 +66,22 @@
<span itemprop="thumbnail" itemscope itemtype="http://schema.org/ImageObject">
<link itemprop="url" href="./media/obsNinja_logo_full.png" />
</span>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=26"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=177"></script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=29"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=181"></script>
<input id="zoomSlider" type="range" style="display: none;" />
<div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px;">
<span data-translate="logo-header">
<font id="qos">O</font>BS.Ninja
</span>
</a>
<div id="head1" style="display: inline-block; padding:1px; position: relative;">
<input type="text" autocorrect="off" autocapitalize="none" id="joinroomID" name="joinroomID" size="22" placeholder="Join by Room Name here" alt="Enter a room name to join" title="Enter a room name to quick join" onkeyup="jumptoroom(event)"/>
<button onclick="jumptoroom();" role="button" aria-pressed="false" alt="Join room" title="Join room" >GO</button>
</div>
<div id="head5" class="advanced"></div>
<div id="head3" style="display: inline-block;" class="advanced">
<font style="color: #888;" id="copythisurl"> &nbsp;
<span data-translate="copy-this-url">Copy this URL into an OBS "Browser Source"</span> <i style="color: #CCC;" class="las la-long-arrow-alt-right"></i> &nbsp;
@@ -103,7 +106,6 @@
</div>
<div id="head2" class="advanced" style="display: inline-block; text-decoration: none; font-size: 60%; color: white;">
<span data-translate="joining-room">You are in room</span>:
<div id="roomid" style="display: inline-block;"></div>
</div>
@@ -125,12 +127,12 @@
<i id="mutetoggle" class="toggleSize las la-microphone my-float" style="position: relative; top: 0.5px;"></i>
</div>
<div id="mutevideobutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Disable the Camera" alt="Disable the Camera" onclick="toggleVideoMute()" tabindex="19" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="advanced float" style="cursor: pointer;">
<i id="mutevideotoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-eye my-float"></i>
<i id="mutevideotoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-video my-float"></i>
</div>
<div id="screensharebutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Share a Screen with others" alt="Share a Screen with others" onclick="toggleScreenShare()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
<i id="screensharetoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-desktop my-float"></i>
</div>
<div id="screenshare2button" onmousedown="event.preventDefault(); event.stopPropagation();" title="Create a Seconary Stream" alt="Create a Seconary Stream" onclick="createIframePopup()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
<div id="screenshare2button" onmousedown="event.preventDefault(); event.stopPropagation();" title="Create a Secondary Stream" alt="Create a Secondary Stream" onclick="createIframePopup()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
<i id="screenshare2toggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-tv my-float"></i>
</div>
<div id="settingsbutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Settings" onclick="toggleSettings()" class="advanced float" tabindex="21" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Toggle the Settings Menu">
@@ -146,6 +148,7 @@
<i class="toggleSize my-float las la-dot-circle" style="position: relative;" aria-hidden="true"></i>
</div>
<span id="miniPerformer" style="pointer-events: auto;" class="advanced"></span>
<span id="rooms" style="padding-top:3px;padding-left:6px;pointer-events: auto;color:#fff;"></span>
<div id="hangupbutton2" onmousedown="event.preventDefault(); event.stopPropagation();" title="Cancel the Director's Video/Audio" onclick="hangup2()" class="advanced float" tabindex="25" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Disconnect Direcotor's cam">
<i class="toggleSize my-float las la-phone rotate225" aria-hidden="true"></i>
@@ -164,7 +167,7 @@
<span
id="helpbutton"
title="Show Help Info"
onclick="alert('Email steve@seguin.email if the system breaks or check https://reddit.com/r/obsninja for support.\n\nThe Wiki contains many help guides and advanced settings.\n\nAccess the debug menu by pressing CTRL (command) and Left-Clicking on a video.\n\nMost 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 Wiki also contains many help guides and advanced settings, located at https://wiki.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.')"
style="cursor: pointer; display:none;"
alt="How to Use This with OBS"
>
@@ -355,6 +358,25 @@
<select id="outputSource" ></select>
</span>
<span id="addPasswordBasic" style="width: 450px;
white-space: nowrap;
overflow: hidden;
border-bottom: 0;
display: inline-block;
text-align: left;
margin: 17px 0;
max-width: 550px;
min-width: 420px;
background-color: #f3f3f3;
padding: 10px 10px;
border: 1px solid #ccc;
vertical-align: middle;">
<i class="las la-key"></i><span data-translate="add-a-password"> Add a Password:</span>
<input type="text" id="passwordBasicInput" placeholder="optional" style="border: solid 1px #AAA;
padding: 4px 6px;
width: 200px;
margin: 0 6px;"/>
</span>
</div>
<div class="outer close">
@@ -562,7 +584,8 @@
<input id="fileselector" onchange="session.publishFile(this,event);" type="file" accept="video/*,audio/*" alt="Hold CTRL (or CMD) to select multiple files" title="Hold CTRL (or CMD) to select multiple files" multiple/>
<br /><br />
<p style="margin:10px">Keep this tab visible if using Chrome, else the video playback will stop</p>
<p style="margin:10px">(Media file streaming is still quite experimental)</p>
<p style="margin:10px">(Media file streaming is still quite experimental)</p><br />
<p style="margin:10px">File Sharing seems to be broken on Chrome v88. <br />Using The Electron Capture app instead of Chrome should work: <a href="https://github.com/steveseguin/electroncapture/releases/tag/1.1.3" style="color:blue"><u>GET IT HERE</u></a><br />You can also <a href="https://github.com/aws/amazon-chime-sdk-js/issues/1031" style="color:blue">turn off hardware-accleration</a> in Chrome/Edge to fix the issue.</p>
</div>
<div class="outer close">
@@ -586,9 +609,9 @@
<button onclick="previewIframe(getById('iframeURL').value);" >Preview</button>
<button onclick="this.innerHTML = 'Update'; session.publishIFrame(getById('iframeURL').value);" >Share</button><br />
<small class="iframeblob">
<li>Remote website must be CORS/IFrame compatible with full SSL-encryption enabled.</li>
<li>Not all websites will work with this feature and many will actively not allow embedding like this.</li>
<li>If sharing a Youtube video, try using the embeddable link version and ensure that the video is set to allow embedding.</li>
<li>Not all websites will work with this feature as some sites disallow embedding.</li>
<li>The site will try to auto-optimize standard Youtube or Twitch links.</li>
<li>Remote websites must be CORS/IFrame compatible with full SSL-encryption enabled.</li>
</small>
<div id="iFramePreview" style=" width: 1280px; height: 720px; margin: auto; padding: 10px;"></div>
</div>
@@ -602,7 +625,7 @@
</div>
<div id="container-7" class="column columnfade pointer card advanced" style="overflow: hidden;" onclick="window.location = './speedtest';">
<div id="container-7" class="column columnfade pointer card advanced" style="overflow: hidden;" onclick="window.location = './speedtest.html';">
<h2><span data-translate="run-a-speed-test">Run a Speed Test</span></h2>
<i style="margin-top:30px;font-size:600%;overflow:hidden;" class="las la-tachometer-alt"></i>
</div>
@@ -619,52 +642,37 @@
<span data-translate="info-blob">
<h2>What is OBS.Ninja</h2>
<br />
<li>100%
<b>free</b>; no downloads; no personal data collection; no sign-in
</li>
<li>Bring video from your smartphone, computer, or friends directly into your OBS video stream</li>
<li>100% <b>free</b>; no downloads; no personal data collection; no sign-in</li>
<li>Bring live video from your smartphone, remote computer, or friends directly into OBS or other studio software.</li>
<li>We use cutting edge Peer-to-Peer forwarding technology that offers privacy and ultra-low latency</li>
<br />
<li>Youtube video
<i class="lab la-youtube"></i>
<a href="https://www.youtube.com/watch?v=vLpRzMjUDaE&list=PLWodc2tCfAH1WHjl4WAOOoRSscJ8CHACe&index=2" alt="Youtube video demoing OBS.Ninja">Demoing it here</a>
</li>
<br />
<i>
<font style="color: red;">Known issues:</font>
</i>
<br />
<li>
MacOS users using OBS will need to update to <a href="https://github.com/obsproject/obs-studio/releases/tag/26.1.2">OBS Studio 26.1.2</a> or resort to
window-capturing with the provided <a href="https://github.com/steveseguin/electroncapture">Electron-Capture app</a>.
</li>
<li>If you have <a href="https://github.com/steveseguin/obsninja/wiki/FAQ#video-is-pixelated">"pixel smearing"</a> or corrupted video, try adding <b>&codec=vp9</b> or &codec=h264 to the OBS view link. Using Wi-Fi will make the issue worse.
</li>
<li>
iOS devices may have occasional audio or camera issues, such as no sound or distorted sound. <a href="https://bugs.webkit.org/show_bug.cgi?id=218762">Partially fixed in iOS 14.3</a>
If you have <a href="https://github.com/steveseguin/obsninja/wiki/FAQ#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>
Chrome on Android 11 has an issue with the browser freezing at times. To unfreeze it, background the browser and then foreground it again.
</li>
<li>
The VP9 codec on Chromium-based browsers seems to lag when screen-sharing at the moment. Use the OBS Virtual Camera as a capture source instead.
</li>
<br />
🥳 Site Updated: <a href="https://github.com/steveseguin/obsninja/wiki/v16-release-notes">Fed 3rd, 2021</a>. The previous version can be found at
<a href="https://obs.ninja/v15/">https://obs.ninja/v15/</a> if you are having new issues.
Site Updated: <a href="https://github.com/steveseguin/obsninja/wiki/v16.4-update-notes">March 3rd, 2021</a> (v16.4). The previous version can be found at <a href="https://obs.ninja/v16/">https://obs.ninja/v16/</a> if you are having issues with this minor update.
<br />
<br />
<h3>
<i>
Check out the
<a href="https://www.reddit.com/r/OBSNinja/">sub-reddit
<i class="lab la-reddit-alien"></i> </a>for help and see the <a href="https://github.com/steveseguin/obsninja/wiki/">Wiki for advanced info</a>. I'm also on
<a href="https://discord.gg/T4xpQVv">Discord <i class="lab la-discord"></i></a> or email me at steve@seguin.email
🛠 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://github.com/steveseguin/obsninja/wiki/">Wiki is here</a> and my personal email is <i>steve@seguin.email</i>
</h3>
</i>
</h3>
</span>
</div>
</center>
@@ -781,13 +789,13 @@
<input type="checkbox" data-param="&l" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Ask for Display Name
Ask for display name
<Br />
<label class="switch" title="Display Names will be shown in the bottom-left corner of videos">
<input type="checkbox" data-param="&sl" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Show Display Names
Show display names
</div>
<div style="display:inline-block;top: 12px; position: relative; margin-left:10px;">
<label class="switch" title="Request 1080p60 from the Guest instead of 720p60, if possible">
@@ -800,13 +808,13 @@
<input type="checkbox" data-param="&ad" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Auto-select Default Microphone
Auto-select default microphone
<Br />
<label class="switch" title="The default camera device will selected automatically">
<input type="checkbox" data-param="&vd" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Auto-select Default Camera
Auto-select default camera
<Br />
<label class="switch" title="The guest won't have access to changing camera settings or screenshare">
<input type="checkbox" data-param="&ns" onchange="updateLink(1,this);">
@@ -819,50 +827,50 @@
<input type="checkbox" data-param="&np" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Disable Self-Preview
Disable self-preview
<Br />
<label class="switch" title="Guests will have an option to poke the Director by pressing a button">
<input type="checkbox" data-param="&hand" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Display 'Raise-Hand' button
Display 'raise-hand' button
<Br />
<label class="switch" title="Add an audio compressor to the guest's microphone">
<input type="checkbox" data-param="&comp" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Enable Audio Compressor
Enable audio compressor
<Br />
<label class="switch" title="Add an Equalizer to the guest's microphone that the director can control">
<input type="checkbox" data-param="&eq" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Enable Equalizer as Option
Enable equalizer as option
</div>
<div style="display:inline-block;top: 12px; position: relative; margin-left:10px; height: 20px;">
<label class="switch" title="The guest can only see the Director's video, if provided">
<input type="checkbox" data-param="&broadcast" id="broadcastSlider" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Only see the Director's Feed
Only see the director's feed
<br />
<label class="switch" title="The guest's microphone will be muted on joining. They can unmute themselves.">
<input type="checkbox" data-param="&m" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Mute Microphone by Default
Mute microphone by default
<Br />
<label class="switch" title="Have the guest join muted, so only the director can Unmute the guest.">
<input type="checkbox" data-param="&g=0" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Unmute by Director Only
Unmute by director only
<Br />
<label class="switch" title="Make the invite URL encoded, so parameters are harder to tinker with by guests">
<input type="checkbox" data-param="" id="obfuscate_director_1" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
Obfuscate Link and Parameters
Obfuscate link and parameters
</div>
</div>
@@ -885,7 +893,7 @@
<input type="checkbox" data-param="&sl" onchange="updateLink(3,this);">
<span class="slider"></span>
</label>
Show Display Names
Show display names
</div>
<div style="display:inline-block;top: 12px; height: 20px; position: relative; margin-left:10px;">
<label class="switch">
@@ -899,7 +907,7 @@
<input type="checkbox" data-param="&mono" onchange="updateLink(3,this);">
<span class="slider"></span>
</label>
Force Mono Audio
Force mono audio
</div>
</div>
@@ -932,18 +940,42 @@
<span data-translate="send-direct-chat"><i class="las la-envelope"></i> Message</span>
</button>
<button data-action-type="addToScene" style="grid-column: 1;" data-value="0" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event);">
<button data-action-type="addToScene" style="grid-column: 1;" data-value="0" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event, 1);">
<i class="las la-plus-square"></i>
<span data-translate="add-to-scene">Add to Scene</span>
<span data-translate="add-to-scene">add to scene</span>
</button>
<button data-action-type="mute-scene" style="grid-column: 2;" title="Remotely Mute this Audio in all remote '&scene' views" onclick="directMute(this, event);">
<i class="las la-microphone-slash"></i>
<span data-translate="mute-scene" >mute in scene</span>
</button>
<font class="tooltip" style="height: 0; border: 0;">
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest" oninput="remoteVolumeUI(this)" onclick="remoteVolume(this);" style="grid-column: 1; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/><span class="tooltiptext" style='float: right; overflow: auto; left: 40px; width: 2.5em; top: -13px; margin: 0; position:relative;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;' >100</span></font>
<span id="sceneGroup1" style="display:none">
<button style="width: 35.2px" data-action-type="add-scene-2" title="Add to Scene 2" onclick="directEnable(this, event, 2);">
<span >S2</span>
</button>
<button style="width:35.2px;" data-action-type="add-scene-3" title="Add to Scene 3" onclick="directEnable(this, event, 3);">
<span >S3</span>
</button>
<button style="width: 35.2px" data-action-type="add-scene-4" title="Add to Scene 4" onclick="directEnable(this, event, 4);">
<span >S4</span>
</button>
</span>
<font class="tooltip" style="height: 0; border: 0;">
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest" oninput="remoteVolumeUI(this)" onclick="remoteVolume(this);" style="grid-column: 2; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/><span class="tooltiptext" style='float: right; overflow: auto; left: 40px; width: 2.5em; top: -13px; margin: 0; position:relative;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;' >100</span>
</font>
<span id="sceneGroup2" style="display:none">
<button style="width: 35.2px" data-action-type="add-scene-5" title="Add to Scene 5" onclick="directEnable(this, event, 5);">
<span >S5</span>
</button>
<button style="width: 35.2px" data-action-type="add-scene-6" title="Add to Scene 6" onclick="directEnable(this, event, 6);">
<span >S6</span>
</button>
<button style="width: 35.2px" data-action-type="add-scene-7" title="Add to Scene 7" onclick="directEnable(this, event, 7);">
<span >S7</span>
</button>
</span>
<button data-action-type="mute-guest" style="grid-column: 2;" title="Mute this guest everywhere" onclick="remoteMute(this, event);">
<i class="las la-microphone-slash"></i>
<span data-translate="mute-guest" >mute guest</span>
@@ -991,13 +1023,56 @@
</button>
</span>
<button data-action-type="toggle-remote-speaker" style="grid-column: 1;" title="Toggle the remote guest's speaker output" onclick="remoteSpeakerMute(this, event);">
<span id="channelGroup1" style="display:none">
<button style="width: 35.2px" data-action-type="add-channel" class="pressed" title="Set to Default Audio Channel" onclick="changeChannelOffset(this.dataset.UUID, false);">
<span >00</span>
</button>
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 1" onclick="changeChannelOffset(this.dataset.UUID, 0);">
<span >C1</span>
</button>
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 2" onclick="changeChannelOffset(this.dataset.UUID, 1);">
<span >C2</span>
</button>
</span>
<button data-action-type="toggle-remote-speaker" title="Toggle the remote guest's speaker output" onclick="remoteSpeakerMute(this, event);">
<i class="las la-volume-off"></i> <span data-translate="toggle-remote-speaker">Deafen Guest</span>
</button>
<span id="channelGroup2" style="display:none" >
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 3" onclick="changeChannelOffset(this.dataset.UUID, 2);">
<span >C3</span>
</button>
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 4" onclick="changeChannelOffset(this.dataset.UUID,3);">
<span >C4</span>
</button>
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 5" onclick="changeChannelOffset(this.dataset.UUID, 4);">
<span >C5</span>
</button>
</span>
<button data-action-type="toggle-remote-display" style="grid-column: 2;" title="Toggle the remote guest's display output" onclick="remoteDisplayMute(this, event);">
<i class="las la-eye-slash"></i> <span data-translate="toggle-remote-display">Blind Guest</span>
</button>
<span id="channelGroup3" style="display:none" >
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 6" onclick="changeChannelOffset(this.dataset.UUID, 5);">
<span >C6</span>
</button>
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 7" onclick="changeChannelOffset(this.dataset.UUID, 6);">
<span >C7</span>
</button>
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 8" onclick="changeChannelOffset(this.dataset.UUID, 7);">
<span >C8</span>
</button>
</span>
<button data-action-type="force-keyframe" data-value="0" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="requestKeyframeScene(this, event);">
<i class="las la-first-aid"></i>
<span data-translate="force-keyframe">Rainbow Puke</span>
</button>
<button class="" data-action-type="advanced-audio-settings" data-active="false" style="grid-column: 1;" title="Remote Audio Settings" onclick="requestAudioSettings(this);">
<span data-translate="advanced-audio-settings"><i class="las la-sliders-h"></i> Audio Settings</span>
</button>
@@ -1005,7 +1080,6 @@
<span data-translate="advanced-camera-settings"><i class="las la-sliders-h"></i> Video Settings</span>
</button>
</div>
</div>
@@ -1157,7 +1231,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 = "16.2";
session.version = "16.4";
session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed
session.defaultPassword = "someEncryptionKey123"; // Disabling improves compatibility and is helpful for debugging.
@@ -1222,7 +1296,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=163"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=174"></script>
<script type="text/javascript">
setTimeout(function(){ // lazy load
var script = document.createElement('script');

View File

@@ -37,6 +37,8 @@ That all aside, please continue:
### SETUP
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.
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.
@@ -92,5 +94,7 @@ A newly deployed code deployment should work without any changes to the index.ht
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.
Regards,
Steve

183
main.css
View File

@@ -139,6 +139,14 @@ button.grey {
background-size: 50%;
}
#miniPerformer>#previewWebcam{
width: 80px;
height: 45px;
margin: 5px;
background-color: #464749 !important;
background-size: 50%;
}
#reportbutton{
visibility: hidden;
}
@@ -196,6 +204,15 @@ button.white:active {
background-color: #0F131D;
color: #FFF;
}
#head5 {
display: inline-block;
text-decoration: none;
color: white;
text-align: right;
margin-right: 10px;
pointer-events: none;
float: right;
}
#overlayMsgs{
margin:0 auto;
@@ -486,9 +503,9 @@ button.glyphicon-button.active.focus {
#container.vidcon {
height:100%;
}
@media only screen and (max-width: 700px){
@media only screen and (max-width: 640px){
#controlButtons {
transform: scale(0.8) translateY(20%);
transform: scale(0.9) translateY(10%);
}
}
@media only screen and (max-width: 400px){
@@ -501,17 +518,41 @@ button.glyphicon-button.active.focus {
padding: 0px;
}
}
/* Node selector to prioritise this selector above .float */
button.btnArmTransferRoom{
width:auto;
margin-left: 2px;
height:38px;
border-radius: 15px;
}
button.btnArmTransferRoom i{
margin-right:3px;
}
button.btnArmTransferRoom:hover{
background-color: var(--green-accent);
}
button.btnArmTransferRoom.selected{
background-color: #840000;
}
@media only screen and (max-height: 540px){
#subControlButtons {
transform: scale(0.90);
transform: scale(0.88);
}
#gridlayout>#container.vidcon {
height:90%
height:88%
}
#controlButtons {
height:54px;
}
}
@media only screen and (max-height: 500px){
#subControlButtons {
transform: scale(0.87);
}
#gridlayout>#container.vidcon {
height:87%
}
#controlButtons {
height:54px;
@@ -521,7 +562,19 @@ button.glyphicon-button.active.focus {
#subControlButtons {
transform: scale(0.85);
}
#header{
#logoname{
display:none;
}
#head1{
display:none;
}
#head4{
display:none;
}
#head5{
display:none;
}
#head2{
display:none;
}
#gridlayout>#container.vidcon {
@@ -531,6 +584,20 @@ button.glyphicon-button.active.focus {
height:50px;
}
}
@media only screen and (max-height: 300px){
#gridlayout>#container.vidcon {
height:81%
}
#subControlButtons {
transform: scale(0.81);
}
#controlButtons {
height:46.2px;
}
#head2 {
display:none !important;
}
}
@media only screen and (max-height: 240px){
#gridlayout>#container.vidcon {
height:78%
@@ -576,6 +643,9 @@ button.glyphicon-button.active.focus {
}
}
#header:empty{
display:none;
}
@keyframes pulse {
0% {
@@ -936,6 +1006,18 @@ input[type=range]:focus::-ms-fill-upper {
}
}
@media only screen and (max-height: 355px) {
#popupSelector {
padding: 0 !important;
}
}
@media only screen and (max-height: 330px) {
#popupSelector {
padding: 0 !important;
font-size: 92%;
}
}
.popupSelector_constraints{
margin:30px 9% 0 7%;
}
@@ -1065,6 +1147,7 @@ label {
/* padding: 30px; */
border-radius: 30px;
cursor:pointer;
color: #636363
}
.fullcolumn {
float: left;
@@ -1810,10 +1893,10 @@ input[type=checkbox] {
font-size: 0.8rem;
list-style-type: none;
left: 50px;
top: 50px;
top: 0px;
width: 300px;
min-height: 200px;
max-height: 90vh;
max-height: 99vh;
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.95);
position: absolute;
@@ -2141,42 +2224,27 @@ span#guestTips {
color: white;
padding: 5px 10px;
background: rgba(0, 0, 0, .5);
pointer-events:none;
font-size: 1em;
pointer-events:none;
}
.video-label.zoom {
position: absolute;
bottom: 0;
left: 0;
margin: 0px;
color: white;
padding: 5px 10px;
background: rgba(0, 0, 0, .5);
pointer-events:none;
}
.video-label.teams {
position: absolute;
bottom: 0.6vh;
left: 0.5vh;
margin: 0px;
color: white;
padding: 5px 10px;
background: rgba(0, 0, 0, .4);
pointer-events:none;
border-radius: 5px;
}
.video-label.skype {
position: absolute;
bottom: 2vh;
left: 50%;
transform: translateX(-50%);
margin: 0px;
color: white;
padding: 5px 10px;
background: rgba(0, 0, 0, .8);
pointer-events:none;
border-radius: 5px;
@@ -2184,7 +2252,6 @@ span#guestTips {
}
.video-label.ninjablue {
position: absolute;
bottom: 5%;
left: 0;
background: #141926;
@@ -2192,7 +2259,6 @@ span#guestTips {
}
.video-label.toprounded {
position: absolute;
top: 0;
bottom: unset;
background: rgb(0 0 0 / 70%);
@@ -2210,11 +2276,10 @@ span#guestTips {
}
.video-label.fire {
color: #FFFFFF;
text-shadow: 0 -1px 4px #FFF, 0 -2px 10px #ff0, 0 -10px 20px #ff8000, 0 -18px 40px #F00;
font-weight: bold;
position: absolute;
bottom: 2vh;
left: 0;
width: 100%;
text-align: center;
}
@@ -2224,22 +2289,25 @@ span#guestTips {
display:block;
width:0.5vh;
height:0.5vh;
min-width:10px;
min-height:10px;
top: 2vh;
right: 2vh;
background-color:green;
position:absolute;
display:none;
border-radius: 1vh;
border-radius: 2vh;
pointer-events:none;
}
.video-mute-state {
top: 0.5em;
right: 0.5em;
top: 2vh;
right: 2vh;
position: absolute;
color:white;
border-radius: 1vh;
border-radius: 2vh;
background-color:#b11313;
padding: 2px 2px 2px 1px;
}
#help_directors_room{
@@ -2341,4 +2409,51 @@ input:checked + .slider:before {
-webkit-transform: translateX(16px);
-ms-transform: translateX(16px);
transform: translateX(16px);
}
.alertModal {
position: absolute;
background-color: rgb(221 221 221);
box-shadow: 0 0 30px 10px #0000005c;
color: black;
font-size: 1.2em;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
font-weight: bold;
z-index:2;
width:400px;
max-width:90%;
}
.alertModalInner {
position: relative;
padding: 2em;
}
.alertModalClose {
position: absolute;
top: -4px;
right: 4px;
cursor: pointer;
font-weight: bolder;
font-size: 1.8em;
}
.alertModalBackdrop {
background: var(--background-color);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
opacity: 0.8;
}
@media only screen and (max-width: 390px) {
.alertModal {
width: 90%;
}
}

1032
main.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="64"><style> .a{stroke-width:10;stroke:#000;}</style><title> background</title><rect height="66" width="67" y="-1" x="-1" fill="#0000"/><g height="100" width="100"><rect y="28.56" x="29.5" height="600" width="800" fill="url(#gridpattern)"/></g><title> Layer 1</title><rect height="3" width="1" y="27.02" x="302" style="fill:#0000;stroke-width:2;stroke:#0000"/><rect height="43" width="48" y="10.8" x="8.38" style="fill-opacity:null;fill:#0000;stroke-opacity:null;stroke-width:2;stroke:#FFF"/>
<text font-family="Helvetica, Arial, sans-serif" font-size="24" y="40.49" x="15.38" style="fill:#FFF;font-weight:bold"> HQ</text></svg>
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="white" x="0px" y="0px" viewBox="0 0 122.88 122.87" style="enable-background:new 0 0 122.88 122.87" xml:space="preserve"><g><path d="M122.88,77.63v41.12c0,2.28-1.85,4.12-4.12,4.12H77.33v-9.62h35.95c0-12.34,0-23.27,0-35.62H122.88L122.88,77.63z M77.39,9.53V0h41.37c2.28,0,4.12,1.85,4.12,4.12v41.18h-9.63V9.53H77.39L77.39,9.53z M9.63,45.24H0V4.12C0,1.85,1.85,0,4.12,0h41 v9.64H9.63V45.24L9.63,45.24z M45.07,113.27v9.6H4.12c-2.28,0-4.12-1.85-4.12-4.13V77.57h9.63v35.71H45.07L45.07,113.27z"/></g></svg>

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -1,2 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="64"><style> .a{stroke-width:10;stroke:#000;}</style><title> background</title><rect height="66" width="67" y="-1" x="-1" fill="#0000"/><g height="100" width="100"><rect y="28.56" x="29.5" height="600" width="800" fill="url(#gridpattern)"/></g><title> Layer 1</title><rect height="3" width="1" y="27.02" x="302" style="fill:#0000;stroke-width:5;stroke:#0000"/><rect height="43" width="48" y="10.8" x="8.38" style="fill-opacity:null;fill:#0000;stroke-opacity:null;stroke-width:5;stroke:#FFF"/>
<text font-family="Helvetica, Arial, sans-serif" font-size="24" y="40.49" x="15.38" style="fill:#FFF;font-weight:bold"> LQ</text></svg>
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="white" width="122.88px" height="122.871px" viewBox="0 0 122.88 122.871" enable-background="new 0 0 122.88 122.871" xml:space="preserve"><g><path d="M122.88,35.775v9.529H81.515c-2.278,0-4.125-1.847-4.125-4.125V0h9.63v35.775H122.88L122.88,35.775z M35.499,0h9.63v41.118 c0,2.278-1.847,4.125-4.125,4.125H0v-9.644h35.499V0L35.499,0z M0,87.164v-9.598h40.942c2.277,0,4.125,1.846,4.125,4.125v41.18 h-9.633V87.164H0L0,87.164z M77.328,122.871V81.752c0-2.277,1.847-4.125,4.125-4.125h41.427v9.625H86.931 c0,12.338-0.003,23.271-0.003,35.619H77.328L77.328,122.871z"/></g></svg>

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 733 B

2
media/svg.md Normal file
View File

@@ -0,0 +1,2 @@
https://uxwing.com/collapse-icon/
https://uxwing.com/expand-icon/

View File

@@ -6,6 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OBSN Speed Test</title>
<script>
function getChromeVersion() {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
}
if (!getChromeVersion()){
alert("This speedtest is optimized for Chromium-based browsers; graphs will not work for Firefox or Safari browsers.");
}
(function (w) {
w.URLSearchParams =
w.URLSearchParams ||
@@ -251,10 +260,7 @@
];
updateData("buffer", buffer);
var packetloss =
e.data.stats.inbound_stats[streamID][key][
"packetLoss_in_percentage"
];
var packetloss = e.data.stats.inbound_stats[streamID][key]["packetLoss_in_percentage"];
if (packetloss != undefined) {
packetloss = packetloss.toFixed(2);
updateData("packetloss", packetloss);

View File

@@ -31,17 +31,13 @@ var CodecsHandler = (function() {
if (!info.videoCodecNumbers) {
return sdp;
}
if (codecName === 'vp8' && info.vp8LineNumber === info.videoCodecNumbers[0]) {
} else if (codecName === 'vp8' && info.vp8LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
if (codecName === 'vp9' && info.vp9LineNumber === info.videoCodecNumbers[0]) {
} else if (codecName === 'vp9' && info.vp9LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
if (codecName === 'h264' && info.h264LineNumber === info.videoCodecNumbers[0]) {
} else if (codecName === 'h264' && info.h264LineNumber === info.videoCodecNumbers[0]) {
return sdp;
} else if (codecName === 'av1' && info.av1LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
@@ -58,21 +54,24 @@ var CodecsHandler = (function() {
return sdp;
}
preferCodecNumber = info.vp8LineNumber;
}
if (codec === 'vp9') {
} else if (codec === 'vp9') {
if (!info.vp9LineNumber) {
return sdp;
}
preferCodecNumber = info.vp9LineNumber;
}
if (codec === 'h264') {
} else if (codec === 'h264') {
if (!info.h264LineNumber) {
return sdp;
}
preferCodecNumber = info.h264LineNumber;
} else if (codec === 'av1') {
if (!info.av1LineNumber) {
return sdp;
}
preferCodecNumber = info.av1LineNumber;
}
var newLine = info.videoCodecNumbersOriginal.split('SAVPF')[0] + 'SAVPF ';
@@ -117,6 +116,10 @@ var CodecsHandler = (function() {
if (line.indexOf('H264/90000') !== -1 && !info.h264LineNumber) {
info.h264LineNumber = line.replace('a=rtpmap:', '').split(' ')[0];
}
if (line.indexOf('AV1X/90000') !== -1 && !info.av1LineNumber) {
info.av1LineNumber = line.replace('a=rtpmap:', '').split(' ')[0];
}
});

View File

@@ -1,36 +1,36 @@
{
"titles": {
"toggle-the-chat": "Toggle the Chat",
"mute-the-speaker": "Mute the Speaker",
"mute-the-mic": "Mute the Mic",
"disable-the-camera": "Disable the Camera",
"settings": "Settings",
"hangup-the-call": "Hangup the Call",
"show-help-info": "Show Help Info",
"language-options": "Language Options",
"tip-hold-ctrl-command-to-select-multiple": "tip: Hold CTRL (command) to select Multiple",
"ideal-for-1080p60-gaming-if-your-computer-and-upload-are-up-for-it": "Ideal for 1080p60 gaming, if your computer and upload are up for it",
"better-video-compression-and-quality-at-the-cost-of-increased-cpu-encoding-load": "Better video compression and quality at the cost of increased CPU encoding load",
"disable-digital-audio-effects-and-increase-audio-bitrate": "Disable digital audio-effects and increase audio bitrate",
"the-guest-will-not-have-a-choice-over-audio-options": "The guest will not have a choice over audio-options",
"the-guest-will-only-be-able-to-select-their-webcam-as-an-option": "The guest will only be able to select their webcam as an option",
"hold-ctrl-and-the-mouse-wheel-to-zoom-in-and-out-remotely-of-compatible-video-streams": "Hold CTRL and the mouse wheel to zoom in and out remotely of compatible video streams",
"add-a-password-to-make-the-stream-inaccessible-to-those-without-the-password": "Add a password to make the stream inaccessible to those without the password",
"add-the-guest-to-a-group-chat-room-it-will-be-created-automatically-if-needed-": "Add the guest to a group-chat room; it will be created automatically if needed.",
"customize-the-room-settings-for-this-guest": "Customize the room settings for this guest",
"hold-ctrl-or-cmd-to-select-multiple-files": "Hold CTRL (or CMD) to select multiple files",
"enter-an-https-url": "Enter an HTTPS URL",
"toggle-the-chat": "Attiva o disattiva la chat",
"mute-the-speaker": "Muta lo Speaker",
"mute-the-mic": "Disattiva il microfono",
"disable-the-camera": "Disattiva la Camera",
"settings": "Impostazioni",
"hangup-the-call": "Riaggancia la chiamata",
"show-help-info": "Mostra Info Helo",
"language-options": "Opzioni di lingua",
"tip-hold-ctrl-command-to-select-multiple": "tip: Tieni premut CTRL (command) per selezionare multipla ",
"ideal-for-1080p60-gaming-if-your-computer-and-upload-are-up-for-it": "Ideale per il gioco a 1080p60 , se il tuo computer e upload lo supportano",
"better-video-compression-and-quality-at-the-cost-of-increased-cpu-encoding-load": "Migliore compressione e qualità video al costo di un maggiore carico di codifica della CPU ",
"disable-digital-audio-effects-and-increase-audio-bitrate": "Disattiva gli effetti audio digitali e aumenta il bitrate audio ",
"the-guest-will-not-have-a-choice-over-audio-options": "L'ospite non avrà scelta sulle opzioni audio",
"the-guest-will-only-be-able-to-select-their-webcam-as-an-option": "L'ospite potrà solo selezionare la propria webcam come opzione",
"hold-ctrl-and-the-mouse-wheel-to-zoom-in-and-out-remotely-of-compatible-video-streams": "Tieni premuto CTRL e la rotellina del mouse per ingrandire e ridurre in remoto i flussi video compatibili ",
"add-a-password-to-make-the-stream-inaccessible-to-those-without-the-password": "Aggiungi una password per rendere il flusso inaccessibile a chi non ha la password",
"add-the-guest-to-a-group-chat-room-it-will-be-created-automatically-if-needed-": "Aggiungere l'ospite a una chat room di gruppo; verrà creato automaticamente se necessario.",
"customize-the-room-settings-for-this-guest": "Personalizza le impostazioni della stanza per questo ospite ",
"hold-ctrl-or-cmd-to-select-multiple-files": "Tieni premuto CTRL (or CMD) per selezionare più file",
"enter-an-https-url": "Inserisci un URL HTTPS ",
"lucy-g": "Lucy G",
"flaticon": "Flaticon",
"creative-commons-by-3-0": "Creative Commons BY 3.0",
"gregor-cresnar": "Gregor Cresnar",
"add-this-video-to-any-remote-scene-1-": "Add this Video to any remote '&scene=1'",
"forward-user-to-another-room-they-can-always-return-": "Forward user to another room. They can always return.",
"start-recording-this-stream-experimental-views": "Start Recording this stream. *experimental*' views",
"force-the-user-to-disconnect-they-can-always-reconnect-": "Force the user to Disconnect. They can always reconnect.",
"change-this-audio-s-volume-in-all-remote-scene-views": "Change this Audio's volume in all remote '&scene' views",
"remotely-mute-this-audio-in-all-remote-scene-views": "Remotely Mute this Audio in all remote '&scene' views",
"disable-video-preview": "Disable Video Preview",
"add-this-video-to-any-remote-scene-1-": "Aggiungi questo video a una scena in remoto '&scene=1'",
"forward-user-to-another-room-they-can-always-return-": "Inoltra l'utente a un'altra stanza. Possono sempre tornare.",
"start-recording-this-stream-experimental-views": "Inizia a registrare questo flusso. visualizzazioni *sperimentale*'",
"force-the-user-to-disconnect-they-can-always-reconnect-": "Forza l'utente a disconnettersi. Possono sempre riconnettersi.",
"change-this-audio-s-volume-in-all-remote-scene-views": "Cambia il volume di questo audio in tutte le scene in remoto '&scene'",
"remotely-mute-this-audio-in-all-remote-scene-views": "Disattiva l'audio in remoto '&scene'",
"disable-video-preview": "Disattiva anteprima video",
"low-quality-preview": "Low-Quality Preview",
"high-quality-preview": "High-Quality Preview",
"send-direct-message": "Send Direct Message",
@@ -159,13 +159,13 @@
"add-more-here": "Aggiungi altro qui!",
"waiting-for-camera-to-load": "waiting-for-camera-to-load",
"start": "START",
"share-your-mic": "Share your microphone",
"share-your-camera": "Share your Camera",
"share-your-screen": "Share your Screen",
"join-room-with-mic": "Join room with Microphone",
"share-screen-with-room": "Share-screen with Room",
"join-room-with-camera": "Join room with Camera",
"click-start-to-join": "Click Start to Join",
"share-your-mic": "Condividi il tuo microfono",
"share-your-camera": "Condividi Camera",
"share-your-screen": "Condividi il tuo schermo",
"join-room-with-mic": "Entra nella stanza con il Microfono",
"share-screen-with-room": "Condivisione dello schermo con la stanza",
"join-room-with-camera": "Entra nella stanza con la Camera ",
"click-start-to-join": "Fare clic su Start per partecipare",
"guests-only-see-director": "Guests can only see the Director's Video",
"default-codec-select": "Preferred Video Codec: ",
"obfuscate_url": "Obfuscate the Invite URL",
@@ -189,4 +189,4 @@
"enter-room-name-here": "Enter Room name here",
"enter-chat-message-to-send-here": "Enter chat message to send here"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"titles": {
"toggle-the-chat": "Chat aan/uit",
"mute-the-speaker": "Demp de sSreker",
"mute-the-speaker": "Demp de Spreker",
"mute-the-mic": "Demp de Mic",
"disable-the-camera": "Camera uitzetten",
"settings": "Instellingen",
@@ -24,18 +24,18 @@
"flaticon": "Flaticon",
"creative-commons-by-3-0": "Creative Commons BY 3.0",
"gregor-cresnar": "Gregor Cresnar",
"add-this-video-to-any-remote-scene-1-": "Add this Video to any remote '&scene=1'",
"forward-user-to-another-room-they-can-always-return-": "Stuur gast door naar andere room, kan terugkeren.",
"start-recording-this-stream-experimental-views": "Stream opnemen starten. *experimenteel*' views",
"add-this-video-to-any-remote-scene-1-": "Voeg deze video toe aan iedereen met '&scene=1'",
"forward-user-to-another-room-they-can-always-return-": "Stuur gast door naar andere kamer,deze kan terugkeren.",
"start-recording-this-stream-experimental-views": "Stream opnemen starten. *experimenteel*",
"force-the-user-to-disconnect-they-can-always-reconnect-": "Forceer einde verbinding, kan wel opnieuw verbinden.",
"change-this-audio-s-volume-in-all-remote-scene-views": "Change this Audio's volume in all remote '&scene' views",
"remotely-mute-this-audio-in-all-remote-scene-views": "Remotely Mute this Audio in all remote '&scene' views",
"change-this-audio-s-volume-in-all-remote-scene-views": "Veranderd dit audio volume in alle '&scene' weergaven",
"remotely-mute-this-audio-in-all-remote-scene-views": "Op afstand dempen van deze audio op alle '&scene' weergavens",
"disable-video-preview": "Schakel Video Preview uit",
"low-quality-preview": "Lage kwaliteit Preview",
"high-quality-preview": "Hoge kwaliteit Preview",
"send-direct-message": "Stuur een Direct Message",
"advanced-settings-and-remote-control": "Geavanceerde instellingen en Remote Conntrol",
"toggle-voice-chat-with-this-guest": "Schakel Voice chat aan/uit met deze gast",
"send-direct-message": "Stuur een prive bericht",
"advanced-settings-and-remote-control": "Geavanceerde instellingen en Remote Control",
"toggle-voice-chat-with-this-guest": "Schakel geluids chat aan/uit met deze gast",
"join-by-room-name-here": "Voer een naam in voor snelle toegang",
"join-room": "Ga de kamer in",
"share-a-screen-with-others": "Deel je scherm met anderen",
@@ -50,7 +50,7 @@
"add-your-camera-to-obs": "Voeg je camera aan OBS toe",
"remote-screenshare-into-obs": "Schermdelen naar OBS",
"create-reusable-invite": "creëer herbruikbaare link",
"encode-the-url-so-that-it-s-harder-for-a-guest-to-modify-the-settings-": "Encode the URL so that it's harder for a guest to modify the settings.",
"encode-the-url-so-that-it-s-harder-for-a-guest-to-modify-the-settings-": "Coder de link zo dat het moeilijker is voor de gast om er achter te komen welke instellingen ingesteld staan",
"more-options": "Meer opties",
"youtube-video-demoing-how-to-do-this": "Youtube video voorbeelden, hoe dit te doen!",
"invite-a-guest-or-camera-source-to-publish-into-the-group-room": "Vraag een gast zijn beeld te publiceren in de kamer",
@@ -85,22 +85,22 @@
"remote-audio-settings": "Audio instellingen op afstand",
"advanced-video-settings": "Geavanceerde instellingen van video",
"activate-or-reload-this-video-device-": "Activeer of herlaadt deze video bron.",
"create-a-seconary-stream": "Create a Seconary Stream",
"the-director-will-be-visible-in-scenes-as-if-a-performer-themselves-": "The director will be visible in scenes, as if a performer themselves.",
"useful-if-you-want-to-perform-and-direct-at-the-same-time": "Useful if you want to perform and direct at the same time",
"create-a-secondary-stream": "Maak een tweede stream aan",
"the-director-will-be-visible-in-scenes-as-if-a-performer-themselves-": "De director zal zichtbaar zijn in de scenes, alsof hij ook een gast is.",
"useful-if-you-want-to-perform-and-direct-at-the-same-time": "Handig als je de director en gast tegelijk wilt zijn.",
"start-streaming": "start streaming",
"if-disabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "If disabled, the invited guest will not be able to see or hear anyone in the room.",
"if-disabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "If disabled, you must manually add a video to a scene for it to appear.",
"toggle-solo-voice-chat": "Toggle Solo Voice Chat",
"toggle-the-remote-guest-s-speaker-output": "Toggle the remote guest's speaker output",
"toggle-the-remote-guest-s-display-output": "Toggle the remote guest's display output"
"if-disabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "Waneer uitgeschakeld, zal de uitgenodigde gast niemand kunnen horen of zien in de kamer.",
"if-disabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "Wanneer uitgeschakeld, moet je handmatig eeen video toevoegen aan een scene voordat deze zichtbaar wordt.",
"toggle-solo-voice-chat": "Schakel Solo Geluids Chat",
"toggle-the-remote-guest-s-speaker-output": "Schakel de gast's speaker uitgang",
"toggle-the-remote-guest-s-display-output": "Schakel de gast's beeld uitgang"
},
"innerHTML": {
"logo-header": "<font id=\"qos\" style=\"color: white;\">O</font>BS Ninja",
"copy-this-url": "Deelbare Link naar deze video",
"you-are-in-the-control-center": "U bent in het kamer beheers centrum",
"joining-room": "U neemt deel aan de kamer",
"add-group-chat": "Voeg Groepsgesprek toe",
"add-group-chat": "Voeg groepsgesprek toe",
"rooms-allow-for": "Kamers maken eenvoudige groepsgespreken en geavanceerd beheer van meerdere streams tegelijkertijd mogelijk.",
"room-name": "Kamer Naam",
"password-input-field": "Password",
@@ -126,11 +126,11 @@
"here-you-can-pre-generate": "Hier kan u vooraf een herbruikbare weergave link en een bijbehorende gast uitnodigingslink aanmaken.",
"generate-invite-link": "GENEREER DE UITNODIGINGS LINK",
"advanced-paramaters": "Geavanceerde Parameters",
"unlock-video-bitrate": "Ontsluit Video Bitrate (20mbps)",
"unlock-video-bitrate": "Verwijder limitatie video bitrate (20mbps)",
"force-vp9-video-codec": "Forceer VP9 Video Codec (minder verstoring)",
"enable-stereo-and-pro": "Activeer Stereo en Pro HD Geluid",
"video-resolution": "Video Resolutie: ",
"hide-mic-selection": "Force Default Microphone",
"hide-mic-selection": "Forceer Standaard Microfoon",
"hide-screen-share": "Verberg Scherm Delen Optie",
"allow-remote-control": "Afstandsbediening Camera Zoom (android)",
"add-a-password-to-stream": " Add a password:",
@@ -139,28 +139,28 @@
"can-see-and-hear": "Het groepsgesprek zien en horen",
"can-hear-only": "Alleen het groepsgesprek horen",
"cant-see-or-hear": "Het groepsgesprek niet horen en zien",
"share-local-video-file": "Stream Media File",
"share-website-iframe": "Share Website",
"run-a-speed-test": "Run a Speed Test",
"read-the-guides": "Browse the Guides",
"share-local-video-file": "Stream Media Bestand",
"share-website-iframe": "Deel Website",
"run-a-speed-test": "Doe een Speed Test",
"read-the-guides": "Blader in de handleidingen",
"info-blob": "\n\t\t\t\t\t\t<h2>Wat is OBS.Ninja</h2><br>\n\t\t\t\t\t\t<li>100% <b>gratis</b>; geen downloads; geen persoonlijke gegevens verzamelen; niet inloggen</li>\n\t\t\t\t\t\t<li>Breng video van uw smartphone, laptop, computer, of van uw vrienden direct in uw OBS video stroom</li>\n\t\t\t\t\t\t<li>We gebruiken vooruitstrevende Peer-to-Peer technologie die privacy en ultra lage vertraging biedt</li>\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t<li>Youtube video <i class=\"fa fa-youtube-play\" aria-hidden=\"true\"></i> <a href=\"https://www.youtube.com/watch?v=6R_sQKxFAhg\">Demonstratie</a> </li>\n\t\t\t\t\t\t",
"add-to-scene": "Add to Scene",
"forward-to-room": "Transfer",
"add-to-scene": "Toevoegen aan Scene",
"forward-to-room": "Doorverbinden",
"record": "Neem op",
"disconnect-guest": "Hangup",
"disconnect-guest": "Ophangen",
"mute": "Demp",
"change-to-low-quality": "&nbsp;&nbsp;<i class=\"las la-video-slash\"></i>",
"change-to-medium-quality": "&nbsp;&nbsp;<i class=\"las la-video\"></i>",
"change-to-high-quality": "&nbsp;&nbsp;<i class=\"las la-binoculars\"></i>",
"send-direct-chat": "<i class=\"las la-envelope\"></i> Message",
"advanced-camera-settings": "<i class=\"las la-cog\"></i> Advanced",
"voice-chat": "<i class=\"las la-microphone\"></i> Voice Chat",
"open-in-new-tab": "Open in nieuwe Tab",
"copy-to-clipboard": "Kopiëren naar Clipboard",
"click-for-quick-room-overview": "❔ Click Here for a quick overview and help",
"push-to-talk-enable": "🔊 Enable Director's Push-to-Talk Mode",
"send-direct-chat": "<i class=\"las la-envelope\"></i> Bericht",
"advanced-camera-settings": "<i class=\"las la-cog\"></i> Geavanceerd",
"voice-chat": "<i class=\"las la-microphone\"></i> Geluids Chat",
"open-in-new-tab": "Open in nieuw tabblad",
"copy-to-clipboard": "Kopiëren naar klembord",
"click-for-quick-room-overview": "❔ Klik hier voor een snel overzicht en hulp",
"push-to-talk-enable": "🔊 Enable Director's Push-to-Talk Mode Zet ",
"welcome-to-control-room": "Welcome. This is the control-room for the group-chat. There are different things you can use this room for:<br><br>\t<li>You can host a group chat with friends using a room. Share the blue link to invite guests who will join the chat automatically.</li>\t<li>A group room can handle around 4 to 30 guests, depending on numerous factors, including CPU and available bandwidth of all guests in the room.</li>\t<li>Solo-views of each video are offered under videos as they load. These can be used within an OBS Browser Source.</li>\t<li>You can use the auto-mixing Group Scene, the green link, to auto arrange multiple videos for you in OBS.</li>\t<li>You can use this control room to record isolated video or audio streams, but it is an experimental feature still.</li>\t<li>Videos in the Director's room will be of low quality on purpose; to save bandwidth/CPU</li>\t<li>Guest's in the room will see each other's videos at a very limited quality to conserve bandwidth/CPU.</li>\t<li>OBS will see a guest's video in high-quality; the default video bitrate is 2500kbps.</li>\t<br>\tAs guests join, their videos will appear below. You can bring their video streams into OBS as solo-scenes or you can add them to the Group Scene.\t<br>The Group Scene auto-mixes videos that have been added to the group scene. Please note that the Auto-Mixer requires guests be manually added to it for them to appear in it; they are not added automatically.<br><br>Apple mobile devices, such as iPhones and iPads, do not fully support Video Group Chat. This is a hardware constraint.<br><br>\tFor advanced options and parameters, <a href=\"https://github.com/steveseguin/obsninja/wiki/Guides-and-How-to's#urlparameters\">see the Wiki.</a>",
"more-than-four-can-join": "These four guest slots are just for demonstration. More than four guests can actually join a room.",
"more-than-four-can-join": "Er staan momenteel vier gasten plekken gevuld voor de demonstratie. Het is mogelijk om meer gasten te hebben in een kamer.",
"welcome-to-obs-ninja-chat": "\n\t\t\t\t\tWelcome to OBS.Ninja! You can send text messages directly to connected peers from here.\n\t\t\t\t",
"names-and-labels-coming-soon": "\n\t\t\t\t\tNames identifying connected peers will be a feature in an upcoming release.\n\t\t\t\t",
"send-chat": "Verstuur",
@@ -188,15 +188,15 @@
"order-down": "<i class=\"las la-minus\"></i>",
"order-up": "<i class=\"las la-plus\"></i>",
"advanced-audio-settings": "<i class=\"las la-sliders-h\"></i> Audio instellingen",
"scenes-can-see-director": "Director will also be a performer",
"toggle-remote-speaker": "Deafen Guest",
"toggle-remote-display": "Blind Guest"
"scenes-can-see-director": "Director is ook een gast",
"toggle-remote-speaker": "Verdoof Gast",
"toggle-remote-display": "Verblind Gast"
},
"placeholders": {
"join-by-room-name-here": "Ga binnen met een kamer naam",
"enter-a-room-name-here": "Geef hier een kamer naam op",
"optional-room-password-here": "Optionele wachtwoord voor kamer",
"give-this-media-source-a-name-optional-": "Geef de media bron een naam(optioneel)",
"give-this-media-source-a-name-optional-": "Geef de media bron een naam (optioneel)",
"add-an-optional-password": "Voeg optioneel wachtwoord toe",
"enter-room-name-here": "Geef hier de kamer naam op",
"enter-chat-message-to-send-here": "Type hier om te chatten"

View File

@@ -5,22 +5,25 @@
This install script and config file was used with a standard virtual machine server loaded with Ubuntu 20. GCP/AWS servers might need slightly different settings.
```
sudo apt-get update
sudo apt-get update # update package lists
sudo apt-get install coturn -y
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get install certbot -y
sudo apt-get install coturn -y # install coturn, the implementation of the TURN server
sudo add-apt-repository ppa:certbot/certbot # Add the certbot repository
sudo apt-get install certbot -y # Install certbot required for the HTTPS certificate
sudo vi /etc/default/coturn
sudo vi /etc/default/coturn # open the coturn configuration in Vim (you can also use nano or any other editor)
```
...and we uncomment the line:
```
#TURNSERVER_ENABLED=1
```
….leaving it like this:
```
TURNSERVER_ENABLED=1
```
Next make sure you have the DNS pointing to your IP address for this next step (ipv4, and ipv6 if possible). You will need to validate that in the next step.
```
sudo certbot certonly --standalone
sudo certbot certonly --standalone # only generate the HTTPS certificate without actually changing any configs
sudo apt install net-tools
```
note: If you run into error 701 issues with your TURN server, check that the coturn service has access to your new SSL certificates:
@@ -37,10 +40,10 @@ sudo systemctl daemon-reload
Next, we are going to open up some ports... just in case they are blocked by default. Which exactly? well, these are default ports. TCP may not be needed?
```
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 3478/tcp # The default coturn TCP port
sudo ufw allow 3478/udp # The default coturn UDP port
sudo ufw allow 443/tcp # The HTTPS TCP port
sudo ufw allow 443/udp # The HTTPS UDP port
sudo ufw allow 49152:65535/tcp
sudo ufw allow 49152:65535/udp
```

16
turnserver3.conf Normal file
View File

@@ -0,0 +1,16 @@
# Another TURNSERVER conf file; this one is a basic UDP-based setup. Port 3478, no SSL/TLS, and basic username/password.
listening-port=3478
alt-listening-port=0
fingerprint
no-stun
lt-cred-mech
user=obsninja:somepasswordwhere
stale-nonce=600
realm=turn-eu2.obs.ninja
server-name=turn-eu2.obs.ninja
no-multicast-peers
stale-nonce=600
dh2066
#verbose
no-stdout-log

File diff suppressed because one or more lines are too long

182
zoom.html Normal file
View File

@@ -0,0 +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>