104 Commits
14.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
Steve Seguin
c28ecebc13 Merge pull request #686 from steveseguin/v16-beta
V16.3
2021-02-03 11:17:29 -05:00
Steve Seguin
28a79b20b7 v16.3 release? 2021-02-03 11:16:35 -05:00
Steve Seguin
9172ebade1 Add files via upload 2021-02-01 09:58:31 -05:00
Steve Seguin
c4aed40be6 v16.0 2021-01-30 09:40:34 -05:00
Steve Seguin
1927b84d25 Merge pull request #676 from steveseguin/steveseguin-patch-1
Update nl.json
2021-01-27 17:29:55 -05:00
Steve Seguin
b6b39b893f Update nl.json 2021-01-27 17:29:08 -05:00
Steve Seguin
1f408e645f Merge pull request #675 from SilverServerT/master
Update nl.json
2021-01-27 17:04:45 -05:00
SilverServerT
eee79d2ef9 Update nl.json 2021-01-27 22:57:28 +01:00
Steve Seguin
abe5028458 Update README.md 2021-01-27 10:42:11 -05:00
Steve Seguin
09e1111be3 Update README.md 2021-01-27 10:40:54 -05:00
Steve Seguin
c2b8b1769d Delete videojs-vr.js 2021-01-27 10:38:35 -05:00
Steve Seguin
7c9ba6fd12 Delete video.js 2021-01-27 10:38:27 -05:00
Steve Seguin
f50ca54b32 Delete adapter-latest.js
using a smaller version instead
2021-01-27 10:38:13 -05:00
Steve Seguin
a4edbf1a18 Delete jquery.min.js
begone vile beast
2021-01-27 10:37:56 -05:00
Steve Seguin
308664fdc2 Delete tone.ogg
moved to media
2021-01-27 10:37:18 -05:00
Steve Seguin
8c68737501 Delete tone.mp3
moved to media
2021-01-27 10:37:08 -05:00
Steve Seguin
50e50c4d1b Delete cap.webm
moved to media
2021-01-27 10:36:57 -05:00
Steve Seguin
f05efc5288 Delete animations.js
migrated all the code away from jquery
2021-01-27 10:36:40 -05:00
Steve Seguin
a6b500bfa8 Delete images directory
deleting the right directory for once.  not enough coffeeeeee
2021-01-27 10:36:10 -05:00
Steve Seguin
32f4527784 Add files via upload 2021-01-27 10:35:42 -05:00
Steve Seguin
d45584c444 Delete favicon-16x16.png 2021-01-27 10:34:50 -05:00
Steve Seguin
70d83b35a7 Delete cap.webm 2021-01-27 10:30:02 -05:00
Steve Seguin
5198817371 Delete tone.mp3 2021-01-27 10:29:48 -05:00
Steve Seguin
d483b8ca3a Delete tone.ogg 2021-01-27 10:29:42 -05:00
Steve Seguin
a6c903fe1a Add files via upload 2021-01-27 10:29:23 -05:00
Steve Seguin
4a203a1e60 Add files via upload 2021-01-27 10:27:45 -05:00
Steve Seguin
c9849849a5 Merge pull request #674 from steveseguin/version-v15.1
Version v15.1
2021-01-27 10:16:36 -05:00
Steve Seguin
705c8028a1 Add files via upload 2021-01-27 10:13:18 -05:00
Steve Seguin
4618426724 Update animations.js 2021-01-27 10:07:46 -05:00
Steve Seguin
5b86a4f906 version v15.1 2021-01-27 10:05:42 -05:00
Joel Calado
2d0908f326 Merge pull request #673 from digisomni/patch-1
Fix typo in index.html
2021-01-27 10:10:27 +00:00
Kalila
843c49fa20 Update index.html
Fix extra "
2021-01-27 01:01:03 -05:00
Steve Seguin
e4d6862d9f Update animations.js 2021-01-21 07:20:21 -05:00
Steve Seguin
5538afdd71 Update animations.js
updating code to align with css; 220px is old.
2021-01-21 07:19:54 -05:00
Steve Seguin
e34b68a573 Merge pull request #671 from filiptronicek/master
Refactor animation code
2021-01-21 07:18:15 -05:00
Filip Troníček
0af23bf6de Break 2021-01-20 17:54:16 +01:00
Filip Troníček
8819b2fe95 Eqeqeq
https://eslint.org/docs/rules/eqeqeq
2021-01-20 17:52:36 +01:00
Filip Troníček
0913cc204d Unnecesary spaces 2021-01-20 17:51:28 +01:00
Filip Troníček
e31571e508 Rewrite style injection as a template string 2021-01-20 17:50:43 +01:00
Filip Troníček
a26cd7a1c9 Unused events as _e 2021-01-20 17:46:33 +01:00
Filip Troníček
e412f486bd var =>const 2021-01-20 17:46:11 +01:00
Filip Troníček
547a914a59 Template strings and const 2021-01-20 17:44:55 +01:00
Filip Troníček
89b651bb89 Simple code changes in animations.js
- delete double semicolons
- Convert anonymous functions to arrow functions where possible
2021-01-20 17:42:42 +01:00
Steve Seguin
b0b992c3a9 Add files via upload 2021-01-20 11:27:21 -05:00
55 changed files with 14382 additions and 123749 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

