mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b56b5ea33 | ||
|
|
41e1b4dc02 | ||
|
|
a22388a24b | ||
|
|
10ef9193b0 | ||
|
|
554af144f2 | ||
|
|
36ae6c0f1e | ||
|
|
b949986182 | ||
|
|
b36b52ad8b | ||
|
|
de4f981455 | ||
|
|
eae305913f | ||
|
|
14c13b02de | ||
|
|
5bb7bbdd5c | ||
|
|
ea1895c129 | ||
|
|
80295a7a9a | ||
|
|
ec1ec458aa | ||
|
|
6b6e2b5a3f | ||
|
|
a67e585eb1 | ||
|
|
c6690fcdac | ||
|
|
1f2c193956 | ||
|
|
1ba4d61486 | ||
|
|
e1f7ccacfe | ||
|
|
e366bdfa4c | ||
|
|
62292e71ea | ||
|
|
06379c1f1f | ||
|
|
b471096842 | ||
|
|
85b336bafd | ||
|
|
fae0b094ac | ||
|
|
a028a753e8 | ||
|
|
a24e5ae667 | ||
|
|
0f7b09526d | ||
|
|
f335813c5e | ||
|
|
f62f31329d | ||
|
|
cbc7971fa5 | ||
|
|
838acf07ac | ||
|
|
864969ebae | ||
|
|
474e142908 | ||
|
|
e5a7b29a46 | ||
|
|
9cc109c254 | ||
|
|
8338573c10 | ||
|
|
46b00b1ce1 | ||
|
|
d2dd662d10 | ||
|
|
c89fcc0064 | ||
|
|
8d24e23232 | ||
|
|
474155e0d9 | ||
|
|
166f933953 | ||
|
|
43aa080bce | ||
|
|
4e273219f8 | ||
|
|
1c3d45ae6f | ||
|
|
c7a801c0e0 | ||
|
|
8c4c1cd040 | ||
|
|
c487bed906 | ||
|
|
e762910907 | ||
|
|
f0cfa1effd | ||
|
|
8ee5be2960 | ||
|
|
1934097875 | ||
|
|
4e922c13aa | ||
|
|
17403d816b | ||
|
|
944e619f0a | ||
|
|
d6d4d93dab | ||
|
|
5c7648d86a |
456
IFRAME.md
456
IFRAME.md
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -20,10 +20,10 @@ And Here is another video series touching on some more advanced settings: https:
|
||||
|
||||
Check the subreddit for added use cases, advanced features, and support. Advanced features includes high-quality audio modes, custom video resolutions, and more.
|
||||
|
||||
MacOS users will face some challenges in using OBS 25/26, but there are workarounds. Please see the subreddit or the Wiki.
|
||||
MacOS users will face some challenges in using OBS 25/26, but there are workarounds. Please see the subreddit or [the Wiki](https://github.com/steveseguin/obsninja/wiki).
|
||||
|
||||
## What's in this repo?
|
||||
This repo contains software for OBS.Ninja, including the HTML landing page for its Electron Capture app offering. A sample config file and instructions for setting up a TURN server (video relay server), is also provided. You may also find the Wiki for the project in this repo, which contains added information on how to use the software.
|
||||
This repo contains software for OBS.Ninja, including the HTML landing page for its Electron Capture app offering. A sample config file and instructions for setting up a TURN server (video relay server), is also provided. You may also find [the Wiki](https://github.com/steveseguin/obsninja/wiki) for the project in this repo, which contains added information on how to use the software.
|
||||
|
||||
## How to Deploy this Repo:
|
||||
To use, just download and host the files on a HTTPS-enabled webserver. You may want to hide the .html extensions within your HTTP server as well, else the generated links will not work. See [here](https://github.com/steveseguin/obsninja/blob/master/install.md) for added details and alternative install options.
|
||||
@@ -33,7 +33,7 @@ Directions on how to deploy a TURN server are listed in the turnserver.md file.
|
||||
## Server side / API software?
|
||||
Since OBS.Ninja uses peer-2-peer technology, video connections are made directly between viewer and publisher in 90% of cases. Hosting a TURN server yourself may help improve performance, but less than 1% of users will see any benefit of this. Details on how to deploy a TURN server are provided. For those capable of hosting their own TURN server, that would be appreciated if possible, as TURN servers are the only real cost incurred by OBS.Ninja at present. (other than time, of course)
|
||||
|
||||
Other than TURN servers, OBS.Ninja also uses public STUN servers and a hosted handshake server. These are used to facilitate the initial setup of peer connections and are generally not required after a peer connection is established. These servers are free to access and use, even for private deployments.
|
||||
Other than TURN servers, OBS.Ninja also uses public STUN servers and a hosted handshake server. These are used to facilitate the initial setup of peer connections and are generally not required after a peer connection is established. These servers are free to access and use, even for private deployments. The handshake server's code is currently not available, so basic access to the Internet is still required to use OBS.Ninja even with a private deployment.
|
||||
|
||||
Development builds of OBS.Ninja may include debugging software, but in-production releases have this removed. Double check to ensure "console.re" debugging is disabled before deployment, just to be safe.
|
||||
|
||||
@@ -63,7 +63,7 @@ https://steves.app/
|
||||
A browser-based studio solution and simplified alternative to OBS, with built-in OBS.Ninja functionality. It is a server-based approach to group interactions and live production. Steve Seguin is affiliated with StageTEn, yet StageTEN is not affiliated with OBS.Ninja.
|
||||
|
||||
## Privacy
|
||||
I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: turnserver.md
|
||||
I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: [turnserver.md](turnserver.md)
|
||||
|
||||
https://obs.ninja may unavoidably use cookies that are exempt from EU laws of requiring notice of their use; they are exempt as they are required and necessary for the technical functioning of the web service. Our webserver is cached by Cloudflare and it provides denial of server protection for the users of OBS.Ninja.
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
103
devices.html
103
devices.html
@@ -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>
|
||||
|
||||
10
iframe.html
10
iframe.html
@@ -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!"}, '*');};
|
||||
|
||||
190
index.html
190
index.html
@@ -55,7 +55,7 @@
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
||||
<link rel="stylesheet" href="./main.css?ver=46" />
|
||||
<link rel="stylesheet" href="./main.css?ver=52" />
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/adapter.min.js"></script>
|
||||
</head>
|
||||
<body id="main" class="hidden">
|
||||
@@ -66,19 +66,22 @@
|
||||
<span itemprop="thumbnail" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<link itemprop="url" href="./media/obsNinja_logo_full.png" />
|
||||
</span>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=26"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=177"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=29"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=181"></script>
|
||||
<input id="zoomSlider" type="range" style="display: none;" />
|
||||
<div id="header">
|
||||
|
||||
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px;">
|
||||
<span data-translate="logo-header">
|
||||
<font id="qos">O</font>BS.Ninja
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div id="head1" style="display: inline-block; padding:1px; position: relative;">
|
||||
<input type="text" autocorrect="off" autocapitalize="none" id="joinroomID" name="joinroomID" size="22" placeholder="Join by Room Name here" alt="Enter a room name to join" title="Enter a room name to quick join" onkeyup="jumptoroom(event)"/>
|
||||
<button onclick="jumptoroom();" role="button" aria-pressed="false" alt="Join room" title="Join room" >GO</button>
|
||||
</div>
|
||||
<div id="head5" class="advanced"></div>
|
||||
<div id="head3" style="display: inline-block;" class="advanced">
|
||||
<font style="color: #888;" id="copythisurl">
|
||||
<span data-translate="copy-this-url">Copy this URL into an OBS "Browser Source"</span> <i style="color: #CCC;" class="las la-long-arrow-alt-right"></i>
|
||||
@@ -103,7 +106,6 @@
|
||||
</div>
|
||||
<div id="head2" class="advanced" style="display: inline-block; text-decoration: none; font-size: 60%; color: white;">
|
||||
<span data-translate="joining-room">You are in room</span>:
|
||||
|
||||
<div id="roomid" style="display: inline-block;"></div>
|
||||
</div>
|
||||
|
||||
@@ -125,12 +127,12 @@
|
||||
<i id="mutetoggle" class="toggleSize las la-microphone my-float" style="position: relative; top: 0.5px;"></i>
|
||||
</div>
|
||||
<div id="mutevideobutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Disable the Camera" alt="Disable the Camera" onclick="toggleVideoMute()" tabindex="19" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="advanced float" style="cursor: pointer;">
|
||||
<i id="mutevideotoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-eye my-float"></i>
|
||||
<i id="mutevideotoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-video my-float"></i>
|
||||
</div>
|
||||
<div id="screensharebutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Share a Screen with others" alt="Share a Screen with others" onclick="toggleScreenShare()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
|
||||
<i id="screensharetoggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-desktop my-float"></i>
|
||||
</div>
|
||||
<div id="screenshare2button" onmousedown="event.preventDefault(); event.stopPropagation();" title="Create a Seconary Stream" alt="Create a Seconary Stream" onclick="createIframePopup()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
|
||||
<div id="screenshare2button" onmousedown="event.preventDefault(); event.stopPropagation();" title="Create a Secondary Stream" alt="Create a Secondary Stream" onclick="createIframePopup()" tabindex="20" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float advanced" style="cursor: pointer;">
|
||||
<i id="screenshare2toggle" onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-tv my-float"></i>
|
||||
</div>
|
||||
<div id="settingsbutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Settings" onclick="toggleSettings()" class="advanced float" tabindex="21" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Toggle the Settings Menu">
|
||||
@@ -146,6 +148,7 @@
|
||||
<i class="toggleSize my-float las la-dot-circle" style="position: relative;" aria-hidden="true"></i>
|
||||
</div>
|
||||
<span id="miniPerformer" style="pointer-events: auto;" class="advanced"></span>
|
||||
<span id="rooms" style="padding-top:3px;padding-left:6px;pointer-events: auto;color:#fff;"></span>
|
||||
|
||||
<div id="hangupbutton2" onmousedown="event.preventDefault(); event.stopPropagation();" title="Cancel the Director's Video/Audio" onclick="hangup2()" class="advanced float" tabindex="25" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Disconnect Direcotor's cam">
|
||||
<i class="toggleSize my-float las la-phone rotate225" aria-hidden="true"></i>
|
||||
@@ -164,7 +167,7 @@
|
||||
<span
|
||||
id="helpbutton"
|
||||
title="Show Help Info"
|
||||
onclick="alert('Email steve@seguin.email if the system breaks or check https://reddit.com/r/obsninja for support.\n\nThe Wiki contains many help guides and advanced settings.\n\nAccess the debug menu by pressing CTRL (command) and Left-Clicking on a video.\n\nMost issues can be fixed by using Wired Internet instead of Wi-Fi.')"
|
||||
onclick="warnUser('For support, please browse https://reddit.com/r/obsninja or join the live chat on Discord at https://discord.obs.ninja.\n\nThe Wiki also contains many help guides and advanced settings, located at https://wiki.obs.ninja.\n\nTo access the video stats menu, hold CTRL (command) and Left-Click on a video. Most video issues can be fixed by using Wired Internet instead of Wi-Fi.')"
|
||||
style="cursor: pointer; display:none;"
|
||||
alt="How to Use This with OBS"
|
||||
>
|
||||
@@ -355,6 +358,25 @@
|
||||
<select id="outputSource" ></select>
|
||||
|
||||
</span>
|
||||
<span id="addPasswordBasic" style="width: 450px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border-bottom: 0;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
margin: 17px 0;
|
||||
max-width: 550px;
|
||||
min-width: 420px;
|
||||
background-color: #f3f3f3;
|
||||
padding: 10px 10px;
|
||||
border: 1px solid #ccc;
|
||||
vertical-align: middle;">
|
||||
<i class="las la-key"></i><span data-translate="add-a-password"> Add a Password:</span>
|
||||
<input type="text" id="passwordBasicInput" placeholder="optional" style="border: solid 1px #AAA;
|
||||
padding: 4px 6px;
|
||||
width: 200px;
|
||||
margin: 0 6px;"/>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="outer close">
|
||||
@@ -562,7 +584,8 @@
|
||||
<input id="fileselector" onchange="session.publishFile(this,event);" type="file" accept="video/*,audio/*" alt="Hold CTRL (or CMD) to select multiple files" title="Hold CTRL (or CMD) to select multiple files" multiple/>
|
||||
<br /><br />
|
||||
<p style="margin:10px">Keep this tab visible if using Chrome, else the video playback will stop</p>
|
||||
<p style="margin:10px">(Media file streaming is still quite experimental)</p>
|
||||
<p style="margin:10px">(Media file streaming is still quite experimental)</p><br />
|
||||
<p style="margin:10px">File Sharing seems to be broken on Chrome v88. <br />Using The Electron Capture app instead of Chrome should work: <a href="https://github.com/steveseguin/electroncapture/releases/tag/1.1.3" style="color:blue"><u>GET IT HERE</u></a><br />You can also <a href="https://github.com/aws/amazon-chime-sdk-js/issues/1031" style="color:blue">turn off hardware-accleration</a> in Chrome/Edge to fix the issue.</p>
|
||||
</div>
|
||||
|
||||
<div class="outer close">
|
||||
@@ -586,9 +609,9 @@
|
||||
<button onclick="previewIframe(getById('iframeURL').value);" >Preview</button>
|
||||
<button onclick="this.innerHTML = 'Update'; session.publishIFrame(getById('iframeURL').value);" >Share</button><br />
|
||||
<small class="iframeblob">
|
||||
<li>Remote website must be CORS/IFrame compatible with full SSL-encryption enabled.</li>
|
||||
<li>Not all websites will work with this feature and many will actively not allow embedding like this.</li>
|
||||
<li>If sharing a Youtube video, try using the embeddable link version and ensure that the video is set to allow embedding.</li>
|
||||
<li>Not all websites will work with this feature as some sites disallow embedding.</li>
|
||||
<li>The site will try to auto-optimize standard Youtube or Twitch links.</li>
|
||||
<li>Remote websites must be CORS/IFrame compatible with full SSL-encryption enabled.</li>
|
||||
</small>
|
||||
<div id="iFramePreview" style=" width: 1280px; height: 720px; margin: auto; padding: 10px;"></div>
|
||||
</div>
|
||||
@@ -602,7 +625,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div id="container-7" class="column columnfade pointer card advanced" style="overflow: hidden;" onclick="window.location = './speedtest';">
|
||||
<div id="container-7" class="column columnfade pointer card advanced" style="overflow: hidden;" onclick="window.location = './speedtest.html';">
|
||||
<h2><span data-translate="run-a-speed-test">Run a Speed Test</span></h2>
|
||||
<i style="margin-top:30px;font-size:600%;overflow:hidden;" class="las la-tachometer-alt"></i>
|
||||
</div>
|
||||
@@ -619,52 +642,37 @@
|
||||
<span data-translate="info-blob">
|
||||
<h2>What is OBS.Ninja</h2>
|
||||
<br />
|
||||
<li>100%
|
||||
<b>free</b>; no downloads; no personal data collection; no sign-in
|
||||
</li>
|
||||
<li>Bring video from your smartphone, computer, or friends directly into your OBS video stream</li>
|
||||
<li>100% <b>free</b>; no downloads; no personal data collection; no sign-in</li>
|
||||
<li>Bring live video from your smartphone, remote computer, or friends directly into OBS or other studio software.</li>
|
||||
<li>We use cutting edge Peer-to-Peer forwarding technology that offers privacy and ultra-low latency</li>
|
||||
<br />
|
||||
<li>Youtube video
|
||||
<i class="lab la-youtube"></i>
|
||||
<a href="https://www.youtube.com/watch?v=vLpRzMjUDaE&list=PLWodc2tCfAH1WHjl4WAOOoRSscJ8CHACe&index=2" alt="Youtube video demoing OBS.Ninja">Demoing it here</a>
|
||||
</li>
|
||||
|
||||
<br />
|
||||
<i>
|
||||
<font style="color: red;">Known issues:</font>
|
||||
</i>
|
||||
<br />
|
||||
<li>
|
||||
MacOS users using OBS will need to update to <a href="https://github.com/obsproject/obs-studio/releases/tag/26.1.2">OBS Studio 26.1.2</a> or resort to
|
||||
window-capturing with the provided <a href="https://github.com/steveseguin/electroncapture">Electron-Capture app</a>.
|
||||
|
||||
</li>
|
||||
<li>If you have <a href="https://github.com/steveseguin/obsninja/wiki/FAQ#video-is-pixelated">"pixel smearing"</a> or corrupted video, try adding <b>&codec=vp9</b> or &codec=h264 to the OBS view link. Using Wi-Fi will make the issue worse.
|
||||
</li>
|
||||
<li>
|
||||
iOS devices may have occasional audio or camera issues, such as no sound or distorted sound. <a href="https://bugs.webkit.org/show_bug.cgi?id=218762">Partially fixed in iOS 14.3</a>
|
||||
If you have <a href="https://github.com/steveseguin/obsninja/wiki/FAQ#video-is-pixelated">"pixel smearing"</a> or corrupted video, try adding <i>&codec=h264</i> or <i>&codec=vp9</i> to the OBS view link. Using Wi-Fi will make the issue worse.
|
||||
</li>
|
||||
<li>
|
||||
Chrome on Android 11 has an issue with the browser freezing at times. To unfreeze it, background the browser and then foreground it again.
|
||||
</li>
|
||||
<li>
|
||||
The VP9 codec on Chromium-based browsers seems to lag when screen-sharing at the moment. Use the OBS Virtual Camera as a capture source instead.
|
||||
</li>
|
||||
<br />
|
||||
🥳 Site Updated: <a href="https://github.com/steveseguin/obsninja/wiki/v16-release-notes">Fed 3rd, 2021</a>. The previous version can be found at
|
||||
<a href="https://obs.ninja/v15/">https://obs.ninja/v15/</a> if you are having new issues.
|
||||
Site Updated: <a href="https://github.com/steveseguin/obsninja/wiki/v16.4-update-notes">March 3rd, 2021</a> (v16.4). The previous version can be found at <a href="https://obs.ninja/v16/">https://obs.ninja/v16/</a> if you are having issues with this minor update.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<h3>
|
||||
<i>
|
||||
Check out the
|
||||
<a href="https://www.reddit.com/r/OBSNinja/">sub-reddit
|
||||
<i class="lab la-reddit-alien"></i> </a>for help and see the <a href="https://github.com/steveseguin/obsninja/wiki/">Wiki for advanced info</a>. I'm also on
|
||||
<a href="https://discord.gg/T4xpQVv">Discord <i class="lab la-discord"></i></a> or email me at steve@seguin.email
|
||||
🛠 For support, see the <a href="https://www.reddit.com/r/OBSNinja/">sub-reddit <i class="lab la-reddit-alien"></i></a> or join the <a href="https://discord.gg/T4xpQVv">Discord <i class="lab la-discord"></i></a>. The <a href="https://github.com/steveseguin/obsninja/wiki/">Wiki is here</a> and my personal email is <i>steve@seguin.email</i>
|
||||
</h3>
|
||||
|
||||
</i>
|
||||
</h3>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</center>
|
||||
@@ -781,13 +789,13 @@
|
||||
<input type="checkbox" data-param="&l" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Ask for Display Name
|
||||
Ask for display name
|
||||
<Br />
|
||||
<label class="switch" title="Display Names will be shown in the bottom-left corner of videos">
|
||||
<input type="checkbox" data-param="&sl" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Show Display Names
|
||||
Show display names
|
||||
</div>
|
||||
<div style="display:inline-block;top: 12px; position: relative; margin-left:10px;">
|
||||
<label class="switch" title="Request 1080p60 from the Guest instead of 720p60, if possible">
|
||||
@@ -800,13 +808,13 @@
|
||||
<input type="checkbox" data-param="&ad" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Auto-select Default Microphone
|
||||
Auto-select default microphone
|
||||
<Br />
|
||||
<label class="switch" title="The default camera device will selected automatically">
|
||||
<input type="checkbox" data-param="&vd" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Auto-select Default Camera
|
||||
Auto-select default camera
|
||||
<Br />
|
||||
<label class="switch" title="The guest won't have access to changing camera settings or screenshare">
|
||||
<input type="checkbox" data-param="&ns" onchange="updateLink(1,this);">
|
||||
@@ -819,50 +827,50 @@
|
||||
<input type="checkbox" data-param="&np" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Disable Self-Preview
|
||||
Disable self-preview
|
||||
<Br />
|
||||
<label class="switch" title="Guests will have an option to poke the Director by pressing a button">
|
||||
<input type="checkbox" data-param="&hand" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Display 'Raise-Hand' button
|
||||
Display 'raise-hand' button
|
||||
<Br />
|
||||
<label class="switch" title="Add an audio compressor to the guest's microphone">
|
||||
<input type="checkbox" data-param="&comp" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Enable Audio Compressor
|
||||
Enable audio compressor
|
||||
<Br />
|
||||
<label class="switch" title="Add an Equalizer to the guest's microphone that the director can control">
|
||||
<input type="checkbox" data-param="&eq" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Enable Equalizer as Option
|
||||
Enable equalizer as option
|
||||
</div>
|
||||
<div style="display:inline-block;top: 12px; position: relative; margin-left:10px; height: 20px;">
|
||||
<label class="switch" title="The guest can only see the Director's video, if provided">
|
||||
<input type="checkbox" data-param="&broadcast" id="broadcastSlider" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Only see the Director's Feed
|
||||
Only see the director's feed
|
||||
<br />
|
||||
<label class="switch" title="The guest's microphone will be muted on joining. They can unmute themselves.">
|
||||
<input type="checkbox" data-param="&m" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Mute Microphone by Default
|
||||
Mute microphone by default
|
||||
<Br />
|
||||
<label class="switch" title="Have the guest join muted, so only the director can Unmute the guest.">
|
||||
<input type="checkbox" data-param="&g=0" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Unmute by Director Only
|
||||
Unmute by director only
|
||||
<Br />
|
||||
<label class="switch" title="Make the invite URL encoded, so parameters are harder to tinker with by guests">
|
||||
<input type="checkbox" data-param="" id="obfuscate_director_1" onchange="updateLink(1,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Obfuscate Link and Parameters
|
||||
Obfuscate link and parameters
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -885,7 +893,7 @@
|
||||
<input type="checkbox" data-param="&sl" onchange="updateLink(3,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Show Display Names
|
||||
Show display names
|
||||
</div>
|
||||
<div style="display:inline-block;top: 12px; height: 20px; position: relative; margin-left:10px;">
|
||||
<label class="switch">
|
||||
@@ -899,7 +907,7 @@
|
||||
<input type="checkbox" data-param="&mono" onchange="updateLink(3,this);">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Force Mono Audio
|
||||
Force mono audio
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -932,18 +940,42 @@
|
||||
<span data-translate="send-direct-chat"><i class="las la-envelope"></i> Message</span>
|
||||
</button>
|
||||
|
||||
<button data-action-type="addToScene" style="grid-column: 1;" data-value="0" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event);">
|
||||
<button data-action-type="addToScene" style="grid-column: 1;" data-value="0" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event, 1);">
|
||||
<i class="las la-plus-square"></i>
|
||||
<span data-translate="add-to-scene">Add to Scene</span>
|
||||
<span data-translate="add-to-scene">add to scene</span>
|
||||
</button>
|
||||
<button data-action-type="mute-scene" style="grid-column: 2;" title="Remotely Mute this Audio in all remote '&scene' views" onclick="directMute(this, event);">
|
||||
<i class="las la-microphone-slash"></i>
|
||||
<span data-translate="mute-scene" >mute in scene</span>
|
||||
</button>
|
||||
|
||||
<font class="tooltip" style="height: 0; border: 0;">
|
||||
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest" oninput="remoteVolumeUI(this)" onclick="remoteVolume(this);" style="grid-column: 1; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/><span class="tooltiptext" style='float: right; overflow: auto; left: 40px; width: 2.5em; top: -13px; margin: 0; position:relative;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;' >100</span></font>
|
||||
<span id="sceneGroup1" style="display:none">
|
||||
<button style="width: 35.2px" data-action-type="add-scene-2" title="Add to Scene 2" onclick="directEnable(this, event, 2);">
|
||||
<span >S2</span>
|
||||
</button>
|
||||
<button style="width:35.2px;" data-action-type="add-scene-3" title="Add to Scene 3" onclick="directEnable(this, event, 3);">
|
||||
<span >S3</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-scene-4" title="Add to Scene 4" onclick="directEnable(this, event, 4);">
|
||||
<span >S4</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
|
||||
<font class="tooltip" style="height: 0; border: 0;">
|
||||
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest" oninput="remoteVolumeUI(this)" onclick="remoteVolume(this);" style="grid-column: 2; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/><span class="tooltiptext" style='float: right; overflow: auto; left: 40px; width: 2.5em; top: -13px; margin: 0; position:relative;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;' >100</span>
|
||||
</font>
|
||||
<span id="sceneGroup2" style="display:none">
|
||||
<button style="width: 35.2px" data-action-type="add-scene-5" title="Add to Scene 5" onclick="directEnable(this, event, 5);">
|
||||
<span >S5</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-scene-6" title="Add to Scene 6" onclick="directEnable(this, event, 6);">
|
||||
<span >S6</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-scene-7" title="Add to Scene 7" onclick="directEnable(this, event, 7);">
|
||||
<span >S7</span>
|
||||
</button>
|
||||
</span>
|
||||
<button data-action-type="mute-guest" style="grid-column: 2;" title="Mute this guest everywhere" onclick="remoteMute(this, event);">
|
||||
<i class="las la-microphone-slash"></i>
|
||||
<span data-translate="mute-guest" >mute guest</span>
|
||||
@@ -991,13 +1023,56 @@
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<button data-action-type="toggle-remote-speaker" style="grid-column: 1;" title="Toggle the remote guest's speaker output" onclick="remoteSpeakerMute(this, event);">
|
||||
<span id="channelGroup1" style="display:none">
|
||||
<button style="width: 35.2px" data-action-type="add-channel" class="pressed" title="Set to Default Audio Channel" onclick="changeChannelOffset(this.dataset.UUID, false);">
|
||||
<span >00</span>
|
||||
</button>
|
||||
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 1" onclick="changeChannelOffset(this.dataset.UUID, 0);">
|
||||
<span >C1</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 2" onclick="changeChannelOffset(this.dataset.UUID, 1);">
|
||||
<span >C2</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<button data-action-type="toggle-remote-speaker" title="Toggle the remote guest's speaker output" onclick="remoteSpeakerMute(this, event);">
|
||||
<i class="las la-volume-off"></i> <span data-translate="toggle-remote-speaker">Deafen Guest</span>
|
||||
</button>
|
||||
|
||||
<span id="channelGroup2" style="display:none" >
|
||||
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 3" onclick="changeChannelOffset(this.dataset.UUID, 2);">
|
||||
<span >C3</span>
|
||||
</button>
|
||||
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 4" onclick="changeChannelOffset(this.dataset.UUID,3);">
|
||||
<span >C4</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 5" onclick="changeChannelOffset(this.dataset.UUID, 4);">
|
||||
<span >C5</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<button data-action-type="toggle-remote-display" style="grid-column: 2;" title="Toggle the remote guest's display output" onclick="remoteDisplayMute(this, event);">
|
||||
<i class="las la-eye-slash"></i> <span data-translate="toggle-remote-display">Blind Guest</span>
|
||||
</button>
|
||||
|
||||
|
||||
<span id="channelGroup3" style="display:none" >
|
||||
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 6" onclick="changeChannelOffset(this.dataset.UUID, 5);">
|
||||
<span >C6</span>
|
||||
</button>
|
||||
<button style="width:35.2px;" data-action-type="add-channel" title="Set to Audio Channel 7" onclick="changeChannelOffset(this.dataset.UUID, 6);">
|
||||
<span >C7</span>
|
||||
</button>
|
||||
<button style="width: 35.2px" data-action-type="add-channel" title="Set to Audio Channel 8" onclick="changeChannelOffset(this.dataset.UUID, 7);">
|
||||
<span >C8</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<button data-action-type="force-keyframe" data-value="0" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="requestKeyframeScene(this, event);">
|
||||
<i class="las la-first-aid"></i>
|
||||
<span data-translate="force-keyframe">Rainbow Puke</span>
|
||||
</button>
|
||||
|
||||
<button class="" data-action-type="advanced-audio-settings" data-active="false" style="grid-column: 1;" title="Remote Audio Settings" onclick="requestAudioSettings(this);">
|
||||
<span data-translate="advanced-audio-settings"><i class="las la-sliders-h"></i> Audio Settings</span>
|
||||
</button>
|
||||
@@ -1005,7 +1080,6 @@
|
||||
<span data-translate="advanced-camera-settings"><i class="las la-sliders-h"></i> Video Settings</span>
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1157,7 +1231,7 @@
|
||||
}
|
||||
|
||||
var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js.
|
||||
session.version = "16.2";
|
||||
session.version = "16.4";
|
||||
session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed
|
||||
|
||||
session.defaultPassword = "someEncryptionKey123"; // Disabling improves compatibility and is helpful for debugging.
|
||||
@@ -1222,7 +1296,7 @@
|
||||
<script type="text/javascript" id="main-js" src="./main.js" data-translation="blank"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" id="mixer-js" src="./mixer.js?ver=2"></script>
|
||||
-->
|
||||
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=163"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=174"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function(){ // lazy load
|
||||
var script = document.createElement('script');
|
||||
|
||||
@@ -37,6 +37,8 @@ That all aside, please continue:
|
||||
|
||||
### SETUP
|
||||
|
||||
There's a community-created video tutorial on setting up here; https://youtu.be/8sDMwBIlgwE Otherwise, read on.
|
||||
|
||||
I use Cloudflare with Flexible SSL enabled and HTTP Rewrites. If you do not use Cloudflare, you will need to deploy SSL certificates onto your website. You will also have to have Cloudflare or whatever DNS provider you have, point your domain name to the IP address of your webserver. OBS.Ninja REQUIRES a domain name and SSL.
|
||||
|
||||
For webservers, I use NGINX on a Ubuntu server; smaller the better. I rely on Cloudflare to provide caching and SSL, so my installation of NGINX is pretty simple.
|
||||
@@ -92,5 +94,7 @@ A newly deployed code deployment should work without any changes to the index.ht
|
||||
|
||||
My suggestion? Limit changes to images and perhaps the translation files (maybe add a new one); these are good starting points. If making changes to the main.css style sheet or index.html file, you should be mostly okay too, since these files are designed to be changed; I try to keep that in mind when updating the code at least. Making changes to other files though is strongly not recommend and in some cases discouraged. If you find a bug or need to make a change to other files, it might be best to make a Pull Request with the desired changes and hope it gets adopted into the main codebase.
|
||||
|
||||
Final note: I haven't provided here instructions on deploying STUN services or a private handshake server; the OBS.Ninja handshake server code is currently not provided, but access to it as a service is freely accessible for private deployments.
|
||||
|
||||
Regards,
|
||||
Steve
|
||||
|
||||
183
main.css
183
main.css
@@ -139,6 +139,14 @@ button.grey {
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
#miniPerformer>#previewWebcam{
|
||||
width: 80px;
|
||||
height: 45px;
|
||||
margin: 5px;
|
||||
background-color: #464749 !important;
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
#reportbutton{
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -196,6 +204,15 @@ button.white:active {
|
||||
background-color: #0F131D;
|
||||
color: #FFF;
|
||||
}
|
||||
#head5 {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
pointer-events: none;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#overlayMsgs{
|
||||
margin:0 auto;
|
||||
@@ -486,9 +503,9 @@ button.glyphicon-button.active.focus {
|
||||
#container.vidcon {
|
||||
height:100%;
|
||||
}
|
||||
@media only screen and (max-width: 700px){
|
||||
@media only screen and (max-width: 640px){
|
||||
#controlButtons {
|
||||
transform: scale(0.8) translateY(20%);
|
||||
transform: scale(0.9) translateY(10%);
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 400px){
|
||||
@@ -501,17 +518,41 @@ button.glyphicon-button.active.focus {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
/* Node selector to prioritise this selector above .float */
|
||||
button.btnArmTransferRoom{
|
||||
width:auto;
|
||||
margin-left: 2px;
|
||||
height:38px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
button.btnArmTransferRoom i{
|
||||
margin-right:3px;
|
||||
}
|
||||
button.btnArmTransferRoom:hover{
|
||||
background-color: var(--green-accent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
button.btnArmTransferRoom.selected{
|
||||
background-color: #840000;
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 540px){
|
||||
#subControlButtons {
|
||||
transform: scale(0.90);
|
||||
transform: scale(0.88);
|
||||
}
|
||||
#gridlayout>#container.vidcon {
|
||||
height:90%
|
||||
height:88%
|
||||
}
|
||||
#controlButtons {
|
||||
height:54px;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 500px){
|
||||
#subControlButtons {
|
||||
transform: scale(0.87);
|
||||
}
|
||||
#gridlayout>#container.vidcon {
|
||||
height:87%
|
||||
}
|
||||
#controlButtons {
|
||||
height:54px;
|
||||
@@ -521,7 +562,19 @@ button.glyphicon-button.active.focus {
|
||||
#subControlButtons {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
#header{
|
||||
#logoname{
|
||||
display:none;
|
||||
}
|
||||
#head1{
|
||||
display:none;
|
||||
}
|
||||
#head4{
|
||||
display:none;
|
||||
}
|
||||
#head5{
|
||||
display:none;
|
||||
}
|
||||
#head2{
|
||||
display:none;
|
||||
}
|
||||
#gridlayout>#container.vidcon {
|
||||
@@ -531,6 +584,20 @@ button.glyphicon-button.active.focus {
|
||||
height:50px;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 300px){
|
||||
#gridlayout>#container.vidcon {
|
||||
height:81%
|
||||
}
|
||||
#subControlButtons {
|
||||
transform: scale(0.81);
|
||||
}
|
||||
#controlButtons {
|
||||
height:46.2px;
|
||||
}
|
||||
#head2 {
|
||||
display:none !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 240px){
|
||||
#gridlayout>#container.vidcon {
|
||||
height:78%
|
||||
@@ -576,6 +643,9 @@ button.glyphicon-button.active.focus {
|
||||
}
|
||||
}
|
||||
|
||||
#header:empty{
|
||||
display:none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
@@ -936,6 +1006,18 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 355px) {
|
||||
#popupSelector {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 330px) {
|
||||
#popupSelector {
|
||||
padding: 0 !important;
|
||||
font-size: 92%;
|
||||
}
|
||||
}
|
||||
|
||||
.popupSelector_constraints{
|
||||
margin:30px 9% 0 7%;
|
||||
}
|
||||
@@ -1065,6 +1147,7 @@ label {
|
||||
/* padding: 30px; */
|
||||
border-radius: 30px;
|
||||
cursor:pointer;
|
||||
color: #636363
|
||||
}
|
||||
.fullcolumn {
|
||||
float: left;
|
||||
@@ -1810,10 +1893,10 @@ input[type=checkbox] {
|
||||
font-size: 0.8rem;
|
||||
list-style-type: none;
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
top: 0px;
|
||||
width: 300px;
|
||||
min-height: 200px;
|
||||
max-height: 90vh;
|
||||
max-height: 99vh;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
position: absolute;
|
||||
@@ -2141,42 +2224,27 @@ span#guestTips {
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
pointer-events:none;
|
||||
font-size: 1em;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
|
||||
.video-label.zoom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: 0px;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
.video-label.teams {
|
||||
position: absolute;
|
||||
bottom: 0.6vh;
|
||||
left: 0.5vh;
|
||||
margin: 0px;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
background: rgba(0, 0, 0, .4);
|
||||
pointer-events:none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.video-label.skype {
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin: 0px;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
background: rgba(0, 0, 0, .8);
|
||||
pointer-events:none;
|
||||
border-radius: 5px;
|
||||
@@ -2184,7 +2252,6 @@ span#guestTips {
|
||||
}
|
||||
|
||||
.video-label.ninjablue {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 0;
|
||||
background: #141926;
|
||||
@@ -2192,7 +2259,6 @@ span#guestTips {
|
||||
}
|
||||
|
||||
.video-label.toprounded {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
background: rgb(0 0 0 / 70%);
|
||||
@@ -2210,11 +2276,10 @@ span#guestTips {
|
||||
}
|
||||
|
||||
.video-label.fire {
|
||||
color: #FFFFFF;
|
||||
text-shadow: 0 -1px 4px #FFF, 0 -2px 10px #ff0, 0 -10px 20px #ff8000, 0 -18px 40px #F00;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -2224,22 +2289,25 @@ span#guestTips {
|
||||
display:block;
|
||||
width:0.5vh;
|
||||
height:0.5vh;
|
||||
min-width:10px;
|
||||
min-height:10px;
|
||||
top: 2vh;
|
||||
right: 2vh;
|
||||
background-color:green;
|
||||
position:absolute;
|
||||
display:none;
|
||||
border-radius: 1vh;
|
||||
border-radius: 2vh;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
.video-mute-state {
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
top: 2vh;
|
||||
right: 2vh;
|
||||
position: absolute;
|
||||
color:white;
|
||||
border-radius: 1vh;
|
||||
border-radius: 2vh;
|
||||
background-color:#b11313;
|
||||
padding: 2px 2px 2px 1px;
|
||||
}
|
||||
|
||||
#help_directors_room{
|
||||
@@ -2341,4 +2409,51 @@ input:checked + .slider:before {
|
||||
-webkit-transform: translateX(16px);
|
||||
-ms-transform: translateX(16px);
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.alertModal {
|
||||
position: absolute;
|
||||
background-color: rgb(221 221 221);
|
||||
box-shadow: 0 0 30px 10px #0000005c;
|
||||
color: black;
|
||||
font-size: 1.2em;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
z-index:2;
|
||||
width:400px;
|
||||
max-width:90%;
|
||||
}
|
||||
|
||||
.alertModalInner {
|
||||
position: relative;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.alertModalClose {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bolder;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.alertModalBackdrop {
|
||||
background: var(--background-color);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 390px) {
|
||||
.alertModal {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="64"><style> .a{stroke-width:10;stroke:#000;}</style><title> background</title><rect height="66" width="67" y="-1" x="-1" fill="#0000"/><g height="100" width="100"><rect y="28.56" x="29.5" height="600" width="800" fill="url(#gridpattern)"/></g><title> Layer 1</title><rect height="3" width="1" y="27.02" x="302" style="fill:#0000;stroke-width:2;stroke:#0000"/><rect height="43" width="48" y="10.8" x="8.38" style="fill-opacity:null;fill:#0000;stroke-opacity:null;stroke-width:2;stroke:#FFF"/>
|
||||
<text font-family="Helvetica, Arial, sans-serif" font-size="24" y="40.49" x="15.38" style="fill:#FFF;font-weight:bold"> HQ</text></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="white" x="0px" y="0px" viewBox="0 0 122.88 122.87" style="enable-background:new 0 0 122.88 122.87" xml:space="preserve"><g><path d="M122.88,77.63v41.12c0,2.28-1.85,4.12-4.12,4.12H77.33v-9.62h35.95c0-12.34,0-23.27,0-35.62H122.88L122.88,77.63z M77.39,9.53V0h41.37c2.28,0,4.12,1.85,4.12,4.12v41.18h-9.63V9.53H77.39L77.39,9.53z M9.63,45.24H0V4.12C0,1.85,1.85,0,4.12,0h41 v9.64H9.63V45.24L9.63,45.24z M45.07,113.27v9.6H4.12c-2.28,0-4.12-1.85-4.12-4.13V77.57h9.63v35.71H45.07L45.07,113.27z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 697 B After Width: | Height: | Size: 650 B |
@@ -1,2 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="64"><style> .a{stroke-width:10;stroke:#000;}</style><title> background</title><rect height="66" width="67" y="-1" x="-1" fill="#0000"/><g height="100" width="100"><rect y="28.56" x="29.5" height="600" width="800" fill="url(#gridpattern)"/></g><title> Layer 1</title><rect height="3" width="1" y="27.02" x="302" style="fill:#0000;stroke-width:5;stroke:#0000"/><rect height="43" width="48" y="10.8" x="8.38" style="fill-opacity:null;fill:#0000;stroke-opacity:null;stroke-width:5;stroke:#FFF"/>
|
||||
<text font-family="Helvetica, Arial, sans-serif" font-size="24" y="40.49" x="15.38" style="fill:#FFF;font-weight:bold"> LQ</text></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="white" width="122.88px" height="122.871px" viewBox="0 0 122.88 122.871" enable-background="new 0 0 122.88 122.871" xml:space="preserve"><g><path d="M122.88,35.775v9.529H81.515c-2.278,0-4.125-1.847-4.125-4.125V0h9.63v35.775H122.88L122.88,35.775z M35.499,0h9.63v41.118 c0,2.278-1.847,4.125-4.125,4.125H0v-9.644h35.499V0L35.499,0z M0,87.164v-9.598h40.942c2.277,0,4.125,1.846,4.125,4.125v41.18 h-9.633V87.164H0L0,87.164z M77.328,122.871V81.752c0-2.277,1.847-4.125,4.125-4.125h41.427v9.625H86.931 c0,12.338-0.003,23.271-0.003,35.619H77.328L77.328,122.871z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 697 B After Width: | Height: | Size: 733 B |
2
media/svg.md
Normal file
2
media/svg.md
Normal file
@@ -0,0 +1,2 @@
|
||||
https://uxwing.com/collapse-icon/
|
||||
https://uxwing.com/expand-icon/
|
||||
@@ -6,6 +6,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OBSN Speed Test</title>
|
||||
<script>
|
||||
|
||||
function getChromeVersion() {
|
||||
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
return raw ? parseInt(raw[2], 10) : false;
|
||||
}
|
||||
if (!getChromeVersion()){
|
||||
alert("This speedtest is optimized for Chromium-based browsers; graphs will not work for Firefox or Safari browsers.");
|
||||
}
|
||||
|
||||
(function (w) {
|
||||
w.URLSearchParams =
|
||||
w.URLSearchParams ||
|
||||
@@ -251,10 +260,7 @@
|
||||
];
|
||||
updateData("buffer", buffer);
|
||||
|
||||
var packetloss =
|
||||
e.data.stats.inbound_stats[streamID][key][
|
||||
"packetLoss_in_percentage"
|
||||
];
|
||||
var packetloss = e.data.stats.inbound_stats[streamID][key]["packetLoss_in_percentage"];
|
||||
if (packetloss != undefined) {
|
||||
packetloss = packetloss.toFixed(2);
|
||||
updateData("packetloss", packetloss);
|
||||
|
||||
35
thirdparty/CodecsHandler.js
vendored
35
thirdparty/CodecsHandler.js
vendored
@@ -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];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"titles": {
|
||||
"toggle-the-chat": "Chat aan/uit",
|
||||
"mute-the-speaker": "Demp de sSreker",
|
||||
"mute-the-speaker": "Demp de Spreker",
|
||||
"mute-the-mic": "Demp de Mic",
|
||||
"disable-the-camera": "Camera uitzetten",
|
||||
"settings": "Instellingen",
|
||||
@@ -24,18 +24,18 @@
|
||||
"flaticon": "Flaticon",
|
||||
"creative-commons-by-3-0": "Creative Commons BY 3.0",
|
||||
"gregor-cresnar": "Gregor Cresnar",
|
||||
"add-this-video-to-any-remote-scene-1-": "Add this Video to any remote '&scene=1'",
|
||||
"forward-user-to-another-room-they-can-always-return-": "Stuur gast door naar andere room, kan terugkeren.",
|
||||
"start-recording-this-stream-experimental-views": "Stream opnemen starten. *experimenteel*' views",
|
||||
"add-this-video-to-any-remote-scene-1-": "Voeg deze video toe aan iedereen met '&scene=1'",
|
||||
"forward-user-to-another-room-they-can-always-return-": "Stuur gast door naar andere kamer,deze kan terugkeren.",
|
||||
"start-recording-this-stream-experimental-views": "Stream opnemen starten. *experimenteel*",
|
||||
"force-the-user-to-disconnect-they-can-always-reconnect-": "Forceer einde verbinding, kan wel opnieuw verbinden.",
|
||||
"change-this-audio-s-volume-in-all-remote-scene-views": "Change this Audio's volume in all remote '&scene' views",
|
||||
"remotely-mute-this-audio-in-all-remote-scene-views": "Remotely Mute this Audio in all remote '&scene' views",
|
||||
"change-this-audio-s-volume-in-all-remote-scene-views": "Veranderd dit audio volume in alle '&scene' weergaven",
|
||||
"remotely-mute-this-audio-in-all-remote-scene-views": "Op afstand dempen van deze audio op alle '&scene' weergavens",
|
||||
"disable-video-preview": "Schakel Video Preview uit",
|
||||
"low-quality-preview": "Lage kwaliteit Preview",
|
||||
"high-quality-preview": "Hoge kwaliteit Preview",
|
||||
"send-direct-message": "Stuur een Direct Message",
|
||||
"advanced-settings-and-remote-control": "Geavanceerde instellingen en Remote Conntrol",
|
||||
"toggle-voice-chat-with-this-guest": "Schakel Voice chat aan/uit met deze gast",
|
||||
"send-direct-message": "Stuur een prive bericht",
|
||||
"advanced-settings-and-remote-control": "Geavanceerde instellingen en Remote Control",
|
||||
"toggle-voice-chat-with-this-guest": "Schakel geluids chat aan/uit met deze gast",
|
||||
"join-by-room-name-here": "Voer een naam in voor snelle toegang",
|
||||
"join-room": "Ga de kamer in",
|
||||
"share-a-screen-with-others": "Deel je scherm met anderen",
|
||||
@@ -50,7 +50,7 @@
|
||||
"add-your-camera-to-obs": "Voeg je camera aan OBS toe",
|
||||
"remote-screenshare-into-obs": "Schermdelen naar OBS",
|
||||
"create-reusable-invite": "creëer herbruikbaare link",
|
||||
"encode-the-url-so-that-it-s-harder-for-a-guest-to-modify-the-settings-": "Encode the URL so that it's harder for a guest to modify the settings.",
|
||||
"encode-the-url-so-that-it-s-harder-for-a-guest-to-modify-the-settings-": "Coder de link zo dat het moeilijker is voor de gast om er achter te komen welke instellingen ingesteld staan",
|
||||
"more-options": "Meer opties",
|
||||
"youtube-video-demoing-how-to-do-this": "Youtube video voorbeelden, hoe dit te doen!",
|
||||
"invite-a-guest-or-camera-source-to-publish-into-the-group-room": "Vraag een gast zijn beeld te publiceren in de kamer",
|
||||
@@ -85,22 +85,22 @@
|
||||
"remote-audio-settings": "Audio instellingen op afstand",
|
||||
"advanced-video-settings": "Geavanceerde instellingen van video",
|
||||
"activate-or-reload-this-video-device-": "Activeer of herlaadt deze video bron.",
|
||||
"create-a-seconary-stream": "Create a Seconary Stream",
|
||||
"the-director-will-be-visible-in-scenes-as-if-a-performer-themselves-": "The director will be visible in scenes, as if a performer themselves.",
|
||||
"useful-if-you-want-to-perform-and-direct-at-the-same-time": "Useful if you want to perform and direct at the same time",
|
||||
"create-a-secondary-stream": "Maak een tweede stream aan",
|
||||
"the-director-will-be-visible-in-scenes-as-if-a-performer-themselves-": "De director zal zichtbaar zijn in de scenes, alsof hij ook een gast is.",
|
||||
"useful-if-you-want-to-perform-and-direct-at-the-same-time": "Handig als je de director en gast tegelijk wilt zijn.",
|
||||
"start-streaming": "start streaming",
|
||||
"if-disabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "If disabled, the invited guest will not be able to see or hear anyone in the room.",
|
||||
"if-disabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "If disabled, you must manually add a video to a scene for it to appear.",
|
||||
"toggle-solo-voice-chat": "Toggle Solo Voice Chat",
|
||||
"toggle-the-remote-guest-s-speaker-output": "Toggle the remote guest's speaker output",
|
||||
"toggle-the-remote-guest-s-display-output": "Toggle the remote guest's display output"
|
||||
"if-disabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "Waneer uitgeschakeld, zal de uitgenodigde gast niemand kunnen horen of zien in de kamer.",
|
||||
"if-disabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "Wanneer uitgeschakeld, moet je handmatig eeen video toevoegen aan een scene voordat deze zichtbaar wordt.",
|
||||
"toggle-solo-voice-chat": "Schakel Solo Geluids Chat",
|
||||
"toggle-the-remote-guest-s-speaker-output": "Schakel de gast's speaker uitgang",
|
||||
"toggle-the-remote-guest-s-display-output": "Schakel de gast's beeld uitgang"
|
||||
},
|
||||
"innerHTML": {
|
||||
"logo-header": "<font id=\"qos\" style=\"color: white;\">O</font>BS Ninja",
|
||||
"copy-this-url": "Deelbare Link naar deze video",
|
||||
"you-are-in-the-control-center": "U bent in het kamer beheers centrum",
|
||||
"joining-room": "U neemt deel aan de kamer",
|
||||
"add-group-chat": "Voeg Groepsgesprek toe",
|
||||
"add-group-chat": "Voeg groepsgesprek toe",
|
||||
"rooms-allow-for": "Kamers maken eenvoudige groepsgespreken en geavanceerd beheer van meerdere streams tegelijkertijd mogelijk.",
|
||||
"room-name": "Kamer Naam",
|
||||
"password-input-field": "Password",
|
||||
@@ -126,11 +126,11 @@
|
||||
"here-you-can-pre-generate": "Hier kan u vooraf een herbruikbare weergave link en een bijbehorende gast uitnodigingslink aanmaken.",
|
||||
"generate-invite-link": "GENEREER DE UITNODIGINGS LINK",
|
||||
"advanced-paramaters": "Geavanceerde Parameters",
|
||||
"unlock-video-bitrate": "Ontsluit Video Bitrate (20mbps)",
|
||||
"unlock-video-bitrate": "Verwijder limitatie video bitrate (20mbps)",
|
||||
"force-vp9-video-codec": "Forceer VP9 Video Codec (minder verstoring)",
|
||||
"enable-stereo-and-pro": "Activeer Stereo en Pro HD Geluid",
|
||||
"video-resolution": "Video Resolutie: ",
|
||||
"hide-mic-selection": "Force Default Microphone",
|
||||
"hide-mic-selection": "Forceer Standaard Microfoon",
|
||||
"hide-screen-share": "Verberg Scherm Delen Optie",
|
||||
"allow-remote-control": "Afstandsbediening Camera Zoom (android)",
|
||||
"add-a-password-to-stream": " Add a password:",
|
||||
@@ -139,28 +139,28 @@
|
||||
"can-see-and-hear": "Het groepsgesprek zien en horen",
|
||||
"can-hear-only": "Alleen het groepsgesprek horen",
|
||||
"cant-see-or-hear": "Het groepsgesprek niet horen en zien",
|
||||
"share-local-video-file": "Stream Media File",
|
||||
"share-website-iframe": "Share Website",
|
||||
"run-a-speed-test": "Run a Speed Test",
|
||||
"read-the-guides": "Browse the Guides",
|
||||
"share-local-video-file": "Stream Media Bestand",
|
||||
"share-website-iframe": "Deel Website",
|
||||
"run-a-speed-test": "Doe een Speed Test",
|
||||
"read-the-guides": "Blader in de handleidingen",
|
||||
"info-blob": "\n\t\t\t\t\t\t<h2>Wat is OBS.Ninja</h2><br>\n\t\t\t\t\t\t<li>100% <b>gratis</b>; geen downloads; geen persoonlijke gegevens verzamelen; niet inloggen</li>\n\t\t\t\t\t\t<li>Breng video van uw smartphone, laptop, computer, of van uw vrienden direct in uw OBS video stroom</li>\n\t\t\t\t\t\t<li>We gebruiken vooruitstrevende Peer-to-Peer technologie die privacy en ultra lage vertraging biedt</li>\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t<li>Youtube video <i class=\"fa fa-youtube-play\" aria-hidden=\"true\"></i> <a href=\"https://www.youtube.com/watch?v=6R_sQKxFAhg\">Demonstratie</a> </li>\n\t\t\t\t\t\t",
|
||||
"add-to-scene": "Add to Scene",
|
||||
"forward-to-room": "Transfer",
|
||||
"add-to-scene": "Toevoegen aan Scene",
|
||||
"forward-to-room": "Doorverbinden",
|
||||
"record": "Neem op",
|
||||
"disconnect-guest": "Hangup",
|
||||
"disconnect-guest": "Ophangen",
|
||||
"mute": "Demp",
|
||||
"change-to-low-quality": " <i class=\"las la-video-slash\"></i>",
|
||||
"change-to-medium-quality": " <i class=\"las la-video\"></i>",
|
||||
"change-to-high-quality": " <i class=\"las la-binoculars\"></i>",
|
||||
"send-direct-chat": "<i class=\"las la-envelope\"></i> Message",
|
||||
"advanced-camera-settings": "<i class=\"las la-cog\"></i> Advanced",
|
||||
"voice-chat": "<i class=\"las la-microphone\"></i> Voice Chat",
|
||||
"open-in-new-tab": "Open in nieuwe Tab",
|
||||
"copy-to-clipboard": "Kopiëren naar Clipboard",
|
||||
"click-for-quick-room-overview": "❔ Click Here for a quick overview and help",
|
||||
"push-to-talk-enable": "🔊 Enable Director's Push-to-Talk Mode",
|
||||
"send-direct-chat": "<i class=\"las la-envelope\"></i> Bericht",
|
||||
"advanced-camera-settings": "<i class=\"las la-cog\"></i> Geavanceerd",
|
||||
"voice-chat": "<i class=\"las la-microphone\"></i> Geluids Chat",
|
||||
"open-in-new-tab": "Open in nieuw tabblad",
|
||||
"copy-to-clipboard": "Kopiëren naar klembord",
|
||||
"click-for-quick-room-overview": "❔ Klik hier voor een snel overzicht en hulp",
|
||||
"push-to-talk-enable": "🔊 Enable Director's Push-to-Talk Mode Zet ",
|
||||
"welcome-to-control-room": "Welcome. This is the control-room for the group-chat. There are different things you can use this room for:<br><br>\t<li>You can host a group chat with friends using a room. Share the blue link to invite guests who will join the chat automatically.</li>\t<li>A group room can handle around 4 to 30 guests, depending on numerous factors, including CPU and available bandwidth of all guests in the room.</li>\t<li>Solo-views of each video are offered under videos as they load. These can be used within an OBS Browser Source.</li>\t<li>You can use the auto-mixing Group Scene, the green link, to auto arrange multiple videos for you in OBS.</li>\t<li>You can use this control room to record isolated video or audio streams, but it is an experimental feature still.</li>\t<li>Videos in the Director's room will be of low quality on purpose; to save bandwidth/CPU</li>\t<li>Guest's in the room will see each other's videos at a very limited quality to conserve bandwidth/CPU.</li>\t<li>OBS will see a guest's video in high-quality; the default video bitrate is 2500kbps.</li>\t<br>\tAs guests join, their videos will appear below. You can bring their video streams into OBS as solo-scenes or you can add them to the Group Scene.\t<br>The Group Scene auto-mixes videos that have been added to the group scene. Please note that the Auto-Mixer requires guests be manually added to it for them to appear in it; they are not added automatically.<br><br>Apple mobile devices, such as iPhones and iPads, do not fully support Video Group Chat. This is a hardware constraint.<br><br>\tFor advanced options and parameters, <a href=\"https://github.com/steveseguin/obsninja/wiki/Guides-and-How-to's#urlparameters\">see the Wiki.</a>",
|
||||
"more-than-four-can-join": "These four guest slots are just for demonstration. More than four guests can actually join a room.",
|
||||
"more-than-four-can-join": "Er staan momenteel vier gasten plekken gevuld voor de demonstratie. Het is mogelijk om meer gasten te hebben in een kamer.",
|
||||
"welcome-to-obs-ninja-chat": "\n\t\t\t\t\tWelcome to OBS.Ninja! You can send text messages directly to connected peers from here.\n\t\t\t\t",
|
||||
"names-and-labels-coming-soon": "\n\t\t\t\t\tNames identifying connected peers will be a feature in an upcoming release.\n\t\t\t\t",
|
||||
"send-chat": "Verstuur",
|
||||
@@ -188,15 +188,15 @@
|
||||
"order-down": "<i class=\"las la-minus\"></i>",
|
||||
"order-up": "<i class=\"las la-plus\"></i>",
|
||||
"advanced-audio-settings": "<i class=\"las la-sliders-h\"></i> Audio instellingen",
|
||||
"scenes-can-see-director": "Director will also be a performer",
|
||||
"toggle-remote-speaker": "Deafen Guest",
|
||||
"toggle-remote-display": "Blind Guest"
|
||||
"scenes-can-see-director": "Director is ook een gast",
|
||||
"toggle-remote-speaker": "Verdoof Gast",
|
||||
"toggle-remote-display": "Verblind Gast"
|
||||
},
|
||||
"placeholders": {
|
||||
"join-by-room-name-here": "Ga binnen met een kamer naam",
|
||||
"enter-a-room-name-here": "Geef hier een kamer naam op",
|
||||
"optional-room-password-here": "Optionele wachtwoord voor kamer",
|
||||
"give-this-media-source-a-name-optional-": "Geef de media bron een naam(optioneel)",
|
||||
"give-this-media-source-a-name-optional-": "Geef de media bron een naam (optioneel)",
|
||||
"add-an-optional-password": "Voeg optioneel wachtwoord toe",
|
||||
"enter-room-name-here": "Geef hier de kamer naam op",
|
||||
"enter-chat-message-to-send-here": "Type hier om te chatten"
|
||||
|
||||
@@ -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
16
turnserver3.conf
Normal 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
|
||||
182
zoom.html
Normal file
182
zoom.html
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user