@@ -1,5 +1,5 @@
<img src="images/obsNinja_logo_full.png" alt="Logo by brimace" data-canonical-src="https://gyazo.com/eb5c5741b6a9a16c692170a41a49c858.png" height="150" />
<img src="media/obsNinja_logo_full.png" alt="Logo by brimace" data-canonical-src="https://gyazo.com/eb5c5741b6a9a16c692170a41a49c858.png" height="150" />
## What is OBS NINJA
OBS.Ninja uses peer-to-peer technology to bring remote cameras into OBS. In most cases, all video data is transferred directly from peer to peer, without needing to go through any video server. This results in high-quality video with super low latency. In a small number of cases, video data may go through an encrypted TURN server, which is used to facilitate peer connections when otherwise not possible.
@@ -14,32 +14,30 @@ Also check out the FAQ for more info: https://github.com/steveseguin/obsninja/wi
## How to use:
I demo the basic usage of OBS.Ninja on YouTube: https://www.youtube.com/watch?v=6R_sQKxFAhg
Here is a podcast series showing how to use different basic OBS.Ninja features: https://www.youtube.com/watch?v=XfSqufuoV74&list=PLWodc2tCfAH1l_LDvEyxEqFf42hOBKqQM
Here is a podcast series showing how to use different basic OBS.Ninja features, including macOS support: https://www.youtube.com/watch?v=XfSqufuoV74&list=PLWodc2tCfAH1l_LDvEyxEqFf42hOBKqQM
And Here is another video series touching on some more advanced settings: https://www.youtube.com/watch?v=mQ1Jdhf5aYg&list=PL8VJWj2-XLFpFu3G35Hdm1nKZ2xn9_0_8
Check the subreddit for added use cases, advanced features, and support. Advanced features includes high-quality audio modes, custom video resolutions, and more.
[Update as of January 2021]
MacOS need to upgrade to OBS v26.1.2 or newer to have access native support for OBS.Ninja on macOS. Users with older OBS versions or using StreamLabs may still wish to use the Electron Capture app: https://github.com/steveseguin/electroncapture
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 client-side 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. The code provided is designed to allow for innovation, customization, white-labelling, and exploration.
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 may not work. See [here](https://github.com/steveseguin/obsninja/blob/master/install.md) for added details and alternative install options.
To use, just download and host the files on a HTTPS-enabled webserver. You may want to hide the .html extensions within your HTTP server as well, else the generated links will not work. See [here](https://github.com/steveseguin/obsninja/blob/master/install.md) for added details and alternative install options.
Directions on how to deploy a TURN server are listed in the turnserver.md file. You may wish to do so, although not all use cases will need one. Only about ~10% of remote guests need them; those often connected via 4G LTE or those behind corporate firewall. While OBS.Ninja does host some TURN servers freely for OBS.Ninja users, they are quite expensive to operate and are not really for private deployment use. If you are deploying your own version of OBS.Ninja, I'd ask you use your own TURN servers instead, but I likely won't enforce this unless there is heavy abuse.
Directions on how to deploy a TURN server are listed in the turnserver.md file. You may wish to do so, although not all use cases will not need one. Only about 10% of remote guests, those often connected via 4G LTE, will require a TURN server. While OBS.Ninja does host some TURN servers, they are quite expensive to operate and not really for private deployment use. If you are deploying your own version of OBS.Ninja, I'd ask you use your own TURN servers instead.
## Server side / API software?
Since OBS.Ninja uses peer-to-peer technology, video connections are made directly between viewer and publisher in ~90% of cases. The remaining connections will likely have to happen over a TURN video relay server, hosted in the cloud. These servers ensure peer connection compatibility. Very few users will see any benefit of using a TURN server over a direct peer connection, but there are still cases it may be helpful or required to deploy your own. Details on how to deploy a TURN server are provided in the repo.
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 custom 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. This repo does not include details on setting up a STUN servers and does not yet make available the handshake server code.
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 debugging dependencies are disabled though before deployment, just to be safe. Please see the index.html header for any such dependencies.
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.
A design goal of OBS.Ninja is to be serverless and we are like 99% of the way there. This design objective ensures OBS.Ninja can be offered for free, along with providing increased levels of security and privacy.
A design goal of OBS.Ninja is to be serverless and we are 99% of the way there. This design objective ensures OBS.Ninja can be offered for free, along with providing increased levels of security and privacy.
## Issues? problems? Not working?
@@ -65,9 +63,9 @@ 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, as they can be used to mask your IP address, along with some VPN services. 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 function of the web service. Our webserver is cached by Cloudflare and it provides denial of server protection for the users of OBS.Ninja.
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.
Additional security features are being added weekly on request. Please ask about these options if added security and privacy are requirements for you.

View File

@@ -1,121 +0,0 @@
$("body").append('<style id="lightbox-animations" type="text/css"></style>');
$(".column").on('click', function() {
if ( $(this).hasClass( "skip-animation" )){
return;
}
var bounding_box = $(this).get(0).getBoundingClientRect();
$(this).css({ top: bounding_box.top + 'px', left: bounding_box.left -20+ 'px' });
$(this).addClass('in-animation').removeClass('pointer');
$("#empty-container").remove();
$('<div id="empty-container" class="column"></div>').insertAfter(this);
var styles = '';
styles = '@keyframes outlightbox {';
styles += '0% {';
styles += 'height: 100%;';
styles += 'width: 100%;';
styles += 'top: 0px;';
styles += 'left: 0px;';
styles += '}';
styles += '50% {';
styles += 'height: 220px;';
styles += 'top: ' + bounding_box.y + 'px;';
styles += '}';
styles += '100% {';
styles += 'height: 220px;';
styles += 'width: '+bounding_box.width+'px;';
styles += 'top: ' + bounding_box.y + 'px;';
styles += 'left: ' + bounding_box.x + 'px;';
styles += '}';
styles += '}';
$("#lightbox-animations").empty();
$("#lightbox-animations").get(0).sheet.insertRule(styles, 0);
$("body").css('overflow', 'hidden');
});
$(".close").on('click', function(e) {
$(this).hide();
$(".container-inner").hide();
$("body").css('overflow', 'auto');
var bounding_box = $(this).parent().get(0).getBoundingClientRect();
$(this).parent().css({ top: bounding_box.top + 'px', left: bounding_box.left + 'px' });
$(this).parent().addClass('out-animation');
cleanupMediaTracks();
e.stopPropagation();
});
$(".column").on('animationend', function(e){
if (e.originalEvent.animationName == 'inlightbox') {
$(this).children(".close").show();
$(this).children(".container-inner").show();
}
else if (e.originalEvent.animationName == 'outlightbox') {
$(this).removeClass('in-animation').removeClass('out-animation').removeClass('columnfade').addClass('pointer');
$("#empty-container").remove();
$("#lightbox-animations").get(0).sheet.deleteRule(0);
}
});
$('#audioSource').on('mousedown touchend focusin focusout', function(e) {
var state = $('#multiselect-trigger').data('state') || 0;
if( state == 0 ) {
$('#multiselect-trigger').data('state', '1').addClass('open').removeClass('closed');
$('#multiselect-trigger').find('.chevron').removeClass('bottom');
$('#multiselect-trigger').parent().find('.multiselect-contents').show();
$('#multiselect-trigger').parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$('#multiselect-trigger').parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
}
});
$('#audioSource3').on('mousedown touchend focusin focusout', function(e) {
var state = $('#multiselect-trigger3').attr('data-state') || 0;
if( state == 0 ) {
$('#multiselect-trigger3').attr('data-state', '1').addClass('open').removeClass('closed');
$('#multiselect-trigger3').find('.chevron').removeClass('bottom');
$('#multiselect-trigger3').parent().find('.multiselect-contents').show();
$('#multiselect-trigger3').parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$('#multiselect-trigger3').parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
}
});
$('#multiselect-trigger').on('mousedown touchend focusin focusout', function(e) {
var state = $(this).data('state') || 0;
if( state == 0 ) {
// open the dropdown
$(this).data('state', '1').addClass('open').removeClass('closed');
$(this).find('.chevron').removeClass('bottom');
$(this).parent().find('.multiselect-contents').show();
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
} else {
// close the dropdown
$(this).data('state', '0').addClass('closed').removeClass('open');
$(this).find('.chevron').addClass('bottom');
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').not(":checked").parent().hide();;
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').hide();;
}
e.preventDefault();
});
// multiselect dropdowns
$('#multiselect-trigger3').on('mousedown touchend focusin focusout', function(e) {
var state = $(this).attr('data-state') || 0;
if( state == 0 ) {
// open the dropdown
errorlog("STATE: "+state);
$(this).attr('data-state', '1').addClass('open').removeClass('closed');
$(this).find('.chevron').removeClass('bottom');
$(this).parent().find('.multiselect-contents').show();
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
} else {
// close the dropdown
$(this).attr('data-state', '0').addClass('closed').removeClass('open');
$(this).find('.chevron').addClass('bottom');
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').not(":checked").parent().hide();;
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').hide();;
}
e.preventDefault();
});

View File

@@ -1,20 +1,76 @@
<body>
<head>
<link rel="stylesheet" href="./main.css?ver=39" />
<style>
body {
}
input {padding:10px;}
h3 { margin-top:10px; padding:5px;}
hr {
margin: 20px 0;
}
video{
max-width:640px;
max-height:360px;
padding:20px;
}
audio{
max-width:640px;
max-height:360px;
padding:20px;
}
</style>
</head>
<body style='color:white'>
<video id="player" controls style="display:none"></video>
<audio id="player2" controls style="display:none"></audio>
<div id="info">
<h3>This tool can be used to convert WebM videos of dynamic resolution to MP4 files of a fixed 1280x720 resolution.</h3> Just select a video file and wait. It takes about 60-seconds to transcode 1-second of video. Very sloowww...<br />
<p>You can use FFMpeg locally to achieve much faster results.</p>
<p>This tool performs the following action in your browser: <i>fmpeg -i input.webm -vf scale=1280:720 output.mp4</i><p>
<h1>Web-based Media Conversion Tools</h1>
<hr>
<h3>Transcodes WebM files to MP4 files with a fixed 1280x720 resolution. (very slow!)</h3><br />
<small><p>This tool performs the following action in your browser: <i> fmpeg -i input.webm -vf scale=1280:720 output.mp4</i></p></small>
<input type="file" id="uploader" title="Convert WebM to MP4">
<hr>
<h3>Bonus: This option converts MKV files to MP4 files without transcoding.</h3> </p><i>fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</i>
<br /><br /><input type="file" id="uploader2" accept=".mkv" title="Convert MKV to MP4">
<p>You can use FFMpeg locally to achieve much faster results with either option.</p>
<h3>This option converts WebM files to MP4 files without transcoding, and attempting to force high resolutions.
<br /><br /><input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
</div>
<hr>
<h3>Remuxes MKV files to MP4 files without transcoding.</h3> </p><br /><small><i> fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</i></small>
<br /><input type="file" id="uploader2" accept=".mkv" title="Convert MKV to MP4">
<hr>
<h3>Remuxes WebM files to MP4 files without transcoding (attempts to force high resolutions, also)</h3>
<input type="file" id="uploader3" accept=".webm" title="Convert WebM to MP4">
<hr>
<h3>Remuxes WebM to Audio-only files (opus or wav)</h3>
<input type="file" id="uploader4" accept=".webm" title="Convert WebM to OPUS">
<hr>
</div>
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.9.6/dist/ffmpeg.min.js"></script>
<script>
function download(data, filename) {
const blob = new Blob([data.buffer]);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 100);
}
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const transcode = async ({ target: { files } }) => {
@@ -44,13 +100,14 @@
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
video.style.display="block";
document.getElementById('info').innerText = "Operation Done. Play video or download it.";
}
const force1080 = async ({ target: { files } }) => {
const { name } = files[0];
const sourceBuffer = await fetch("cap.webm").then(r => r.arrayBuffer());
const sourceBuffer = await fetch("./media/cap.webm").then(r => r.arrayBuffer());
document.getElementById('uploader').style.display="none";
document.getElementById('uploader2').style.display="none";
document.getElementById('uploader3').style.display="none";
@@ -67,8 +124,34 @@
document.getElementById('info').innerText = "Operation Done. Play video or download it.";
}
const convertToAudioOnly = async ({ target: { files } }) => {
const { name } = files[0];
document.getElementById('info').innerText = "Transcoding file... this will take a while";
await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
const video = document.getElementById('player');
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.opus');
const data = ffmpeg.FS('readFile', 'output.opus');
console.log(data.buffer.byteLength);
if (data.buffer.byteLength){
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/opus' }));
download(data, name.split(".")[0]+".opus");
} else {
await ffmpeg.run('-i', name, '-vn', '-acodec', 'copy', 'output.wav');
const data2 = ffmpeg.FS('readFile', 'output.wav');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'audio/pcm' }));
download(data2, name.split(".")[0]+".wav");
}
video.style.display="block";
document.getElementById('info').innerText = "Operation Done. Play audio or download it.";
}
document.getElementById('uploader').addEventListener('change', transcode);
document.getElementById('uploader2').addEventListener('change', transmux);
document.getElementById('uploader3').addEventListener('change', force1080);
document.getElementById('uploader4').addEventListener('change', convertToAudioOnly);
</script>
</body>

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

@@ -117,22 +117,25 @@ function generateInvite(){
if (getById("invite_vp9").checked){
viewstr+="&codec=vp9";
}
if (getById("invite_h264").checked){
viewstr+="&codec=h264";
}
if (getById("invite_stereo").checked){
viewstr+="&stereo";
sendstr+="&stereo";
}
if (getById("invite_secure").checked){
sendstr+="&secure";
}
//if (getById("invite_secure").checked){
// sendstr+="&secure";
//}
if (getById("invite_hidescreen").checked){
sendstr+="&webcam";
}
if (getById("invite_remotecontrol").checked){ //
var remote_gen_id = generateStreamID();
sendstr+="&remote="+remote_gen_id; // security
viewstr+="&remote="+remote_gen_id;
}
//if (getById("invite_remotecontrol").checked){ //
// var remote_gen_id = generateStreamID();
// sendstr+="&remote="+remote_gen_id; // security
// viewstr+="&remote="+remote_gen_id;
//}
if (getById("invite_joinroom").value.trim().length){
sendstr+="&room="+getById("invite_joinroom").value.replace(/[\W]+/g,"_");
@@ -231,7 +234,9 @@ document.addEventListener("dragstart", event => {
<input type="checkbox" id="invite_bitrate" /><label for="invite_bitrate"> <span data-translate="unlock-video-bitrate">Unlock Video Bitrate (20mbps)</span></label>
<br />
<input type="checkbox" id="invite_vp9" /><label for="invite_vp9"> <span data-translate="force-vp9-video-codec">VP9 Video Codec</span></label>
<input type="checkbox" id="invite_vp9" onclick="getById('invite_h264').checked=false;" /><label for="invite_vp9"> <span data-translate="force-vp9-video-codec">VP9 Video Codec</span></label>
<br />
<input type="checkbox" id="invite_h264" onclick="getById('invite_vp9').checked=false;" /><label for="invite_h264"> <span data-translate="force-h264-video-codec">H264 Video Codec</span></label>
<br />
<input type="checkbox" id="invite_stereo" /><label for="invite_stereo"> <span data-translate="enable-stereo-and-pro">Stereo and Pro HD Audio</span></label>
<br />
@@ -245,17 +250,11 @@ document.addEventListener("dragstart", event => {
</select>
<br />
<br />
<input type="checkbox" id="invite_secure" />
<label for="invite_secure"> <span data-translate="high-security-mode">High Security Mode</span></label>
<br />
<input type="checkbox" id="invite_hidescreen" />
<label for="invite_hidescreen"> <span data-translate="hide-screen-share">Hide Screenshare Option</span></label>
<br />
<input type="checkbox" id="invite_remotecontrol" />
<label for="invite_remotecontrol"> <span data-translate="allow-remote-control">Remote Control Camera Zoom</span></label>
<br />
<br />
<label for="videoname">Stream label:</label>
<label for="videoname">Stream Label:</label>
<input id="videoname" placeholder="Give stream a description" />
<br />
<br />
@@ -293,7 +292,7 @@ document.addEventListener("dragstart", event => {
</div>
<div class="gone" >
<!-- This image is used when dragging elements -->
<img src="./images/favicon-32x32.png" id="dragImage" />
<img src="./media/favicon-32x32.png" id="dragImage" />
</div>
</body>
</html>

View File

@@ -125,7 +125,7 @@ input[type='checkbox']:checked {
<div id="header" style="-webkit-app-region: drag;color:white;font-size:2em">OBS.Ninja</div>
<div class="formcss" >
<div id='warning4mac' style="border:2px dotted; display:none;max-width:700px; padding:10px; margin:0 90px 20px 90px;color:white;font-size:1.3em"> 🚨 If using OBS v26 on macOS, right-click the Electron Capture app and disable <i>Always-on-Top</i> to reveal it during window-selection. You can enable it again afterwards.</div>
<div id='warning4mac' style="border:2px dotted; display:none;max-width:800px; padding:10px; margin:0 90px 20px 90px;color:white;font-size:1.3em"> ✨ Great News! OBS v26.1.2 <a href="https://github.com/obsproject/obs-browser/issues/209#issuecomment-748683083">now supports</a> OBS.Ninja without needing the Electron Capture app! 🥳</div>
<input type="checkbox" class="check" id="prefervp9" name="prefervp9" value="false" onclick="modURL(this);">
<label for="prefervp9">Force VP9 Codec</label>
@@ -164,9 +164,9 @@ input[type='checkbox']:checked {
*
*/
//if (navigator.userAgent.indexOf('Mac OS X') != -1){
// document.getElementById("warning4mac").style.display="block";
//}
if (navigator.userAgent.indexOf('Mac OS X') != -1){
document.getElementById("warning4mac").style.display="block";
}
var audioOutputSelect = document.querySelector('select#audioOutput');
audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype);

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!"}, '*');};
@@ -228,6 +238,11 @@ function loadIframe(){ // this is pretty important if you want to avoid camera
button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; // publishScreen
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "eval('alert(\"DANGERUS\")'";
button.onclick = function(){iframe.contentWindow.postMessage({"function":"eval", "value":'alert(\"DANGERUS\")'}, '*');}; // publishScreen
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Change Add Camera text";
button.onclick = function(){iframe.contentWindow.postMessage({"function":"changeHTML", "target":"add_camera", "value":"NEW CAMERA TEXT"}, '*');}; // change text of add camera button

View File

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

Before

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

View File

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

Before

Width:  |  Height:  |  Size: 697 B

File diff suppressed because it is too large Load Diff

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

527
main.css
View File

@@ -2,9 +2,9 @@
--background-color: #141926;
--container-color: #373737;
--button-color: #2A2A2A;
--blue-accent: #4B4959;
--blue-accent: #4a4c63;
--red-accent: #553737;
--green-accent: #304F2B;
--green-accent: #3f4f50;
--olive-accent: #535D32;
--regular-margin: 10px;
}
@@ -130,13 +130,37 @@ button.grey {
color: white;
}
#miniPerformer>#videosource{
width: 80px;
height: 45px;
margin: 5px;
background-color: #464749 !important;
background-size: 50%;
}
#miniPerformer>#previewWebcam{
width: 80px;
height: 45px;
margin: 5px;
background-color: #464749 !important;
background-size: 50%;
}
#reportbutton{
visibility: hidden;
}
.red {
background-color: #840000 !important;
}
button.red {
-webkit-app-region: no-drag;
padding: 10px;
margin: 10px 0px;
cursor: pointer;
border-radius: 2px;
background-color: var(--red-accent);
color: white;
}
@@ -149,6 +173,7 @@ button {
border-radius: 2px;
}
button.white {
-webkit-app-region: no-drag;
padding: 6px 10px 4px 9px;
@@ -179,14 +204,23 @@ 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;
background-color: #0000;
color: white;
font-family: Cousine, monospace;
font-size: 4em;
line-height: 1.5em;
font-size: 6vh;
line-height: 8vh;
letter-spacing: 0.0em;
text-shadow: 0.05em 0.05em 0px rgba(0,0,0,1);
width:100%;
@@ -197,13 +231,13 @@ button.white:active {
top: 0;
position: fixed;
overflow-wrap: anywhere;
padding:3%;
padding:2% 3%;
pointer-events: none
}
#overlayMsgs span{
background-color: #000A;
padding: 8px 8px 0px 8px;
margin:10px;
background-color: #000B;
padding: 2px;
margin: 0.5vh;
text-align: center;
width:100%;
pointer-events: none
@@ -334,7 +368,6 @@ hr {
#gridlayout {
padding: 0;
display: grid;
width: 100%;
height: 100%;
grid-gap: 0;
@@ -374,14 +407,14 @@ hr {
.directorsgrid .vidcon video {
max-width: 100%;
padding: 5px;
padding: 0 5px;
width: 100%;
height: 157px;
height: 148px;
}
.directorsgrid .vidcon {
display: inline-block !important;
width: 269.2px !important;
width: 269.7px !important;
background: #7E7E7E;
color: #FCFCFC;
vertical-align: top;
@@ -416,6 +449,19 @@ hr {
height: 11px;
margin: 0;
}
.queueNotification {
position: relative;
top: -40px;
right: -33px;
padding: 2px 0;
border-radius: 50%;
background: #335c3a;
color: white;
width: 23px;
height: 23px;
margin: 0;
}
button.glyphicon-button:focus,
button.glyphicon-button:active:focus,
@@ -433,7 +479,173 @@ button.glyphicon-button.active.focus {
height:100%;
}
#controlButtons {
position: fixed;
z-index: 5;
bottom: 0px;
width: 100%;
display: none;
justify-content: center;
align-items: center;
height: 60px;
border: 0;
pointer-events: none;
}
#subControlButtons {
display: flex;
border-radius: 38px;
background-color: #030303dd;
padding: 5px 7px;
position: absolute;
pointer-events: auto;
}
#container.vidcon {
height:100%;
}
@media only screen and (max-width: 640px){
#controlButtons {
transform: scale(0.9) translateY(10%);
}
}
@media only screen and (max-width: 400px){
#controlButtons {
transform: scale(0.8) translateY(20%);
}
}
@media only screen and (max-width: 300px){
#subControlButtons {
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.88);
}
#gridlayout>#container.vidcon {
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;
}
}
@media only screen and (max-height: 400px){
#subControlButtons {
transform: scale(0.85);
}
#logoname{
display:none;
}
#head1{
display:none;
}
#head4{
display:none;
}
#head5{
display:none;
}
#head2{
display:none;
}
#gridlayout>#container.vidcon {
height:85%
}
#controlButtons {
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%
}
#subControlButtons {
transform: scale(0.77);
}
#controlButtons {
height:46.2px;
}
}
@media only screen and (max-height: 190px){
#gridlayout>#container.vidcon {
height:75%
}
#subControlButtons {
transform: scale(0.73);
}
#controlButtons {
height:42px
}
}
@media only screen and (max-height: 160px){
#gridlayout>#container.vidcon {
height:70%
}
#subControlButtons {
transform: scale(0.65);
}
#controlButtons {
height:38px
}
}
@media only screen and (max-height: 120px){
#gridlayout>#container.vidcon {
height:70%
}
#subControlButtons {
transform: scale(0.52);
}
#controlButtons {
height:30px
}
}
#header:empty{
display:none;
}
@keyframes pulse {
0% {
@@ -794,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%;
}
@@ -802,7 +1026,33 @@ input[type=range]:focus::-ms-fill-upper {
text-shadow: 0px 0px 6px #000000FF;
font-weight: 700;
}
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 10em;
background-color: #9d5050;
color: #fff;
text-align: center;
/* padding: 5px 0; */
border-radius: 10px;
position: absolute;
z-index: 1;
top: -10px;
font-family: "Lato", sans-serif;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
#screensharebutton.float2{
background-color: #335c3a;
}
#screenshare2button.float2{
background-color: #335c3a;
}
#popupSelector {
background: linear-gradient(6deg, rgba(221, 221, 221, 0) 4%, rgb(221, 221, 221, 0.2) 30%, rgba(120, 120, 100, .5) 100%);
transition: all 0.2s linear 0s;
@@ -810,9 +1060,10 @@ input[type=range]:focus::-ms-fill-upper {
position: fixed;
top: 0px;
height: 90%;
width: 490px;
width: 505px;
right: -400px;
overflow: auto;
z-index: 1;
}
h2 {
@@ -896,6 +1147,7 @@ label {
/* padding: 30px; */
border-radius: 30px;
cursor:pointer;
color: #636363
}
.fullcolumn {
float: left;
@@ -913,13 +1165,14 @@ label {
/* Create four equal columns that floats next to each other */
.column {
display: inline-block;
margin: 1.8%;
min-width: 300px;
width: 20%;
padding: 25px;
height: 220px;
height: 200px;
/* Should be removed. Only for demonstration */
border-radius: 5px;
text-align: center;
@@ -943,12 +1196,6 @@ label {
color: black;
}
@media only screen and (max-height: 650px) {
.column {
height: 180px;
}
}
@keyframes fading {
0% {
opacity: 0
@@ -972,7 +1219,7 @@ img {
width: 20%;
min-width: 300px;
padding: 28px;
height: 220px;
height: 200px;
/* Should be removed. Only for demonstration */
margin: 1.8%;
@@ -1037,6 +1284,7 @@ img {
text-align: center;
margin: 5px;
pointer-events: auto;
outline:none;
}
.float2 {
opacity: 0.8;
@@ -1049,6 +1297,7 @@ img {
z-index: 10;
margin: 5px;
pointer-events: auto;
outline:none;
}
.rotate225 {
@@ -1059,7 +1308,6 @@ img {
.myVideo {
box-shadow: rgb(88, 88, 88) 0px 0px 5px 1px;
max-width: 800px !important;
width: 100% !important;
max-height: 100% !important;
height: auto !important;
display: block !important;
@@ -1075,18 +1323,9 @@ img {
cursor: pointer;
z-index: 6;
}
#controlButtons {
position: fixed;
z-index: 5;
bottom: 0px;
width: 100%;
display: none;
justify-content: center;
align-items: center;
height: 60px;
border: 0;
pointer-events: none;
}
.popup .menu { margin: 2px; }
.my-float {
margin-top: 7px;
opacity: 0.9;
@@ -1095,47 +1334,12 @@ img {
font-size: 32px;
color: white;
}
@media only screen and (max-height: 650px) {
.my-float {
margin-top: 4px;
margin-left: 1px;
}
.toggleSize {
font-size: 24px;
color: white;
}
.float {
opacity: 0.8;
width: 30px;
height: 30px;
background-color: #6666;
color: #FFF;
border-radius: 38px;
text-align: center;
margin: 5px;
pointer-events: auto;
}
.float2 {
opacity: 0.8;
width: 30px;
height: 30px;
background-color: #8888;
color: #FFF;
border-radius: 38px;
text-align: center;
z-index: 10;
margin: 5px;
pointer-events: auto;
}
}
img {
max-width: 100%;
}
.in-animation {
animation: inlightbox 0.8s forwards;
animation: inlightbox 0.5s forwards;
position: fixed !important;
margin: 0 !important;
}
@@ -1153,7 +1357,7 @@ img {
}
.out-animation {
animation: outlightbox 0.8s forwards;
animation: outlightbox 0.5s forwards;
}
.pointer {
@@ -1163,7 +1367,7 @@ img {
50% {
width: 100%;
left: 0;
height: 220px;
height: 200px;
}
100% {
@@ -1470,7 +1674,7 @@ video.clean::-webkit-media-controls-timeline-container {
font-weight: bold;
font-size: 1em;
padding: 10px;
margin: 5px;
margin: 5px 0;
word-break: break-all;
}
.grabLinks a:hover {
@@ -1689,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;
@@ -1803,24 +2007,32 @@ input[type=checkbox] {
font-size: 100%;
}
}
.hideLinksClass {
background-color: var(--container-color);
width:1191px;
padding: 10px;
margin: 10px;
}
.directorContainer {
background-color: var(--container-color);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
margin: 10px;
padding: 5px 10px;
max-width: 1190px
grid-template-columns: 1fr ;
margin: 10px 0px 10px 10px;
padding: 10px;
max-width: 1191px
}
#directorLinksButton{
cursor:pointer;
}
.directorContainer.half {
background-color: var(--container-color);
display: grid;
grid-template-columns: 1fr 1fr;
padding: 5px 20px;
max-width: 1190px
padding: 10px 10px;
width: 591px;
}
.directorBlock {
cursor: grab;
padding: 10px 10px 50px 10px;
padding: 10px 10px 5px 10px;
margin: 10px;
color: white;
position:relative;
@@ -1831,16 +2043,15 @@ input[type=checkbox] {
background-color: var(--blue-accent);
}
.directorBlock:nth-child(2) {
background-color: var(--red-accent);
}
.directorBlock:nth-child(3) {
background-color: var(--green-accent);
}
.directorBlock:nth-child(4) {
.directorBlock:nth-child(3) {
background-color: var(--olive-accent);
}
.directorBlock:nth-child(4) {
background-color: var(--red-accent);
}
.directorBlock button {
position: absolute;
bottom: 0;
margin: 10px;
}
@@ -1854,8 +2065,9 @@ input[type=checkbox] {
}
.directorBlock h2 {
text-transform: uppercase;
font-size: 1em;
margin-bottom: 10px;
margin-left: 5px;
font-size:1.2em;
}
#toggleroomnotes {
grid-column: 4;
@@ -1864,7 +2076,7 @@ input[type=checkbox] {
div#roomnotes2 {
background: var(--container-color);
padding: 10px !important;
margin: 0px var(--regular-margin);
margin: 0 var(--regular-margin) 10px var(--regular-margin);
width: 100%;
}
.directorsgrid .directorContainer:nth-child(2) button {
@@ -1910,7 +2122,6 @@ i.las.la-circle {
text-align: right;
margin: 5px;
font-size: 0.7em;
cursor: copy;
text-overflow: ellipsis;
overflow: hidden;
}
@@ -1936,7 +2147,7 @@ i.las.la-circle {
}
div#guestFeeds {
background: var(--container-color);
padding: 0 0 15px 20px;
padding: 5px 0 15px 20px;
display: inline-block;
margin: 0px var(--regular-margin);
}
@@ -2013,65 +2224,45 @@ span#guestTips {
color: white;
padding: 5px 10px;
background: rgba(0, 0, 0, .5);
font-size: 1em;
pointer-events:none;
font-size: 4vh;
}
.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;
font-size: 4vh;
}
.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;
font-size: 4vh;
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;
font-size: 4vh;
border-radius: 5px;
font-size: 0.8em;
}
.video-label.ninjablue {
position: absolute;
bottom: 5%;
left: 0;
background: #141926;
padding: 10px 5%;
font-size: 4vh;
}
.video-label.toprounded {
position: absolute;
top: 0;
bottom: unset;
background: rgb(0 0 0 / 70%);
padding: 10px 5%;
font-size: 4vh;
left: 50%;
transform: translateX(-50%);
width: 50%;
@@ -2081,15 +2272,14 @@ span#guestTips {
text-transform: uppercase;
letter-spacing: 3;
box-shadow: 0px 0px 10px #00000059;
font-size: 0.7em
}
.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;
font-size: 5vh;
position: absolute;
bottom: 2vh;
left: 0;
width: 100%;
text-align: center;
}
@@ -2099,18 +2289,38 @@ 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: 2vh;
right: 2vh;
position: absolute;
color:white;
border-radius: 2vh;
background-color:#b11313;
padding: 2px 2px 2px 1px;
}
#help_directors_room{
cursor:pointer;
}
.iframeblob{
padding-top:18px;
text-align: left;
width: 600px;
display: block;
margin: auto;
}
#shareScreenGear{
display:none;
}
@@ -2139,46 +2349,54 @@ span#guestTips {
position: absolute;
}
.director-link-icons {
font-size: 1.5em;
float: left;
bottom: 4px;
position: relative;
margin-right: 9px;
}
.switch {
position: relative;
display: inline-block;
margin:5px 5px 10px 5px;
width: 40px;
height: 24px;
margin:5px 5px 10px 5px;
bottom:20px;
border-radius: 2px;
display: inline-block;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
opacity: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: .3s;
transition: .3s;
position: absolute;
}
.slider:before {
position: absolute;
content: "";
height: 17px;
width: 17px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: .3s;
transition: .3s;
position: absolute;
}
input:checked + .slider {
background-color: #86b98f;
}
@@ -2191,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%;
}
}

21098
main.js

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 757 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

1
media/hd.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 650 B

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 299 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
media/robot.mp3 Normal file

Binary file not shown.

BIN
media/screenshare.webm Normal file

Binary file not shown.

1
media/sd.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 733 B

View File

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

2
media/svg.md Normal file
View File

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

View File

@@ -97,6 +97,7 @@ canvas {
padding: 20px 0px;
border: 1px solid #383838;
cursor: pointer;
max-height:300px;
}
#log ul {

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 ||
@@ -66,11 +75,8 @@
iframe.allowfullscreen ="true";
//iframe.allow = "autoplay";
var srcString =
"./?push=" +
streamID +
"&cleanoutput&privacy&webcam&audiodevice=0&fullscreen&transparent";
var srcString = "./?push=" + streamID + "&cleanoutput&privacy&speedtest&webcam&audiodevice=0&fullscreen&transparent";
if (urlParams.has("turn")) {
srcString = srcString + "&turn=" + urlParams.get("turn");
}
@@ -109,7 +115,7 @@
var iframeContainer = document.createElement("span");
iframe.allow = "autoplay";
var srcString = "./?view=" + streamID + "&cleanoutput&privacy&noaudio";
var srcString = "./?view=" + streamID + "&cleanoutput&privacy&noaudio&scale=0";
if (urlParams.has("turn")) {
srcString = srcString + "&turn=" + urlParams.get("turn");
@@ -254,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];
}
});

4
thirdparty/StreamSaver.js vendored Normal file
View File

@@ -0,0 +1,4 @@
/* global chrome location ReadableStream define MessageChannel TransformStream */
// https://github.com/jimmywarting/StreamSaver.js
// MIT License
((e,t)=>{"undefined"!=typeof module?module.exports=t():"function"==typeof define&&"object"==typeof define.amd?define(t):this.streamSaver=t()})(0,()=>{"use strict";const e="object"==typeof window?window:this;e.HTMLElement||console.warn("streamsaver is meant to run on browsers main thread");let t=null,a=!1;const r=e.WebStreamsPolyfill||{},n=e.isSecureContext;let o=/constructor/i.test(e.HTMLElement)||!!e.safari||!!e.WebKitPoint;const s=n||"MozAppearance"in document.documentElement.style?"iframe":"navigate",i={createWriteStream:function(r,m,d){let c={size:null,pathname:null,writableStrategy:void 0,readableStrategy:void 0},p=0,f=null,u=null,w=null;Number.isFinite(m)?([d,m]=[m,d],console.warn("[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream"),c.size=d,c.writableStrategy=m):m&&m.highWaterMark?(console.warn("[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream"),c.size=d,c.writableStrategy=m):c=m||{};if(!o){t||(t=n?l(i.mitm):function(t){const a=document.createDocumentFragment(),r={frame:e.open(t,"popup","width=200,height=100"),loaded:!1,isIframe:!1,isPopup:!0,remove(){r.frame.close()},addEventListener(...e){a.addEventListener(...e)},dispatchEvent(...e){a.dispatchEvent(...e)},removeEventListener(...e){a.removeEventListener(...e)},postMessage(...e){r.frame.postMessage(...e)}},n=t=>{t.source===r.frame&&(r.loaded=!0,e.removeEventListener("message",n),r.dispatchEvent(new Event("load")))};return e.addEventListener("message",n),r}(i.mitm)),u=new MessageChannel,r=encodeURIComponent(r.replace(/\//g,":")).replace(/['()]/g,escape).replace(/\*/g,"%2A");const o={transferringReadable:a,pathname:c.pathname||Math.random().toString().slice(-6)+"/"+r,headers:{"Content-Type":"application/octet-stream; charset=utf-8","Content-Disposition":"attachment; filename*=UTF-8''"+r}};c.size&&(o.headers["Content-Length"]=c.size);const m=[o,"*",[u.port2]];if(a){const e="iframe"===s?void 0:{transform(e,t){if(!(e instanceof Uint8Array))throw new TypeError("Can only wirte Uint8Arrays");p+=e.length,t.enqueue(e),f&&(location.href=f,f=null)},flush(){f&&(location.href=f)}},t=(w=new i.TransformStream(e,c.writableStrategy,c.readableStrategy)).readable;u.port1.postMessage({readableStream:t},[t])}u.port1.onmessage=(e=>{e.data.download&&("navigate"===s?(t.remove(),t=null,p?location.href=e.data.download:f=e.data.download):(t.isPopup&&(t.remove(),t=null,"iframe"===s&&l(i.mitm)),l(e.data.download)))}),t.loaded?t.postMessage(...m):t.addEventListener("load",()=>{t.postMessage(...m)},{once:!0})}let g=[];return!o&&w&&w.writable||new i.WritableStream({write(e){if(!(e instanceof Uint8Array))throw new TypeError("Can only wirte Uint8Arrays");o?g.push(e):(u.port1.postMessage(e),p+=e.length,f&&(location.href=f,f=null))},close(){if(o){const e=new Blob(g,{type:"application/octet-stream; charset=utf-8"}),t=document.createElement("a");t.href=URL.createObjectURL(e),t.download=r,t.click()}else u.port1.postMessage("end")},abort(){g=[],u.port1.postMessage("abort"),u.port1.onmessage=null,u.port1.close(),u.port2.close(),u=null}},c.writableStrategy)},WritableStream:e.WritableStream||r.WritableStream,supported:!0,version:{full:"2.0.5",major:2,minor:0,dot:5},mitm:"./thirdparty/mitm.html?version=2.0.0"};function l(e){if(!e)throw new Error("meh");const t=document.createElement("iframe");return t.hidden=!0,t.src=e,t.loaded=!1,t.name="iframe",t.isIframe=!0,t.postMessage=((...e)=>t.contentWindow.postMessage(...e)),t.addEventListener("load",()=>{t.loaded=!0},{once:!0}),document.body.appendChild(t),t}try{new Response(new ReadableStream),!n||"serviceWorker"in navigator||(o=!0)}catch(e){o=!0}return(e=>{try{e()}catch(e){}})(()=>{const{readable:e}=new TransformStream,t=new MessageChannel;t.port1.postMessage(e,[e]),t.port1.close(),t.port2.close(),a=!0,Object.defineProperty(i,"TransformStream",{configurable:!1,writable:!1,value:TransformStream})}),i});

File diff suppressed because it is too large Load Diff

10
thirdparty/adapter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
thirdparty/mitm.html vendored Normal file
View File

@@ -0,0 +1,7 @@
<!--
https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html
// MIT License
-->
<script>
let keepAlive=()=>{keepAlive=(()=>{});var e=location.href.substr(0,location.href.lastIndexOf("/"))+"/ping",a=setInterval(()=>{sw?sw.postMessage("ping"):fetch(e).then(e=>e.text(!e.ok&&clearInterval(a)))},1e4)},messages=[];window.onmessage=(e=>messages.push(e));let sw=null,scope="";function registerWorker(){return navigator.serviceWorker.getRegistration("./").then(e=>e||navigator.serviceWorker.register("sw.js",{scope:"./thirdparty/"})).then(e=>{const a=e.installing||e.waiting;return scope=e.scope,(sw=e.active)||new Promise(r=>{a.addEventListener("statechange",fn=(()=>{"activated"===a.state&&(a.removeEventListener("statechange",fn),sw=e.active,r())}))})})}function onMessage(e){let{data:a,ports:r,origin:t}=e;if(!r||!r.length)throw new TypeError("[StreamSaver] You didn't send a messageChannel");if("object"!=typeof a)throw new TypeError("[StreamSaver] You didn't send a object");a.origin=t,a.referrer=a.referrer||document.referrer||t,a.streamSaverVersion=new URLSearchParams(location.search).get("version"),"1.2.0"===a.streamSaverVersion&&console.warn("[StreamSaver] please update streamsaver"),a.headers?new Headers(a.headers):console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts"),"string"==typeof a.filename&&(console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option"),a.filename=a.filename.replace(/\//g,":")),a.size&&console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option"),a.readableStream&&console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm"),a.pathname||(console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)"),a.pathname=Math.random().toString().slice(-6)+"/"+a.filename),a.pathname=a.pathname.replace(/^\/+/g,"");let n=t.replace(/(^\w+:|^)\/\//,"");if(a.url=new URL(`${scope+n}/${a.pathname}`).toString(),!a.url.startsWith(`${scope+n}/`))throw new TypeError("[StreamSaver] bad `data.pathname`");const s=a.readableStream?[r[0],a.readableStream]:[r[0]];return a.readableStream||a.transferringReadable||keepAlive(),sw.postMessage(a,s)}window.opener&&window.opener.postMessage("StreamSaver::loadedPopup","*"),navigator.serviceWorker?registerWorker().then(()=>{window.onmessage=onMessage,messages.forEach(window.onmessage)}):keepAlive();
</script>

4
thirdparty/polyfill.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
thirdparty/polyfill.min.js.map vendored Normal file

File diff suppressed because one or more lines are too long

4
thirdparty/sw.js vendored Normal file
View File

@@ -0,0 +1,4 @@
/* global self ReadableStream Response */
// https://github.com/jimmywarting/StreamSaver.js/blob/master/sw.js
// MIT License
self.addEventListener("install",()=>{self.skipWaiting()}),self.addEventListener("activate",e=>{e.waitUntil(self.clients.claim())});const map=new Map;function createStream(e){return new ReadableStream({start(t){e.onmessage=(({data:e})=>{if("end"===e)return t.close();"abort"!==e?t.enqueue(e):t.error("Aborted the download")})},cancel(){console.log("user aborted")}})}self.onmessage=(e=>{if("ping"===e.data)return;const t=e.data,n=t.url||self.registration.scope+Math.random()+"/"+("string"==typeof t?t:t.filename),a=e.ports[0],s=new Array(3);s[1]=t,s[2]=a,e.data.readableStream?s[0]=e.data.readableStream:e.data.transferringReadable?a.onmessage=(e=>{a.onmessage=null,s[0]=e.data.readableStream}):s[0]=createStream(a),map.set(n,s),a.postMessage({download:n})}),self.onfetch=(e=>{const t=e.request.url;if(t.endsWith("/ping"))return e.respondWith(new Response("pong"));const n=map.get(t);if(!n)return null;const[a,s,o]=n;map.delete(t);const r=new Headers({"Content-Type":"application/octet-stream; charset=utf-8","Content-Security-Policy":"default-src 'none'","X-Content-Security-Policy":"default-src 'none'","X-WebKit-CSP":"default-src 'none'","X-XSS-Protection":"1; mode=block"});let i=new Headers(s.headers||{});i.has("Content-Length")&&r.set("Content-Length",i.get("Content-Length")),i.has("Content-Disposition")&&r.set("Content-Disposition",i.get("Content-Disposition")),s.size&&(console.warn("Depricated"),r.set("Content-Length",s.size));let l="string"==typeof s?s:s.filename;l&&(console.warn("Depricated"),l=encodeURIComponent(l).replace(/['()]/g,escape).replace(/\*/g,"%2A"),r.set("Content-Disposition","attachment; filename*=UTF-8''"+l)),e.respondWith(new Response(a,{headers:r})),o.postMessage({debug:"Download started"})});

55669
thirdparty/video.js vendored

File diff suppressed because it is too large Load Diff

52728
thirdparty/videojs-vr.js vendored

File diff suppressed because one or more lines are too long

31
thirdparty/webmidi.js vendored Normal file

File diff suppressed because one or more lines are too long

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,98 +1,107 @@
{
"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": "Chat aan/uit",
"mute-the-speaker": "Demp de Spreker",
"mute-the-mic": "Demp de Mic",
"disable-the-camera": "Camera uitzetten",
"settings": "Instellingen",
"hangup-the-call": "Gesprek ophangen",
"show-help-info": "Geef Help weer",
"language-options": "Taal opties",
"tip-hold-ctrl-command-to-select-multiple": "tip: Houd CTRL (command) vast om meerdere te selecteren",
"ideal-for-1080p60-gaming-if-your-computer-and-upload-are-up-for-it": "Ideaal voor 1080p60 game streaming, als de pc krachtig genoeg is",
"better-video-compression-and-quality-at-the-cost-of-increased-cpu-encoding-load": "Betere videocompressie en kwaliteit ten koste van verhoogde CPU encodering belasting",
"disable-digital-audio-effects-and-increase-audio-bitrate": "Schakel digitale audio effecten uit en verhoogt audio bitrate",
"the-guest-will-not-have-a-choice-over-audio-options": "Gasten kunnen audio opties niet wijzigen",
"the-guest-will-only-be-able-to-select-their-webcam-as-an-option": "Gast kan alleen een webcam selecteren",
"hold-ctrl-and-the-mouse-wheel-to-zoom-in-and-out-remotely-of-compatible-video-streams": "Scroll in en uit door CTRL vast te houden en te scrollen met d emuis (wanneer mogelijk)",
"add-a-password-to-make-the-stream-inaccessible-to-those-without-the-password": "Voeg een wachtwoord toe en eis deze voor toegang",
"add-the-guest-to-a-group-chat-room-it-will-be-created-automatically-if-needed-": "Gast toevoegen aan groepschat.(Word automatisch gemaakt)",
"customize-the-room-settings-for-this-guest": "Geef (kamer) instellingen op voor deze gast",
"hold-ctrl-or-cmd-to-select-multiple-files": "Meerdere selecteren? CTRL vasthouden tijdens selecteren",
"enter-an-https-url": "Voer een HTTPS URL in",
"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",
"low-quality-preview": "Low-Quality Preview",
"high-quality-preview": "High-Quality Preview",
"send-direct-message": "Send Direct Message",
"advanced-settings-and-remote-control": "Advanced Settings and Remote Control",
"toggle-voice-chat-with-this-guest": "Toggle Voice Chat with this Guest",
"join-by-room-name-here": "Enter a room name to quick join",
"join-room": "Join room",
"share-a-screen-with-others": "Share a Screen with others",
"alert-the-host-you-want-to-speak": "Alert the host you want to speak",
"record-your-stream-to-disk": "Record your stream to disk",
"cancel-the-director-s-video-audio": "Cancel the Director's Video/Audio",
"submit-any-error-logs": "Submit any error logs",
"add-group-chat-to-obs": "Add Group Chat to OBS",
"for-large-group-rooms-this-option-can-reduce-the-load-on-remote-guests-substantially": "For large group rooms, this option can reduce the load on remote guests substantially",
"which-video-codec-would-you-want-used-by-default-": "Which video codec would you want used by default?",
"you-ll-enter-as-the-room-s-director": "You'll enter as the room's director",
"add-your-camera-to-obs": "Add your Camera to OBS",
"remote-screenshare-into-obs": "Remote Screenshare into OBS",
"create-reusable-invite": "Create Reusable Invite",
"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.",
"more-options": "More Options",
"youtube-video-demoing-how-to-do-this": "Youtube Video demoing how to do this",
"invite-a-guest-or-camera-source-to-publish-into-the-group-room": "Invite a guest or camera source to publish into the group room",
"if-enabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "If enabled, the invited guest will not be able to see or hear anyone in the room.",
"use-this-link-in-the-obs-browser-source-to-capture-the-video-or-audio": "Use this link in the OBS Browser Source to capture the video or audio",
"if-enabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "If enabled, you must manually add a video to a scene for it to appear.",
"disables-echo-cancellation-and-improves-audio-quality": "Disables Echo Cancellation and improves audio quality",
"audio-only-sources-are-visually-hidden-from-scenes": "Audio-only sources are visually hidden from scenes",
"guest-will-be-prompted-to-enter-a-display-name": "Guest will be prompted to enter a Display Name",
"display-names-will-be-shown-in-the-bottom-left-corner-of-videos": "Display Names will be shown in the bottom-left corner of videos",
"request-1080p60-from-the-guest-instead-of-720p60-if-possible": "Request 1080p60 from the Guest instead of 720p60, if possible",
"the-default-microphone-will-be-pre-selected-for-the-guest": "The default microphone will be pre-selected for the guest",
"the-default-camera-device-will-selected-automatically": "The default camera device will selected automatically",
"the-guest-won-t-have-access-to-changing-camera-settings-or-screenshare": "The guest won't have access to changing camera settings or screenshare",
"the-guest-will-not-see-their-own-self-preview-after-joining": "The guest will not see their own self-preview after joining",
"guests-will-have-an-option-to-poke-the-director-by-pressing-a-button": "Guests will have an option to poke the Director by pressing a button",
"add-an-audio-compressor-to-the-guest-s-microphone": "Add an audio compressor to the guest's microphone",
"add-an-equalizer-to-the-guest-s-microphone-that-the-director-can-control": "Add an Equalizer to the guest's microphone that the director can control",
"the-guest-can-only-see-the-director-s-video-if-provided": "The guest can only see the Director's video, if provided",
"the-guest-s-microphone-will-be-muted-on-joining-they-can-unmute-themselves-": "The guest's microphone will be muted on joining. They can unmute themselves.",
"have-the-guest-join-muted-so-only-the-director-can-unmute-the-guest-": "Have the guest join muted, so only the director can Unmute the guest.",
"make-the-invite-url-encoded-so-parameters-are-harder-to-tinker-with-by-guests": "Make the invite URL encoded, so parameters are harder to tinker with by guests",
"move-the-user-to-another-room-controlled-by-another-director": "Move the user to another room, controlled by another director",
"send-a-direct-message-to-this-user-": "Send a Direct Message to this user.",
"remotely-change-the-volume-of-this-guest": "Remotely change the volume of this guest",
"mute-this-guest-everywhere": "Mute this guest everywhere",
"start-recording-this-remote-stream-to-this-local-drive-experimental-": "Start Recording this remote stream to this local drive. *experimental*'",
"the-remote-guest-will-record-their-local-stream-to-their-local-drive-experimental-": "The Remote Guest will record their local stream to their local drive. *experimental*",
"shift-this-video-down-in-order": "Shift this Video Down in Order",
"current-index-order-of-this-video": "Current Index Order of this Video",
"shift-this-video-up-in-order": "Shift this Video Up in Order",
"remote-audio-settings": "Remote Audio Settings",
"advanced-video-settings": "Advanced Video Settings",
"activate-or-reload-this-video-device-": "Activate or Reload this video device."
"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": "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 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",
"alert-the-host-you-want-to-speak": "Geef de director een seintje",
"record-your-stream-to-disk": "Streamopname naar lokale opslag",
"cancel-the-director-s-video-audio": "Annuleer de Director zijn Video/Audio",
"submit-any-error-logs": "Verzend foutmeldingslog",
"add-group-chat-to-obs": "Voeg een groepschat toe aan OBS",
"for-large-group-rooms-this-option-can-reduce-the-load-on-remote-guests-substantially": "Bij grotere groepen kan deze optie voor een lagere belasting aan gast zijde zorgen",
"which-video-codec-would-you-want-used-by-default-": "Welke video codec wil je standaard gebruiken?",
"you-ll-enter-as-the-room-s-director": "Je gaat de kamer binnen als Director",
"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-": "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",
"if-enabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "Wanneer deze optie aan staat kan de gast niemand zien of horen",
"use-this-link-in-the-obs-browser-source-to-capture-the-video-or-audio": "Gebruik deze link als OBS Browser source om video/audio binnen te halen",
"if-enabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "Wanneer deze optie aanstaat moet je video handmatig toevoegen aan een scene om hem te zien.",
"disables-echo-cancellation-and-improves-audio-quality": "Schakelt Echo Cancellation uit en verbetert audio kwaliteit",
"audio-only-sources-are-visually-hidden-from-scenes": "Bronnen met alleen audio niet laten zien in de scene",
"guest-will-be-prompted-to-enter-a-display-name": "Vraag gasten om een naam om weer te geven",
"display-names-will-be-shown-in-the-bottom-left-corner-of-videos": "Namen van gasten komen links onder in de hoek van de video",
"request-1080p60-from-the-guest-instead-of-720p60-if-possible": "Vraag om 1080p60 i.p.v. 720p60, wanneer mogelijk",
"the-default-microphone-will-be-pre-selected-for-the-guest": "Voor deze gast standaard microfoon selecteren vooraf",
"the-default-camera-device-will-selected-automatically": "Standaard camera word automatisch geselecteerd",
"the-guest-won-t-have-access-to-changing-camera-settings-or-screenshare": "De gast kan hierdoor niet wisselen tussen camera en scherm delen",
"the-guest-will-not-see-their-own-self-preview-after-joining": "Hierdoor zit de gast zichzelf niet nadat deze het gesprek is binnengekomen",
"guests-will-have-an-option-to-poke-the-director-by-pressing-a-button": "Gasten krijgen een optie de director een verzoek tot aandacht te sturen",
"add-an-audio-compressor-to-the-guest-s-microphone": "Voeg een audio compressor toe aan de gast zijn microfoon",
"add-an-equalizer-to-the-guest-s-microphone-that-the-director-can-control": "Voeg een audio equalizer toe aan gast, onder controle van director",
"the-guest-can-only-see-the-director-s-video-if-provided": "Gat kan alleen de director zien,wanneer deze aanwezig is",
"the-guest-s-microphone-will-be-muted-on-joining-they-can-unmute-themselves-": "Microfoon van gast standaard uit, kunnen zichzelf wel aanzetten",
"have-the-guest-join-muted-so-only-the-director-can-unmute-the-guest-": "Microfoon van gast standaard uit, alleen director kan deze aanzetten",
"make-the-invite-url-encoded-so-parameters-are-harder-to-tinker-with-by-guests": "Encodeer de invitatie URL. Om aanpassingen door gast moeilijker te maken",
"move-the-user-to-another-room-controlled-by-another-director": "Verplaats gast naar andere room. Beheerd door een andere director",
"send-a-direct-message-to-this-user-": "Stuur een Direct Message naar deze gast",
"remotely-change-the-volume-of-this-guest": "Op afstand veranderen volume gast",
"mute-this-guest-everywhere": "Deze gast niet meer hoorbaar maken overal",
"start-recording-this-remote-stream-to-this-local-drive-experimental-": "Start opname van de remote audio/video stream op de lokale drive *experimental*'",
"the-remote-guest-will-record-their-local-stream-to-their-local-drive-experimental-": "Zet opname aan bij gasten zelf op lokale schijf van gast *experimental*",
"shift-this-video-down-in-order": "Schuif deze video lager in orde",
"current-index-order-of-this-video": "Huidige index plek van deze video",
"shift-this-video-up-in-order": "Schuif deze video hoger in orde",
"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-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-": "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",
"rooms-allow-for": "Kamers maken eenvoudige groepsgespreken en geavanceerd beheer van meerdere streams tegelijk mogelijk.",
"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",
"enter-the-rooms-control": "Ga de Kamer's Controle Centrum in",
@@ -100,7 +109,7 @@
"added-notes": "\n\t\t\t\t<u><i>Notities:</i></u>\n\t\t\t\t<li>Iedereen kan de kamer binnenkomen als ze de naam kennen, dus hou hem uniek</li>\n\t\t\t\t<li>Meer dan vier (4) mensen in een kamer is niet aan te raden vanwege prestatie redenen, maar is afhankelijk van uw hardware.</li>\n\t\t\t\t<li>Bij iOS apparaten is de video alleen zichtbaar voor de regiseur. Dit is een hardware beperking.</li>\n\t\t\t\t<li>De \"Opname\" optie is nieuw en is experimenteel.</li>\n\t\t\t\t<li>U moet een video stroom \"Toevoegen\" aan de \"Groeps Scene\" om het hier te tonen.</li>\n\t\t\t\t<li>Er is een nieuwe \"uitgebreid volledig scherm\" knop toegevoegd aan het Gasten scherm.</li>\n\t\t\t\t",
"back": "Terug",
"add-your-camera": "Voeg je Camera toe",
"ask-for-permissions": "Allow Access to Camera/Microphone",
"ask-for-permissions": "Geef toestemming voor gebruik Camera/Microfoon",
"waiting-for-camera": "Wachten op het Laden van de Camera",
"video-source": "Video bron",
"max-resolution": "Max Resolutie",
@@ -117,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:",
@@ -130,63 +139,66 @@
"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": "Send",
"available-languages": "Available Languages:",
"add-more-here": "Add More Here!",
"waiting-for-camera-to-load": "waiting-for-camera-to-load",
"send-chat": "Verstuur",
"available-languages": "Beschikbare talen:",
"add-more-here": "Voer hier meer toe!",
"waiting-for-camera-to-load": "Wachten tot camera geladen is",
"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",
"guests-only-see-director": "Guests can only see the Director's Video",
"default-codec-select": "Preferred Video Codec: ",
"obfuscate_url": "Obfuscate the Invite URL",
"share-your-mic": "Deel je microfoon",
"share-your-camera": "Deel je camera",
"share-your-screen": "Deel je scherm",
"join-room-with-mic": "Ga de kamer binnen met microfoon",
"share-screen-with-room": "Deel je scherm met de kamer",
"join-room-with-camera": "Ga de kamer binnen met camera",
"click-start-to-join": "Druk op start om erin te gaan",
"guests-only-see-director": "Gasten kunnen alleen de directors video zien",
"default-codec-select": "Geprefereerde Video Codec: ",
"obfuscate_url": "Verhul originele uitnodigingslink",
"hide-the-links": " LINKS (GUEST INVITES &amp; SCENES)",
"invite-users-to-join": "Guests can use the link to join the group room",
"this-is-obs-browser-source-link": "Use in OBS or other studio software to capture the group video mix",
"invite-users-to-join": "Gasten kunnen de link gebruiken om de kamer binnen te gaan",
"this-is-obs-browser-source-link": "Gebruik deze link om de video mix te gebruiken in OBS of andere studio software",
"mute-scene": "mute in scene",
"mute-guest": "mute guest",
"record-local": " Record Local",
"record-remote": " Record Remote",
"record-local": " Lokale opname",
"record-remote": " Opname remote",
"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 Settings"
"advanced-audio-settings": "<i class=\"las la-sliders-h\"></i> Audio instellingen",
"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": "Join by Room Name here",
"enter-a-room-name-here": "Enter a Room Name here",
"optional-room-password-here": "Optional room password here",
"give-this-media-source-a-name-optional-": "Give this media source a name (optional)",
"add-an-optional-password": "Add an optional password",
"enter-room-name-here": "Enter Room name here",
"enter-chat-message-to-send-here": "Enter chat message to send here"
"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)",
"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>