diff --git a/.github/workflows/update_translations.yml b/.github/workflows/update_translations.yml
index 370ebb1..d85c1dd 100644
--- a/.github/workflows/update_translations.yml
+++ b/.github/workflows/update_translations.yml
@@ -35,5 +35,5 @@ jobs:
with:
commit-message: Generated updated translations
branch: generated_translations
- title: "[OBSNinja Bot] Updated translations"
+ title: "[VDONinja Bot] Updated translations"
labels: i18n
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ea7d1d..7f1c87f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,19 @@
-# OBS.Ninja Contributor License Agreement (CLA)
+# Contributor License Agreement (CLA)
-To ensure the long-term viability of the open-source OBS.Ninja project, and for the protection of its creator and its users, we request that contributors to the project first agree to some basic terms. The terms when accepted applies to all of your past, present and future contributions.
+To ensure the long-term viability of this project, and for the protection of its creator and its users, we request that contributors to the project first agree to some basic terms. The terms when accepted applies to all of your past, present and future contributions.
# Contribution Policy
-Contributions that only take 20 lines of code or less will have its Intellectual Property implicitly transferred to Stephen Seguin, the creator of OBS.Ninja. You agree with this rule by pushing your code or works to github, by sending the code or works to one of OBS.Ninja's core developers, or by distributing your code or works in any other way.
-
-For all code contributions that take more than 20 lines, you are invited to digitally sign the CLA with the provided CLA Assissant service. You may also print, sign, scan, and then email the CLA to steve@seguin.email.
+You are invited to digitally sign the CLA with the provided CLA Assissant service. You may also print, sign, scan, and then email the CLA to steve@seguin.email.
It is not required that you sign the CLA for every contribution. Once you execute a CLA, it is valid until the CLA agreement is meaningfully changed and requires updating.
-## Important notice
If you are contributing on behalf of your company, an officer of your company (usually a VP or higher title) must sign the CLA on behalf of the company, indicating his or her title. The company can choose to list the specific individuals authorized to make contributions on the "Full Name" line, or may cover all employees with a blanket CLA by not limiting contributors to an authorized list. If necessary, the company may provide a list of authorized contributors in an attachment. The executive signing the CLA must be the first name on such an attached list, and this executive must sign the attachment as well. It may well be the case that your company already has signed a company-wide CLA with Stephen Seguin. Please check this first.
-You can stop your participation in a project at any time, but you cannot rescind your assignments or grants with respect to prior contributions. This protects the whole community, allowing Stephen Seguin and downstream users of the code base to rely on it.
+You can stop your participation in a project at any time, but you cannot rescind your assignments or grants with respect to prior contributions.
+
+You hereby grant Steve Seguin (Steve) a perpetual, worldwide, royalty-free, irrevocable, non-exclusive, and transferable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, distribute the submissions, and to sublicense such rights to others. The rights granted may be exercised in any form or format, and Steve may distribute and sublicense to others on any licensing terms, including without limitation: (a) open source licenses like the GNU General Public License (GPL), or the Berkeley Software Distribution license (BSD); or (b) binary, proprietary, or commercial licenses.
+
+You hereby represent that you are the sole and original author of all Submissions and that, to the best of your knowledge, the submissions do not infringe upon the rights of any third party.
+
+You agree to these terms with your continued submission.
diff --git a/IFRAME.md b/IFRAME.md
index f1f362b..be52e54 100644
--- a/IFRAME.md
+++ b/IFRAME.md
@@ -1,38 +1,47 @@
-## OBS.Ninja - iFrame API documentation
+***
+This documentation has formally moved to: https://docs.vdo.ninja/guides/iframe-api-documentation
+***
+The documentation below is now considered depreciated and out-of-date.
-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.
-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:
+## VDO.Ninja - iFrame API documentation (depreciated; see above)
+
+VDO.Ninja (VDON) is offers here a simple and free solution to quickly enable real-time video streaming in their websites. VDON wishes to make live video streaming development accessible to any developer, even novices, yet still remain flexible and powerful.
+
+While VDO.Ninja does offer source-code to customize the application and UI at a low level, this isn't for beginners and it is rather hard to maintain. As well, due to the complexity of video streaming in the web, typical approaches for offering API access isn't quite feasible either.
+
+The solution decided on isn't an SDK framework, but rather the use of embeddable _IFrames_ and a corresponding bi-directional iframe API. An [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) allows us to embed a webpage inside a webpage, including VDO.Ninja into your own website.
+
+Modern web browsers allow the parent website to communicate with the child webpage, giving a high-level of control to a developer, while also abstracting the complex code and hosting requirements. Functionality, we can make an VDON video stream act much like an HTML video element tag, where you can issue commands like play, pause, or change video sources with ease.
+
+Creating an VDON iframe can be done in HTML or programmatically with Javascript like so:
```
const iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone";
iframe.allowtransparency = "false";
-iframe.src = "https://obs.ninja/?webcam";
+iframe.src = "https://vdo.ninja/?webcam";
```
-You can also make an OBS.Ninja without Javascript, using just HTML, like
+You can also make an VDO.Ninja without Javascript, using just HTML, like
-``
+``
Adding that iframe to the DOM will reveal a simple page accessing for a user to select and share their webcam. For a developer wishing to access a remote guest's stream, this makes the ingestion of that stream into production software like OBS Studios very easy. The level of customization and control opens up opportunities, such as a pay-to-join audience option for a streaming interactive broadcast experience.
-An example of how this API is used by OBS.Ninja is with its Internet Speedtest, which has two OBS.Ninja IFrames on a single page. One iframe feeds video to the other iframe, and the speed at which it does this is a measure of the system's performance. Detailed stats of the connection are made available to the parent window, which displays the results.
-https://obs.ninja/speedtest
+An example of how this API is used by VDO.Ninja is with its Internet Speedtest, which has two VDO.Ninja IFrames on a single page. One iframe feeds video to the other iframe, and the speed at which it does this is a measure of the system's performance. Detailed stats of the connection are made available to the parent window, which displays the results.
+https://vdo.ninja/speedtest
-More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/obsninja/tree/master/examples
+More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/vdoninja/tree/master/examples
-A sandbox of options is available at this page, too: https://obs.ninja/iframe You can enter an OBS.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/obsninja/blob/master/iframe.html
+A sandbox of options is available at this page, too: https://vdo.ninja/iframe You can enter an VDO.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/vdoninja/blob/master/iframe.html
-One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe _src_ URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control OBS.Ninja, while the former is used to initiate the instance to a desired state.
+One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe _src_ URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control VDO.Ninja, while the former is used to initiate the instance to a desired state.
-For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/obsninja/wiki/Advanced-Settings
+For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/vdoninja/wiki/Advanced-Settings
Some of the more interesting ones primarily for iframe users might include:
@@ -56,7 +65,7 @@ Some of the more interesting ones primarily for iframe users might include:
- Send/Recieve a chat message to other connected guests
- Get notified when there is a video connection
-As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file:
+As for the actually details for methods and options available to dynamically control child VDON iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file:
```js
let button = document.createElement("button");
@@ -228,7 +237,7 @@ button.onclick = () => {
};
iframeContainer.appendChild(button);
-// As for listening events, where the parent listens for responses or events from the OBSN child frame:
+// As for listening events, where the parent listens for responses or events from the VDON child frame:
// ////////// LISTEN FOR EVENTS
@@ -298,10 +307,8 @@ eventer(messageEvent, function (e) {
});
```
-This OBS.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete.
+This VDO.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete.
-Regarding versioning, I currently host past versions of OBS.Ninja, so using those past versions can ensure some level of consistency and expectation. https://obs.ninja/v12/ for example is the version 12 hosted version. The active and main production version of OBSN of course undergoes constant updates, and while I try to maintain backwards compatibility with changes to the API, it is still early days and changes might happen.
-
-Please feel free to follow me in the OBS.Ninja Discord channel, where I post news about updates and listen to requests. The upcoming version of OBS.Ninja is also often hosted at https://obs.ninja/beta, where you can explore new features and help crush any unexpected bugs.
+Please feel free to follow me in the VDO.Ninja Discord channel (discord.vdo.ninja) where I post news about updates and listen to requests. The upcoming version of VDO.Ninja is also often hosted at https://vdo.ninja/beta, where you can explore new features and help crush any unexpected bugs.
-steve
diff --git a/LICENCE.md b/LICENCE.md
index 4fc7ede..7f8c8d6 100644
--- a/LICENCE.md
+++ b/LICENCE.md
@@ -1,15 +1,14 @@
-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: [AGPLv3.md](https://github.com/steveseguin/obsninja/blob/master/AGPLv3.md)
+The VDO.Ninja source repository is governed by the GNU AFFERO GENERAL PUBLIC LICENSE. (AGPL-3.0)
+That AGPL-3.0 licence can be found here: [AGPLv3.md](https://github.com/steveseguin/vdo.ninja/blob/master/AGPLv3.md)
-In essence, OBS.Ninja is open-source and free to use, both for commercial and non-commercial use.
+In essence, VDO.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.
Some individual source files may contain different licencing term and perhaps different copyright holders.
Such licencing and copyright information will be contained in the file's header and be limited to those files.
If no such header is present in a file, the default AGPL-3.0 licence applies.
-Alternative licencing options can be made available on request, if AGPL-3.0 is not appropriate.
-Unless stated otherwise, all code is copyright 2020 Stephen Seguin. All rights reserved.
-Contributors to the OBS.Ninja project must first agree to the Contributor License Agreement (CLA).
+Unless stated otherwise, all code is copyright 2021 Stephen Seguin. All rights reserved.
+Contributors to the VDO.Ninja project must first agree to the Contributor License Agreement (CLA).
Thank you for your understanding.
diff --git a/README.md b/README.md
index 8c1515e..bb61a29 100644
--- a/README.md
+++ b/README.md
@@ -1,72 +1,87 @@
-
+### ⚠ Notice! We've rebranded from OBS.Ninja to VDO.Ninja - all else is staying the same ✨
-## What is OBS NINJA
-OBS.Ninja uses peer-to-peer technology to bring remote cameras into OBS. In most cases, all video data is transferred directly from peer to peer, without needing to go through any video server. This results in high-quality video with super low latency. In a small number of cases, video data may go through an encrypted TURN server, which is used to facilitate peer connections when otherwise not possible.
+
-OBS Ninja is not affiliated with OBS. OBS.Ninja is designed to allow content creators to produce real-time live shows with OBS Studio (or other compatible software) using remote media streams. It can also turn smartphones into wireless webcams, with additional Virtualcam software.
+## What is **VDO NINJA**
+VDO.Ninja uses peer-to-peer technology to bring remote cameras into OBS or other studio software.
-Please see the sub-reddit added info: https://reddit.com/r/obsninja
-Also check out the FAQ for more info: https://github.com/steveseguin/obsninja/wiki
+In most cases, all video data is transferred directly from peer to peer, without needing to go through any video server. This results in high-quality video with super low latency. In a small number of cases, video data may go through an encrypted TURN server, which is used to facilitate peer connections when otherwise not possible.
-
+VDO.Ninja is designed to allow content creators to produce real-time live shows using remote media streams. It can also turn smartphones into wireless webcams, with additional Virtualcam software.
+
+VDO.Ninja is freely available to use as a managed service over at https://vdo.ninja.
+
+For live support, please join our discord at https://discord.vdo.ninja
+Please see the sub-reddit added info: https://reddit.com/r/vdoninja
+Also check out the user documentation at: https://docs.vdo.ninja
+
+
## How to use
-I demo the basic usage of OBS.Ninja on YouTube: https://www.youtube.com/watch?v=6R_sQKxFAhg
-
-Here is a podcast series showing how to use different basic OBS.Ninja features, including macOS support: https://www.youtube.com/watch?v=XfSqufuoV74&list=PLWodc2tCfAH1l_LDvEyxEqFf42hOBKqQM
+A video demo and playlist of the basic usage of VDO.Ninja on YouTube can be found here: https://www.youtube.com/watch?v=QaA_6aOP9z8&list=PLWodc2tCfAH1l_LDvEyxEqFf42hOBKqQM&index=1.
And Here is another video series touching on some more advanced settings: https://www.youtube.com/watch?v=mQ1Jdhf5aYg&list=PL8VJWj2-XLFpFu3G35Hdm1nKZ2xn9_0_8
Check the subreddit for added use cases, advanced features, and support. Advanced features includes high-quality audio modes, custom video resolutions, and more.
-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](https://github.com/steveseguin/obsninja/wiki) for the project in this repo, which contains added information on how to use the software.
+This repo contains the web client software for VDO.Ninja, along with many sample apps that leverage its IFRAME API. A sample config file and instructions for setting up an optional TURN video relay server is also provided here. The user documentation for VDO.Ninja itself is found at docs.vdo.ninja.
## 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.
+VDO.Ninja is available as a free-to-use hosted service at https://vdo.ninja, so deploying is optional. If you do wish to self-deploy the service however, details are provided below.
-Directions on how to deploy a TURN server are listed in the turnserver.md file. You may wish to do so, although not all use cases will not need one. Only about 10% of remote guests, those often connected via 4G LTE, will require a TURN server. While OBS.Ninja does host some TURN servers, they are quite expensive to operate and not really for private deployment use. If you are deploying your own version of OBS.Ninja, I'd ask you use your own TURN servers instead.
+Hosting a private deployment can be as simple as hosting the files in this repository on a HTTPS-enabled webserver. For a very simple method on how to do this, there's a video guide here: https://www.youtube.com/watch?v=uYLKkX2_flY
+
+For more advanced users, you can see the [install.md](https://github.com/steveseguin/vdoninja/blob/master/install.md) file for alternative hosting options and more details on deploying additional system components. Limited technical support is provided for self-deployments, mainly due to how time-consuming such requests are, but the details to fully-deploy all system components are provided in the install.md file.
+
+If self-hosting, you might also wish to host your own video relay TURN server. Directions on how to deploy a TURN server are listed in the turnserver.md file. Only about ~ 5% of remote guests usually will need a TURN server, often those connected via 4G LTE or those behind a strict firewall, but most other users don't need one. While VDO.Ninja does host some pubiic TURN servers, they are quite expensive to operate, so please try to avoid abusing if possible. If you are deploying your own version of VDO.Ninja, I'd ask you to use your own TURN servers if you are capable of doing so; it's understandable if you aren't able to though.
+
+### Develop vs Release versions
+
+The develop branch is a bit like the preview or nightly version of VDO.Ninja. It's intended to be functional, but it may not be that well tested or there could be incomplete features. This version aligns closely with what is normally on vdo.ninja/beta/ or vdo.ninja/alpha/, which is well suited for those wishing to submit code changes or to gain access to experimental new features. You can access the GitHub develop on Github pages here as well: https://steveseguin.github.io/vdo.ninja/
+
+Release versions of VDO.Ninja have their own branches though, with the newest release being hosted at https://vdo.ninja/. These latest release branch will be updated to fix bugs or critical issues as needed, but are otherwise unchanged. https://github.com/steveseguin/vdo.ninja/branches
+
+Due to the nature of live video production, where unexpected changes to the app are not welcomed usually, I don't update https://vdo.ninja/ all that often. As well, constant updates to the primary hosted app makes supporting users challenging, as its hard to tell if an issue is with the code or with the user. For this reason, VDO.Ninja does infrequent updates to the primary hosted production version. Users wanting newer features, or who have greater risk tolerance, should use the beta version (vdo.ninja/beta/). The alpha version is usually updated daily, whereas beta is usually updated weekly.
## 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)
+Since VDO.Ninja uses peer-2-peer technology, video connections are made directly between viewer and publisher in 95% of cases. Hosting a TURN server yourself may help improve performance, but less than 1% of users will see an improvement to video quality by using one. They also will not help lower bandwidth usage or CPU usage, so generally you wish to avoid using them if possible.
-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.
+Details on how to deploy a TURN server are provided; see: turnserver.md. For those capable of hosting their own TURN server, that would be appreciated if possible, as TURN servers are the largest cost incurred by VDO.Ninja at present. (other than time, of course)
-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.
+Other than TURN servers, VDO.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. As of Version 17.3 of VDO.Ninja, you can host your own handshake server or use a third-party managed one (such as piesocket.com); please see details here: https://github.com/steveseguin/websocket_server
-A design goal of OBS.Ninja is to be serverless and we are 99% of the way there. This design objective ensures OBS.Ninja can be offered for free, along with providing increased levels of security and privacy.
+A design goal of VDO.Ninja is to be serverless and we are near 99% of the way there. This design objective ensures VDO.Ninja can be offered for free, along with providing increased levels of security and privacy.
## Issues? problems? Not working?
-Please see the sub-reddit for more support: https://reddit.com/r/obsninja
+Please see the sub-reddit for more support: https://reddit.com/r/vdoninja
-Also check out the FAQ for common answers: https://github.com/steveseguin/obsninja/wiki
+Also check out the FAQ for common answers: https://github.com/steveseguin/vdoninja/wiki
-If urgent, join me on discord: https://discord.gg/EksyhGA or email me at steve@seguin.email
+If urgent, join me on discord: https://discord.vdo.ninja or email me at steve@seguin.email (Steve may not respond to emails if deemed unimportant)
## Related Projects
-### OBS.Ninja's Electron Capture:
-A better way to perform "Window Capturing" on desktop if OBS Browser Sources fails you. A downloadable tool designed to enhance OBS.Ninja.
+### VDO.Ninja's Electron Capture:
+A better way to perform "Window Capturing" on desktop if OBS Browser Sources fails you. A downloadable tool designed to enhance VDO.Ninja, but has been expanded to have additional functionality for content creators in general
https://github.com/steveseguin/electroncapture
### CAPTION.Ninja
-A free AI-based closed-captioning tool to add speech-to-text overlays to OBS Studio. It's browser-based with an easy OBS integration. Developed by Steve as well! https://caption.ninja
+A free AI-based closed-captioning tool to add speech-to-text overlays to OBS Studio. It's browser-based with an easy OBS or VMix integration. Developed by Steve as well! https://caption.ninja
-### Chat.Overlay.Ninja
-A free Chrome extension that lets you select Youtube Live Chat comments, with those comments then appearing directly in OBS or VMix as an overlay. No chroma-keying needed and the styling is pretty easy to customize without needing to modify the Chrome extension itself.
-http://chat.overlay.ninja/
+### Social Stream Ninja
+A free Chrome extension that lets you stream and feature chat comments from Youtube, Twitch, Facebook, and more. Featured comments will appear directly in OBS or VMix as an overlay, or as a stream list of comments. It also includes a dock for more advanced function, such as text-to-speech, sentiment analysis, and saving to disk. No chroma-keying needed and the styling is pretty easy to customize without needing to modify the Chrome extension itself.
+http://socialstream.ninja
-### Steves.app:
-A website designed to also work with OBS.Ninja as a Broadcasting tool. Share your webcam, window, desktop, or video file with friends and family. Peer-2-peer, so privacy can be maintained, but you can also list your broadcasts for others to watch.
-https://steves.app/
+### Rasbperry Ninja
+Use a Raspberry Pi, NVidia Jetson, or Linux box as a dedicated camera for VDO.Ninja. This project can use the hardware encoder of the RPi or Jetson to enable 1080p30 or even 4K video capture and webRTC-based broadcasting. Support for USB, CSI, and HDMI video sources is available. Python-based.
+[http://socialstream.ninja](https://github.com/steveseguin/raspberry_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](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 VDO.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.
+https://vdo.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 VDO.Ninja.
Additional security features are being added weekly on request. Please ask about these options if added security and privacy are requirements for you.
@@ -74,4 +89,12 @@ Additional security features are being added weekly on request. Please ask about
Ideas, feedback, bugs, etc -- all welcomed. I'm dumping many of my ideas as issues into Github. Feedback is typically most welcomed via Email or Discord.
## Licence
-OBS.Ninja is available as 'mostly' open-source; please see the LICENCE.md file for details.
+VDO.Ninja is available as 'mostly' open-source; please see the LICENCE.md file for details.
+
+## Credit
+Thank you to everyone who has helped support this project so far. From the moderators, volunteers helping with support, those contributing media assets, the project sponsors, those reporting issues, those offering feedback, and any code submissions.
+
+## Contributors of this repo
+
+
+
diff --git a/check.html b/check.html
new file mode 100644
index 0000000..77a23b3
--- /dev/null
+++ b/check.html
@@ -0,0 +1,752 @@
+
+

This tool performs the following action in your browser: fmpeg -i input.webm -vf scale=1280:720 output.mp4
- - -r)&&(n=r),0 {'%%'===e||(o++,'%c'===e&&(r=o))}),t.splice(r,0,n)},t.save=function(e){try{e?t.storage.setItem('debug',e):t.storage.removeItem('debug')}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem('debug')}catch(e){}return!e&&'undefined'!=typeof o&&'env'in o&&(e=o.env.DEBUG),e},t.useColors=function(){return'undefined'!=typeof window&&window.process&&('renderer'===window.process.type||window.process.__nwjs)||('undefined'!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)?!1:'undefined'!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||'undefined'!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||'undefined'!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&31<=parseInt(RegExp.$1,10)||'undefined'!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.colors=['#0000CC','#0000FF','#0033CC','#0033FF','#0066CC','#0066FF','#0099CC','#0099FF','#00CC00','#00CC33','#00CC66','#00CC99','#00CCCC','#00CCFF','#3300CC','#3300FF','#3333CC','#3333FF','#3366CC','#3366FF','#3399CC','#3399FF','#33CC00','#33CC33','#33CC66','#33CC99','#33CCCC','#33CCFF','#6600CC','#6600FF','#6633CC','#6633FF','#66CC00','#66CC33','#9900CC','#9900FF','#9933CC','#9933FF','#99CC00','#99CC33','#CC0000','#CC0033','#CC0066','#CC0099','#CC00CC','#CC00FF','#CC3300','#CC3333','#CC3366','#CC3399','#CC33CC','#CC33FF','#CC6600','#CC6633','#CC9900','#CC9933','#CCCC00','#CCCC33','#FF0000','#FF0033','#FF0066','#FF0099','#FF00CC','#FF00FF','#FF3300','#FF3333','#FF3366','#FF3399','#FF33CC','#FF33FF','#FF6600','#FF6633','#FF9900','#FF9933','#FFCC00','#FFCC33'],e.exports=n(17)(t);const{formatters:r}=e.exports;r.j=function(e){try{return JSON.stringify(e)}catch(e){return'[UnexpectedJSONParseError]: '+e.message}}}).call(this,n(16))},function(e){function t(){throw new Error('setTimeout has not been defined')}function n(){throw new Error('clearTimeout has not been defined')}function o(e){if(u===setTimeout)return setTimeout(e,0);if((u===t||!u)&&setTimeout)return u=setTimeout,setTimeout(e,0);try{return u(e,0)}catch(t){try{return u.call(null,e,0)}catch(t){return u.call(this,e,0)}}}function r(e){if(p===clearTimeout)return clearTimeout(e);if((p===n||!p)&&clearTimeout)return p=clearTimeout,clearTimeout(e);try{return p(e)}catch(t){try{return p.call(null,e)}catch(t){return p.call(this,e)}}}function s(){g&&c&&(g=!1,c.length?f=c.concat(f):h=-1,f.length&&l())}function l(){if(!g){var e=o(s);g=!0;for(var t=f.length;t;){for(c=f,f=[];++h
-
+
For the best possible experience, make sure Privacy warning: The director will be able to remotely change your camera, microphone, and URL. For the best possible experience, make sure
+
+ Consider using a Chromium-based browser instead.
+ Privacy warning: The director will be able to remotely change your camera, microphone, and URL while this page is open, if you continue. Media file streaming is still quite experimental. Please do not rely on it heavily for your productions. Feedback welcome. Keep this tab visible if using Chrome, else the video playback will stop Keep this tab visible, else the video playback will stop Using The Electron Capture app instead of Chrome should work:
- GET IT HERE
- Safari does not support this feature. Consider Chrome or Firefox instead. relay server relay server host host srflx srflx relay server relay server host host srflx srflx this is the text inside the modal P?(K|=2048,F|(K>>114-P)+(K>>113-P&1)):F=(F|
+P-112<<10|K>>1)+(K&1)}function d(h){var F=new Uint16Array(h.length);h.forEach(function(K,P){F[P]=c(K)});return F}function e(){if(null!==G.$b)return G.$b;var h=n(d([.5,.5,.5,.5]));return null===h?!0:G.$b=h}function m(){if(null!==G.ac)return G.ac;var h=n(new Uint8Array([127,127,127,127]));return null===h?!0:G.ac=h}function n(h){if(!xa.nb()||!k)return null;var F=null,K=Math.sqrt(h.length/4);try{var P=b.getError();if("FUCKING_BIG_ERROR"===P)return!1;F=L.instance({isFloat:!1,S:!0,array:h,width:K});P=b.getError();
+if(P!==b.NO_ERROR)return!1}catch(ra){return!1}la.P();b.viewport(0,0,K,K);b.clearColor(0,0,0,0);b.clear(b.COLOR_BUFFER_BIT);xa.set("s0");F.Rc(0);ma.l(!0,!0);h=4*K*K;P=new Uint8Array(h);b.readPixels(0,0,K,K,b.RGBA,b.UNSIGNED_BYTE,P);K=!0;for(var da=0;da P?(K|=2048,F|(K>>114-P)+(K>>113-P&1)):F=(F|
+P-112<<10|K>>1)+(K&1)}function d(h){var F=new Uint16Array(h.length);h.forEach(function(K,P){F[P]=c(K)});return F}function e(){if(null!==G.$b)return G.$b;var h=n(d([.5,.5,.5,.5]));return null===h?!0:G.$b=h}function m(){if(null!==G.ac)return G.ac;var h=n(new Uint8Array([127,127,127,127]));return null===h?!0:G.ac=h}function n(h){if(!xa.nb()||!k)return null;var F=null,K=Math.sqrt(h.length/4);try{var P=b.getError();if("FUCKING_BIG_ERROR"===P)return!1;F=L.instance({isFloat:!1,S:!0,array:h,width:K});P=b.getError();
+if(P!==b.NO_ERROR)return!1}catch(ra){return!1}la.P();b.viewport(0,0,K,K);b.clearColor(0,0,0,0);b.clear(b.COLOR_BUFFER_BIT);xa.set("s0");F.Rc(0);ma.l(!0,!0);h=4*K*K;P=new Uint8Array(h);b.readPixels(0,0,K,K,b.RGBA,b.UNSIGNED_BYTE,P);K=!0;for(var da=0;da P?(K|=2048,F|(K>>114-P)+(K>>113-P&1)):F=(F|
+P-112<<10|K>>1)+(K&1)}function d(h){var F=new Uint16Array(h.length);h.forEach(function(K,P){F[P]=c(K)});return F}function e(){if(null!==G.$b)return G.$b;var h=n(d([.5,.5,.5,.5]));return null===h?!0:G.$b=h}function m(){if(null!==G.ac)return G.ac;var h=n(new Uint8Array([127,127,127,127]));return null===h?!0:G.ac=h}function n(h){if(!xa.nb()||!k)return null;var F=null,K=Math.sqrt(h.length/4);try{var P=b.getError();if("FUCKING_BIG_ERROR"===P)return!1;F=L.instance({isFloat:!1,S:!0,array:h,width:K});P=b.getError();
+if(P!==b.NO_ERROR)return!1}catch(ra){return!1}la.P();b.viewport(0,0,K,K);b.clearColor(0,0,0,0);b.clear(b.COLOR_BUFFER_BIT);xa.set("s0");F.Rc(0);ma.l(!0,!0);h=4*K*K;P=new Uint8Array(h);b.readPixels(0,0,K,K,b.RGBA,b.UNSIGNED_BYTE,P);K=!0;for(var da=0;dar&&(e=r)}if(void 0!==o&&'string'!=typeof o)throw new TypeError('encoding must be a string');if('string'==typeof o&&!u.isEncoding(o))throw new TypeError('Unknown encoding: '+o)}else'number'==typeof e&&(e&=255);if(0>t||this.length
+Add &sensor to the push link to send data; see: https://docs.vdo.ninja/source-settings/sensor
+Which social integration are you adding?
+
+
+Use VDO.Ninja and SocialStream chat at the same time
+
+
+
+Use VDO.Ninja and Twitch chat at the same time
+
+
+
+STREAMDECK DEMO
+ 
+
+
+
Screen
Record
+
Create a Room
@@ -194,24 +257,38 @@
-
-
+
Room Name:
-
-
+
+
+
-
+
Password:
-
-
+
+
+
+
@@ -220,16 +297,16 @@
- Guests can only see the Director's Video
+ The guests can see the director, but not other guests' videos
-
+
- Director will also be a performer
+ The director will be performing as well, appearing in group scenes
@@ -248,32 +325,37 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
- Important Tips:
-
-
+
+
+ Important Tips:
+
+
+
+
+
+ Looking to just chat and not direct?
+
+
Add your Camera to OBS
-
-
-
-
+
+
+
+
-
-
+
+
Safari is more prone to having audio issues
Remote Screenshare into OBS
-
+
-
+
-
+
Create Reusable Invite
@@ -515,69 +630,82 @@
GENERATE THE INVITE LINK
-
-
- Advanced Options
+ Quality settings
+ User options
+
+
+ Two-way chat
+
+
+ Try out the advanced invite generator here as well.
Stream Media File
Warning
Chrome users
- Chrome/Edge users
+ File Sharing seems to be broken on Chrome v88.
-
- You can also turn off hardware-accleration in Chrome/Edge to fix the issue.Safari Users
+
+ To host a file for download, rather than for streaming, try the following instead:
+
Share Website
-
+ Usage information
+
+
+ Run a Speed Test
Browse the Guides
+ Custom Mixed Layouts
+
+ Multi-Stream Monitor
+
+ Group Voice Comms
+
+ Basic Usage Guides
Wizard Link Generator
+
+ Full Documentation
+
+ Source Code
+
+ Show Your Support
+
+ What is OBS.Ninja
+ What is VDO.Ninja
- Known issues:
+ Known issues:
- 👓🔆 Site Updated on April 20th: v17 Release Notes. The previous version can be found at https://obs.ninja/v164/ if you are having issues with this minor update.
-
+
+ Welcome to VDO Ninja! We've rebranded! Nothing else is changing and we're staying 100% free.
+
+
+ 🚀🚀 Site last updated on Nov.18th (v22 release notes). You can also still access the previous version, which is hosted here. Development updates are here.
- 🛠 For support, see the sub-reddit or join the Discord . The Wiki is here and my personal email is steve@seguin.email
+ 🛠 For support, join the Discord or see the sub-reddit . The documentation is here and my personal email is steve@seguin.email
-
+
You can host a group chat with friends using a room. Share the blue link to invite guests who will join the chat automatically.
- Known Limitations with Group Rooms:
+ Known Limitations with Group Rooms:
- For advanced URL options and parameters, see the Wiki.
-
+ For advanced URL options and parameters, see the Wiki.
+
+
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+ Guest 1
Guest 2
Guest 3
Guest 4
These four guest slots are just for demonstration. More than four guests can actually join a room.
- Guest 1
Guest 2
Guest 3
Guest 4
More scene options
-
-
Additional controls
-
-
-
-
-
-
-
+
No Image Selected
+
Select Local Image
+
+
-
-
Select Local Image
-
-
+
+
+ Change room settings
+ Remote Controller for OBS Studio
+ Assign to slot:
+
Select Local Image
+
+
+
- Add More Here!
+ Add More Here!
Notice: The system cannot be accessed or is currently slow to respond.
\nIf a routing issue, try adding &proxy to the URL; you can also try https://proxy.vdo.ninja or a VPN if the service is blocked in your country.\n\nIf the main service is down, a backup version is also available here: https://backup.vdo.ninja\n\nContact steve@seguin.email for added help.\n\nThis service requires the use of Websockets over port 443.",
+ "no-audio-source-detected": "No Audio Source was detected.\n\nIf you were wanting to capture an Application's Audio, please see:\nhttps://docs.vdo.ninja/help/guides-and-how-tos#audio for some guides.",
+ "viewer-count": "Total outbound p2p connections of this remote stream"
+};
+
+// function log(msg){ // uncomment to enable logging.
+ // console.log(msg);
+// }
+// function warnlog(msg, url=false, lineNumber=false){
+ // onsole.warn(msg);
+ // if (lineNumber){
+ // console.warn(lineNumber);
+ // }
+// }
+// function errorlog(msg, url=false, lineNumber=false){
+ // console.error(msg);
+ // if (lineNumber){
+ // console.error(lineNumber);
+ // }
+// }
+
+if (typeof session === 'undefined') { // make sure to init the WebRTC if not exists.
+ var session = WebRTC.Media;
+ session.streamID = session.generateStreamID();
+ errorlog("Serious error: WebRTC session didn't load in time");
+}
+
+(function(w) {
+ w.URLSearchParams = w.URLSearchParams || function(searchString) {
+ var self = this;
+ searchString = searchString.replace("??", "?");
+ self.searchString = searchString;
+ self.get = function(name) {
+ var results = new RegExp('[\?&]' + name + '=([^]*)').exec(self.searchString);
+ if (results == null) {
+ return null;
+ } else {
+ return decodeURI(results[1]) || 0;
+ }
+ };
+ };
+
+})(window);
+
+var urlEdited = window.location.search.replace(/\?\?/g, "?");
+urlEdited = urlEdited.replace(/\?/g, "&");
+urlEdited = urlEdited.replace(/\&/, "?");
+var urlParams = new URLSearchParams(urlEdited);
+
+if (urlParams.has("invite") || urlParams.has("i") || urlParams.has("code")){
+ session.decodeInvite(urlParams.get("invite") || urlParams.get("i") || urlParams.get("code"));
+}
+
+if (session.decrypted){
+ session.decrypted = session.decrypted + urlEdited.replace("?","&");
+ session.decrypted = session.decrypted.replace(/\?/g, "&");
+ session.decrypted = session.decrypted.replace(/\&/, "?");
+ urlParams = new URLSearchParams(session.decrypted);
+ //session.decrypted = true;
+} else {
+ if (urlEdited !== window.location.search){
+ warnlog(window.location.search + " changed to " + urlEdited);
+ window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
+ }
+}
+delete urlEdited;
+
+var isIFrame = false;
+if ( parent && (window.location !== window.parent.location )) {
+ isIFrame = true;
+}
+
+function mapToAll(targets, callback, parentElement = document) { // js helper
+ if (!targets) {
+ return;
+ }
+ if (!parentElement) {
+ return;
+ }
+ const target = parentElement.querySelectorAll(targets);
+ for (let i = 0; i < target.length; i++) {
+ callback(target[i]);
+ }
+}
+
+function changeParam(url, paramName, paramValue) {
+ paramName = paramName.replace("?", "");
+ var qind = url.indexOf('?');
+ url = url.replace("?", "&");
+ var params = url.substring(qind + 1).split('&');
+ var query = '';
+ var match = false;
+ for (var i = 0; i < params.length; i++) {
+ var tokens = params[i].split('=');
+ var name = tokens[0];
+ var value = "";
+ if (tokens.length > 1 && tokens[1] !== '') {
+ value = tokens[1];
+ }
+
+ if (name == paramName) {
+ if (match) {
+ continue;
+ } // already matched the first time.
+ match = true;
+ value = paramValue;
+ }
+ if (value !== "") {
+ value = '=' + value;
+ }
+
+ if (query == '') {
+ query = "?" + name + value;
+ } else {
+ query = query + '&' + name + value;
+ }
+ }
+ return url.substring(0, qind) + query;
+}
+
+function saveRoom(ele){
+ //this.title = "Quick load settings stored locally";
+ session.sticky = true;
+ ele.parentNode.removeChild(ele);
+ setStorage("permission", "yes");
+ setStorage("settings", encodeURI(window.location.href), 999);
+}
+
+function updateURL(param, force = false, cleanUrl = false) {
+ if (session.decrypted){return;}
+
+ param = param.replace("?", "");
+ var para = param.split('=');
+ if (cleanUrl) {
+ if (history.pushState) {
+ var href = new URL(cleanUrl);
+ if (para.length == 1) {
+ href = changeParam(cleanUrl, para[0], "");
+ } else {
+ href = changeParam(cleanUrl, para[0], para[1]);
+ }
+ log("--" + href.toString());
+ window.history.pushState({path: href.toString()}, '', href.toString());
+ }
+ } else if (!(urlParams.has(para[0]))) { // don't need to replace as it doesn't exist.
+ if (history.pushState) {
+ var href = window.location.href;
+ href = href.replace("??", "?");
+ var arr = href.split('?');
+ var newurl;
+ if (arr.length > 1 && arr[1] !== '') {
+ newurl = href + '&' + param;
+ } else {
+ newurl = href + '?' + param;
+ }
+
+ window.history.pushState({path: newurl.toString()}, '', newurl.toString());
+ }
+ } else if (force) {
+ if (history.pushState) {
+ var href = new URL(window.location.href);
+ if (para.length == 1) {
+ href = changeParam(window.location.href, para[0], "");
+ } else {
+ href = changeParam(window.location.href, para[0], para[1]);
+ }
+ log("---" + href.toString());
+ window.history.pushState({path: href.toString()}, '', href.toString());
+ }
+ }
+ if (session.sticky) {
+ setStorage("settings", encodeURI(window.location.href), 999);
+ }
+ urlParams = new URLSearchParams(window.location.search);
+}
+
+/* function changeGuestSettings(ele){
+ var eles = ele.querySelectorAll('[data-param]');
+ var UUID = ele.dataset.UUID;
+ var settings = {};
+ for (var i = 0;i< eles.length; i++){
+ if (eles[i].tagName.toLowerCase() == "input"){
+ if (eles[i].checked===true){
+ settings[eles[i].dataset.param] = true;
+ } else if (eles[i].checked===false){
+ settings[eles[i].dataset.param] = false;
+ } else {
+ settings[eles[i].dataset.param] = eles[i].value;
+ }
+ }
+ }
+ warnlog(settings);
+
+ if (!settings.changepassword){
+ delete settings.password;
+ }
+
+ delete settings.changepassword;
+
+ if (!settings.changeroom){
+ // send Migration message
+ delete settings.roomid;
+ }
+ delete settings.roomid;
+ delete settings.changeroom;
+
+ warnlog(UUID);
+ var msg = {};
+ msg.changeParams = settings;
+ session.sendRequest(msg, UUID);
+ closeModal();
+} */
+
+// proper room migration needs to happen; in sync.
+// updateMixer after settings changed
+// password needs to be special cased
+// room shouldn't be sent
+
+function applyNewParams(changeParams){
+ for (var key in changeParams){
+ session[key] = changeParams[key];
+ log(key);
+ }
+ log(changeParams);
+ updateMixer();
+}
+
+function submitDebugLog(msg){
+ try {
+ appendDebugLog({"connection_type": session.stats.network_type});
+ if (navigator.userAgent){
+ var _, userAgent = navigator.userAgent;
+ appendDebugLog({"userAgent": userAgent});
+ }
+ if (navigator.platform){
+ appendDebugLog({"userAgent": navigator.platform});
+ }
+ } catch(e){}
+ window.focus();
+ var res = confirm(miscTranslations["submit-error-report"]);
+ if (res){
+ var request = new XMLHttpRequest();
+ request.open('POST', "https://reports.vdo.ninja/"); // php, well, whatever.
+ request.send(JSON.stringify(errorReport));
+ errorReport = [];
+ if (document.getElementById("reportbutton")){
+ getById("reportbutton").style.visibility = "hidden";
+ }
+ }
+}
+
+function detectGPUSupport() {
+ try {
+ const gl = document.createElement('canvas').getContext('webgl');
+
+ if (!gl) {
+ return false;
+ }
+
+ if (!Firefox){
+ try {
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); // chrome
+ if (debugInfo){
+ return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
+ }
+ } catch(e){}
+ }
+
+ try {
+ return gl.getParameter(gl.RENDERER) || false; // firefox
+ } catch(e){}
+
+ } catch(e){}
+ return false;
+}
+
+function isOperaGX(){
+ return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/75') >= 0;
+}
+
+function isSamsungASeries(){
+ return navigator.userAgent.includes("; SM-A") || false;
+}
+
+function getChromeVersion() {
+ var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
+ return raw ? parseInt(raw[2], 10) : false;
+}
+
+function safariVersion() {
+ var ver = 0;
+ try {
+ ver = navigator.appVersion.split("Version/");
+ if (ver.length > 1) {
+ ver = ver[1].split(" Safari");
+ }
+ if (ver.length > 1) {
+ ver = ver[0].split(".");
+ }
+ if (ver.length > 1) {
+ ver = parseInt(ver[0]);
+ } else {
+ ver = 0;
+ }
+ } catch (e) {
+ return 0;
+ }
+ return ver;
+}
+
+try{
+ var iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform); // used by main.js also
+ var iPad = (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
+ var macOS = navigator.userAgent.indexOf('Mac OS X') != -1;
+ macOS = macOS && !(iOS || iPad);
+ var Firefox = navigator.userAgent.indexOf("Firefox")>=0;
+ var Android = navigator.userAgent.toLowerCase().indexOf("android") > -1; //&& ua.indexOf("mobile");
+ var ChromeVersion = getChromeVersion();
+ var OperaGx = isOperaGX();
+ var SafariVersion = safariVersion();
+ var SamsungASeries = isSamsungASeries();
+} catch(e){errorlog(e);}
+
+var gpgpuSupport = detectGPUSupport();
+log(gpgpuSupport);
+
+
+
+
+function isAlphaNumeric(str) {
+ var code, i, len;
+ for (i = 0, len = str.length; i < len; i++) {
+ code = str.charCodeAt(i);
+ if (!(code > 47 && code < 58) && // numeric (0-9)
+ !(code > 64 && code < 91) && // upper alpha (A-Z)
+ !(code > 96 && code < 123)) { // lower alpha (a-z)
+ return false;
+ }
+ }
+ return true;
+}
+
+function convertStringToArrayBufferView(str){
+ var bytes = new Uint8Array(str.length);
+ for (var iii = 0; iii < str.length; iii++){
+ bytes[iii] = str.charCodeAt(iii);
+ }
+ return bytes;
+}
+
+function toHexString(byteArray){
+ return Array.prototype.map.call(byteArray, function(byte){
+ return ('0' + (byte & 0xFF).toString(16)).slice(-2);
+ }).join('');
+}
+function toByteArray(hexString){
+ var result = [];
+ for (var i = 0; i < hexString.length; i += 2){
+ result.push(parseInt(hexString.substr(i, 2), 16));
+ }
+ return new Uint8Array(result);
+}
+
+function playAllVideos(){
+
+ if (session.firstPlayTriggered && (session.audioCtx.state == "suspended")){ // added oct 9th 2022
+ try {
+ session.audioCtx.resume();
+ } catch(e){warnlog(e);}
+ }
+
+ for (var i in session.rpcs){
+ try{
+ if (session.rpcs[i].videoElement){
+ log("I: "+i);
+ if (session.rpcs[i].videoElement.paused){
+ setTimeout(function(UUID){
+ session.rpcs[UUID].videoElement.play().then(_ => {
+ log("playing 3 ");
+ if ((session.audioEffects===true) || session.pushLoudness){
+ log("updateIncomingAudioElement('"+UUID+"')");
+ updateIncomingAudioElement(UUID);
+ }
+ }).catch(errorlog);
+ },0,i);
+ } else if ((session.audioEffects===true) || session.pushLoudness){
+ updateIncomingAudioElement(i);
+ log("updateIncomingAudioElement('"+i+"')");
+ }
+
+ }
+ } catch(e){
+ errorlog(e);
+ }
+ }
+}
+
+var videoElements = Array.from(document.querySelectorAll("video"));
+var audioElements = Array.from(document.querySelectorAll("audio"));
+var mediaStreamCounter = 0;
+
+
+function createMediaStream(){
+ mediaStreamCounter+=1;
+ return new MediaStream();
+}
+
+function deleteOldMedia(){
+ log("CHECKING FOR OLD MEDIA");
+ var i = videoElements.length;
+ while (i--) {
+ //if ((videoElements[i].id == "videosource") || (videoElements[i].id == "previewWebcam")){continue;} // exclude this one, for safety reasons. (Also, iOS safari blanks the video if streams are detached and moved between video elements)
+ if (videoElements[i].isConnected === false){
+ if ((videoElements[i].srcObject==null) || (videoElements[i].srcObject && videoElements[i].srcObject.active === false)){
+ if (videoElements[i].dataset && videoElements[i].dataset.UUID){
+ if (videoElements[i].dataset.UUID in session.rpcs){continue;} // still active, so lets not delete it.
+ }
+ videoElements[i].pause();
+ videoElements[i].removeAttribute("id");
+ videoElements[i].removeAttribute('src'); // empty source
+ videoElements[i].load();
+ videoElements[i].remove();
+ videoElements[i] = null;
+ videoElements.splice(i, 1);
+ }
+ }
+ }
+ i = audioElements.length;
+ while (i--) {
+ if (audioElements[i].isConnected === false){
+ if ((audioElements[i].srcObject==null) || (audioElements[i].srcObject && audioElements[i].srcObject.active === false)){
+ if (audioElements[i].dataset && audioElements[i].dataset.UUID){
+ if (audioElements[i].dataset.UUID in session.rpcs){continue;} // still active, so lets not delete it.
+ }
+ audioElements[i].pause();
+ audioElements[i].id = null;
+ audioElements[i].removeAttribute('src'); // empty source
+ audioElements[i].load();
+ audioElements[i].remove();
+ audioElements[i] = null;
+ audioElements.splice(i, 1);
+ }
+ }
+ }
+}
+
+function createAudioElement(){
+ try{
+ deleteOldMedia();
+ } catch(e){errorlog(e);}
+ var a = document.createElement("audio");
+ audioElements.push(a);
+ return a;
+}
+
+function compare_deltas( a, b ) {
+ var aa = a.delta || 0;
+ var bb = b.delta || 0;
+ if ( aa > bb ){
+ return 1;
+ }
+ if ( aa < bb ){
+ return -1;
+ }
+ return 0;
+}
+
+async function fetchWithTimeout(URL, timeout=8000){ // ref: https://dmitripavlutin.com/timeout-fetch-request/
+ try {
+ const controller = new AbortController();
+ const timeout_id = setTimeout(() => controller.abort(), timeout);
+ const response = await fetch(URL, {...{timeout:timeout}, signal: controller.signal});
+ clearTimeout(timeout_id);
+ return response;
+ } catch(e){
+ errorlog(e);
+ return await fetch(URL); // iOS 11.x/12.0
+ }
+}
+
+function createVideoElement(){
+ try{
+ deleteOldMedia();
+ } catch(e){errorlog(e);}
+ var v = document.createElement("video");
+ videoElements.push(v);
+ if (session.volume!==false){
+ v.volume = session.volume; // setting default volume
+ log("setting volume to manual");
+ }
+ return v;
+}
+
+function getTimezone(){
+ if (session.tz!==false){
+ return session.tz;
+ }
+ const stdTimezoneOffset = () => {
+ var jan = new Date(0, 1);
+ var jul = new Date(6, 1);
+ return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
+ }
+ var today = new Date();
+ const isDstObserved = (today) => {
+ return today.getTimezoneOffset() < stdTimezoneOffset();
+ }
+ if (isDstObserved(today)) {
+ return today.getTimezoneOffset()+60;
+ } else {
+ return today.getTimezoneOffset();
+ }
+}
+
+function promptUser(eleId, UUID=null){
+ if (session.beepToNotify){
+ playtone();
+ }
+ if (document.getElementById("modalBackdrop")){
+ getById("promptModal").innerHTML = ''; // Delete modal
+ getById("promptModal").remove();
+ getById("modalBackdrop").innerHTML = ''; // Delete modal
+ getById("modalBackdrop").remove();
+ }
+
+ zindex = 30 + document.querySelectorAll('#promptModal').length;
+ modalTemplate =
+ `
")+"";
+ inputText = inputText.replace(/\n/g,"
");
+ var type = "text";
+ if (asterix){
+ type = "password";
+ }
+
+
+ modalTemplate =
+ `
"))+"";
+ inputText = inputText.replace(/\n/g,"
");
+
+ modalTemplate =
+ `
")+"";
+ inputText = inputText.replace(/\n/g,"
");
+
+ modalTemplate =
+ `
");
+ } catch(e){
+ errorlog(message);
+ }
+ modalTemplate =
+ `';
+ getById("retryimage").src = decodeURIComponent(session.waitImage);
+ getById("retryimage").onerror = function(){this.style.display='none';};
+
+ if (session.cover) {
+ getById("retryimage").style.objectFit = "cover";
+ }
+ }
+ getById("retryimage").style.display = "block";
+ }, session.waitImageTimeout);
+ }
+ } else if (session.waitImage){
+ try {
+ clearTimeout(session.waitImageTimeoutObject);
+ session.waitImageTimeoutObject = false;
+ getById("retryimage").style.display = "none";
+ } catch(e){}
+ }
+
+ mediaPool.forEach(vid=>{
+ try {
+ if (!vid || !("id" in vid)){
+ errorlog(vid);
+ return;
+ }
+
+ if (session.slots){
+ if (("slot" in vid) && parseInt(vid.slot)){
+ i = parseInt(vid.slot) - 1;
+ if(i<0){return;}
+ } else {
+ return;
+ }
+ }
+
+ var offsetx=0;
+ if (i!==0){
+ if (Math.ceil((i+0.01)/rw)==rh){
+ if (mpl%rw){
+ offsetx = Math.max(((rw - mpl%rw)*(w/rw))/2,0);
+ }
+ }
+ }
+
+ var cover = session.cover
+ var borderOffset = session.border || 0;
+ var videoMargin = session.videoMargin || 0;
+ var borderRadius = session.borderRadius || 0;
+ var borderColor = session.borderColor || "#000";
+ var fadein = session.fadein || false;
+ var backgroundMedia = session.defaultMedia || false;
+ var animated = session.animatedMoves || 0;
+ if (!borderOffset){
+ borderColor = "#0000";
+ }
+
+ if (layout){
+ if (!(vid.dataset.sid && (vid.dataset.sid in layout))){
+ vid.isInvisible = true;
+ if (vid.container){
+ vid.container.style.display = "none";
+ }
+ if (vid.dataset.UUID){
+ session.requestRateLimit(session.hiddenSceneViewBitrate, vid.dataset.UUID, false); // it's added already, so we know it needs sound. But lets d
+ }
+ return;
+ }
+ if ("borderThickness" in layout[vid.dataset.sid]){
+ borderOffset = layout[vid.dataset.sid].borderThickness || 0;
+ }
+ if ("animated" in layout[vid.dataset.sid]){
+ animated = layout[vid.dataset.sid].animated || 0;
+ if (animated===true){
+ animated = session.animatedMoves || 50;
+ }
+ }
+ if ("margin" in layout[vid.dataset.sid]){
+ videoMargin = layout[vid.dataset.sid].margin || 0;
+ }
+ if ("rounded" in layout[vid.dataset.sid]){
+ borderRadius = layout[vid.dataset.sid].rounded || 0;
+ }
+ if (layout[vid.dataset.sid].borderColor){
+ borderColor = layout[vid.dataset.sid].borderColor;
+ }
+ if (layout[vid.dataset.sid].fadeIn){
+ fadein = layout[vid.dataset.sid].fadeIn;
+ }
+ if ("backgroundMedia" in layout[vid.dataset.sid]){
+ backgroundMedia = layout[vid.dataset.sid].backgroundMedia || false;
+ }
+ if (vid.container){
+ if (!((vid.nodeName == "IFRAME") && vid.isConnected)){ // moving an iframe will break it.
+ playarea.appendChild(vid.container);
+ }
+ }
+
+ }
+
+ var skipAnimation = false;
+ if (vid.isInvisible){
+ vid.isInvisible = false;
+ skipAnimation = true;
+ if (fadein){
+ vid.classList.add("fadein");
+ if (vid.holder){
+ vid.holder.classList.add("fadein");
+ }
+ }
+ }
+
+ offsety = Math.max((h - Math.ceil(mpl/rw)*Math.ceil(h/rh))/2,0);
+
+ if (vid.container){
+ var container = vid.container;
+ if (container.move){
+ clearInterval(container.move);
+ container.move = null;
+ }
+ } else {
+ var container = document.createElement("div");
+ vid.container = container;
+ }
+ container.style.position = "absolute";
+ container.style.display = "block";
+
+ // ANIMATED - CONTAINER ; width/height/z-index/cover///////////////
+ if (layout){
+ var left = (w/100*layout[vid.dataset.sid].x) || layout[vid.dataset.sid].xp || 0;
+ var top = (h/100*layout[vid.dataset.sid].y) || layout[vid.dataset.sid].yp || 0;
+ top+=hi;
+ var width = (w/100*layout[vid.dataset.sid].w) || layout[vid.dataset.sid].wp || 0;
+ var height = (h/100*layout[vid.dataset.sid].h) || layout[vid.dataset.sid].hp || 0;
+ if (layout[vid.dataset.sid].cover || layout[vid.dataset.sid].c){ // this should be true/false
+ vid.style.objectFit = "cover";
+ cover = true;
+ } else {
+ vid.style.objectFit = "contain"; // this should fall back to sessio.cover if no layout supplied
+ cover = false;
+ }
+ //container.style.zindex = 0;
+ //container.style.zIndex = layout[vid.dataset.sid].zIndex || layout[vid.dataset.sid].z || 0;
+
+ } else {
+ var left = Math.max(offsetx+Math.floor(((i%rw)+0)*w/rw),0);
+ var top = Math.max(offsety+Math.floor((Math.floor(i/rw)+0)*h/rh + hi),0);
+ var width = Math.ceil(w/rw);
+ var height = Math.ceil(h/rh);
+ //container.style.zIndex = 0;
+ }
+
+ if (cover){
+ vid.style.objectFit = "cover";
+ } else {
+ vid.style.objectFit = "contain";
+ }
+
+ if (animated && !skipAnimation){
+ container.tleft = left;
+ container.ttop = top;
+ container.twidth = width;
+ container.theight = height;
+
+ container.move = setInterval(function(CCC){
+
+ try{
+ if (!CCC){return;}
+ var ww = (parseInt(CCC.style.width) - CCC.twidth);
+ var hh = (parseInt(CCC.style.height) - CCC.theight);
+ var tt = (parseInt(CCC.style.top) - CCC.ttop);
+ var ll = (parseInt(CCC.style.left) - CCC.tleft);
+
+ if (Number.isNaN(ww)){
+ CCC.style.width = CCC.twidth;
+ CCC.style.height = CCC.theight;
+ CCC.style.top = CCC.ttop;
+ CCC.style.left = CCC.tleft;
+ clearInterval(CCC.move);
+ return;
+ } else if (Number.isNaN(hh)){
+ CCC.style.width = CCC.twidth;
+ CCC.style.height = CCC.theight;
+ CCC.style.top = CCC.ttop;
+ CCC.style.left = CCC.tleft;
+ clearInterval(CCC.move);
+ return;
+ } else if (Number.isNaN(tt)){
+ CCC.style.width = CCC.twidth;
+ CCC.style.height = CCC.theight;
+ CCC.style.top = CCC.ttop;
+ CCC.style.left = CCC.tleft;
+ clearInterval(CCC.move);
+ return;
+ } else if (Number.isNaN(ll)){
+ CCC.style.width = CCC.twidth;
+ CCC.style.height = CCC.theight;
+ CCC.style.top = CCC.ttop;
+ CCC.style.left = CCC.tleft;
+ clearInterval(CCC.move);
+ return;
+ }
+
+ var speed = (150 / (201 - animated)) || 1.5;
+
+ var skipRest = true;
+
+ if (ww <=2 && (ww >=-2)){
+ CCC.style.width = CCC.twidth+"px";
+ } else {
+ skipRest=false;
+ CCC.style.width = parseInt((parseInt(CCC.style.width) - ww/speed))+"px";
+ }
+
+ if (hh <=2 && (hh >=-2)){
+ CCC.style.height = CCC.theight+"px";
+ } else {
+ skipRest=false;
+ CCC.style.height = parseInt((parseInt(CCC.style.height) - hh/speed))+"px";
+ }
+
+ if (tt <=2 && (tt >=-2)){
+ CCC.style.top = CCC.ttop+"px";
+ } else {
+ skipRest=false;
+ CCC.style.top = parseInt((parseInt(CCC.style.top) - tt/speed))+"px";
+ }
+
+ if (ll <=2 && (ll >=-2)){
+ CCC.style.left = CCC.tleft+"px";
+ } else {
+ skipRest=false;
+ CCC.style.left = parseInt((parseInt(CCC.style.left) - ll/speed))+"px";
+ }
+
+ if (skipRest){
+ clearInterval(CCC.move);
+ return;
+ }
+ } catch(e){errorlog(e);}
+ }, 20, container);
+ } else if (layout){ ////////////////// NOT ANIMATED - CONTAINER ; width/height/z-index/cover///////////////
+
+ container.style.left = left+"px";
+ container.style.top = top+"px";
+ container.style.width = width+"px";
+ container.style.height = height+"px";
+
+ } else {
+
+ container.style.left = offsetx+Math.floor(((i%rw)+0)*w/rw)+"px";
+ container.style.top = offsety+Math.floor((Math.floor(i/rw)+0)*h/rh + hi)+"px";
+ container.style.width = Math.ceil(w/rw)+"px";
+ container.style.height = Math.ceil(h/rh)+"px";
+
+ }
+
+ //try {
+ if (vid.alreadyAdded && vid.alreadyAdded==true){
+ if (!container.holder){
+ var holder = document.createElement("div");
+ container.holder = holder;
+ holder.className = "holder";
+ holder.dataset.holder = true;
+ container.appendChild(holder);
+ holder.appendChild(vid);
+ } else {
+ var holder = container.holder;
+ }
+ } else if (vid.dataset.doNotMove){
+ vid.style.position = "absolute";
+ vid.style.left = left+"px";
+ vid.style.top = top+"px";
+ vid.style.width = width+"px";
+ vid.style.height = height+"px";
+ vid.style.display = "flex";
+ i+=1;
+ return;
+ } else {
+ if (!container.holder){
+ var holder = document.createElement("div");
+ container.holder = holder;
+ holder.className = "holder";
+ holder.dataset.holder = true;
+ holder.appendChild(vid);
+ container.appendChild(holder);
+ } else {
+ var holder = container.holder;
+ holder.prepend(vid);
+ }
+ playarea.appendChild(container);
+ vid.style.maxWidth = "100%";
+ vid.style.maxHeight = "100%";
+ }
+
+ if (layout){
+ var wrw = (w/100*layout[vid.dataset.sid].w) || 0;
+ var hrh = (h/100*layout[vid.dataset.sid].h) || 0;
+ } else {
+ var wrw = (w/rw);
+ var hrh = (h/rh);
+ }
+
+ vid.style.borderRadius = borderRadius+"px";
+ vid.style.borderColor = borderColor;
+
+ holder.style.borderRadius = borderRadius+"px";
+ holder.style.borderColor = borderColor;
+
+ holder.style.backgroundColor = borderColor;
+ holder.style.borderWidth = borderOffset+"px";
+
+
+ if (backgroundMedia){
+ holder.style.backgroundImage = "url("+backgroundMedia+")";
+ if (cover){
+ holder.style.backgroundSize = "cover";
+ } else {
+ holder.style.backgroundSize = "contain";
+ }
+ holder.style.backgroundPosition = "center";
+ holder.style.backgroundRepeat = "no-repeat";
+ } else if (holder.style.backgroundImage){
+ holder.style.backgroundImage = "block";
+ }
+
+ if (session.sharperScreen && sssid && vid.dataset.sid && (vid.dataset.sid === sssid) ){
+ // do not dynamically scale the screen share feed.
+ } else if (session.dynamicScale){
+ if (vid.dataset.UUID){
+ if (wrw && hrh){
+ if (session.devicePixelRatio){
+ session.requestResolution(vid.dataset.UUID, wrw * session.devicePixelRatio, hrh * session.devicePixelRatio, true); // snap=true; if resolution close to 100%, send 100%. screenshare only
+ } else if (window.devicePixelRatio && parseInt(window.devicePixelRatio) > 1 ){
+ session.requestResolution(vid.dataset.UUID, wrw*window.devicePixelRatio, hrh*window.devicePixelRatio, true);
+ } else {
+ session.requestResolution(vid.dataset.UUID, wrw, hrh, true);
+ }
+ }
+ }
+ }
+ if (("rotated" in vid) && (vid.rotated!==false)){
+ if (vid.rotated==90){
+ vid.style.transform = "rotate(90deg)";
+ } else if (vid.rotated==270){
+ vid.style.transform = "rotate(270deg)";
+ } else if (vid.rotated==180){
+ vid.style.transform = "rotate(180deg)";
+ } else if (!vid.rotated){
+ vid.style.transform = "rotate(0deg)";
+ }
+ }
+
+ vid.style.width = "100%";
+ vid.style.height = "100%";
+ holder.style.position = "absolute";
+
+
+ if (vid.classList.contains("paused")){
+ if (holder.paused){
+ holder.paused.className = "playButton";
+ } else {
+ var paused = document.createElement("span");
+ paused.id = "paused_"+vid.dataset.UUID;
+ paused.className = "playButton";
+ paused.dataset.UUID = vid.dataset.UUID;
+ paused.onclick = function(){unPauseVideo(vid);};
+ holder.paused = paused;
+ holder.appendChild(paused);
+ }
+ } else if (holder.paused){
+ holder.paused.className = "hidden";
+ }
+
+ var vw = vid.naturalWidth || vid.videoWidth; // naturalWidth is for images I guess
+ var vh = vid.naturalHeight || vid.videoHeight;
+
+ // log(vw + " : "+vh);
+
+ if (cover){
+ if ((("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270)))){
+ holder.style.left = borderOffset + "px";
+ holder.style.top = borderOffset + "px";
+ holder.style.height = "calc(100% - "+(videoMargin+borderOffset)+"px)";
+ holder.style.width = "calc(100% - "+(videoMargin+borderOffset)+"px)";
+
+ vid.style.width = (height - (borderOffset + videoMargin)*2) + "px";
+ vid.style.height = (width - (borderOffset + videoMargin)*2) + "px";
+ vid.style.left = 0;
+ vid.style.top = 0;
+ } else {
+ holder.style.left = borderOffset + videoMargin + "px";
+ holder.style.top = borderOffset + videoMargin +"px";
+ holder.style.height = "calc(100% - "+(videoMargin*2+borderOffset)+"px)";
+ holder.style.width = "calc(100% - "+(videoMargin*2+borderOffset)+"px)";
+ vid.style.width = "100%";
+ vid.style.height = "100%";
+ vid.style.left = 0;
+ vid.style.top = 0;
+ }
+ } else if ((vw && vh) || (vid.width && vid.height) || vid.dataset.aspectRatio){
+ if (("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270))){
+ if (vw && vh){
+ var vvw = parseInt(vh);
+ var vvh = parseInt(vw);
+ } else if (vid.width && vid.height){
+ var vvw = parseInt(vid.height);
+ var vvh = parseInt(vid.width);
+ } else { // video disabled; fall back to aspect Ratio
+ var vvw = 1;
+ var vvh = vid.dataset.aspectRatio;
+ }
+
+ vid.style.objectFit = "cover"; //contain;
+ vid.style.overflow = "unset"; //contain;
+ vid.style.maxWidth = "unset";
+ vid.style.maxHeight = "unset";
+ } else {
+ if (vw && vh){
+ var vvw = parseInt(vw);
+ var vvh = parseInt(vh);
+ } else if (vid.width && vid.height){
+ var vvw = parseInt(vid.width);
+ var vvh = parseInt(vid.height);
+ } else {
+ var vvw = vid.dataset.aspectRatio;
+ var vvh =1;
+ }
+ }
+
+ var asw = wrw/vvw; // (window.innerWidth/ N) / vid.videoHeight;
+ var ash = hrh/vvh;
+
+ if (asw < ash){
+ var hsw = wrw - videoMargin*2;
+ var hsh = hsw/(vvw/vvh) + borderOffset*2 - borderOffset*2/(vvw/vvh);
+ var hsl = videoMargin;
+ var hst = (hrh - hsh - videoMargin - borderOffset)/2 + videoMargin;
+
+ } else {
+ var hsh = hrh - videoMargin*2 ;;
+ var hsw = hsh*vvw/vvh + borderOffset*2 - borderOffset*2*(vvw/vvh);
+ var hsl = (wrw - hsw - videoMargin - borderOffset)/2;
+ var hst = videoMargin;
+ }
+
+ holder.style.left = Math.floor(hsl )+ "px"; // this needs to be replaced with padding. This means testing with rotation = 90
+ holder.style.top = Math.floor(hst)+ "px";
+ holder.style.width = Math.ceil(hsw) + 'px';
+ holder.style.height = Math.ceil(hsh) + 'px';
+ //holder.style.padding = videoMargin + "px";
+
+ if (("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270))){
+ vid.style.width = Math.ceil(wrw - borderOffset*2) + "px";
+ vid.style.height = Math.ceil(hsw - borderOffset*2) + "px";
+ vid.style.left = 0;
+
+ if (ChromeVersion && ChromeVersion<77){
+ if (!animated && (parseInt(container.style.width)>parseInt(holder.style.height))){
+ vid.style.position = "relative";
+ vid.style.objectFit = "contain"; //contain;
+ } else if (animated && (container.twidth && (parseInt(container.twidth)>parseInt(holder.style.height)))){
+ vid.style.position = "relative";
+ vid.style.objectFit = "contain"; //contain;
+ }
+ } else {
+ vid.style.position = "relative";
+ }
+
+
+ }
+ } else {
+ holder.style.left = (borderOffset + videoMargin) + "px";
+ holder.style.top = (borderOffset + videoMargin) + "px";
+ holder.style.height = "calc(100% - "+((borderOffset + videoMargin*2))+"px)";
+ holder.style.width = "calc(100% - "+((borderOffset + videoMargin*2))+"px)";
+ }
+
+ if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID] && ("label" in session.rpcs[vid.dataset.UUID]) && (session.rpcs[vid.dataset.UUID].label !== false) && (session.showlabels===true)){ // remote source
+
+ if (container && container.move && container.twidth && container.theight && animated){
+ var vidwidth = container.twidth;
+ var vidheight = container.theight ;
+ } else {
+ var vidwidth = vid.offsetWidth;
+ var vidheight = vid.offsetHeight;
+ }
+
+ var fontsize = (vidwidth + vidheight)*0.03;
+ if ((vidwidth/16)>=(vidheight/9)){
+ var voar = (vidwidth/16)/(vidheight/9);
+ } else {
+ var voar = (vidheight/9)/(vidwidth/16);
+ }
+ voar = Math.pow(voar,0.5);
+ fontsize = fontsize/voar;
+ // creates a video label holder inside the recently created label holder
+ if (holder.label){
+ var label = holder.label;
+ } else {
+ var label = document.createElement("span");
+ holder.label = label;
+ if (session.labelstyle){
+ label.className = 'video-label '+session.labelstyle;
+ } else {
+ label.className = 'video-label';
+ }
+ holder.appendChild(label);
+ }
+ if (fontsize){
+ if (session.labelsize){
+ fontsize = fontsize*session.labelsize/100;
+ }
+ label.style.fontSize = parseInt(fontsize)+"px";
+ }
+ label.innerText = session.rpcs[vid.dataset.UUID].label;
+
+ } else if ((session.showlabels===true) && (vid.id === "videosource") && (session.label)){ // local source
+ // creates a label holder that's the same size of the vid element.
+
+ if (container && container.move && container.twidth && container.theight && animated){
+ var vidwidth = container.twidth;
+ var vidheight = container.theight ;
+ } else {
+ var vidwidth = vid.offsetWidth;
+ var vidheight = vid.offsetHeight;
+ }
+
+ var fontsize = (vidwidth + vidheight)*0.03;
+ if ((vidwidth/16)>=(vidheight/9)){
+ var voar = (vidwidth/16)/(vidheight/9);
+ } else {
+ var voar = (vidheight/9)/(vidwidth/16);
+ }
+ voar = Math.pow(voar,0.5);
+ fontsize = fontsize/voar;
+
+ if (holder.label){
+ var label = holder.label;
+ } else {
+ var label = document.createElement("span");
+ holder.label = label;
+ if (session.labelstyle){
+ label.className = 'video-label '+session.labelstyle;
+ } else {
+ label.className = 'video-label';
+ }
+ holder.appendChild(label);
+ }
+ if (fontsize){
+ if (session.labelsize){
+ fontsize = fontsize*session.labelsize/100;
+ }
+ label.style.fontSize = parseInt(fontsize)+"px";
+ }
+
+ label.innerText = sanitizeLabel(session.label);//.replace(/[\W]+/g,"_").replace(/_+/g, ' ');
+ holder.appendChild(label);
+ }
+
+ if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID]){
+ if (session.rpcs[vid.dataset.UUID].voiceMeter){
+ holder.appendChild(session.rpcs[vid.dataset.UUID].voiceMeter);
+ }
+ if (session.rpcs[vid.dataset.UUID].remoteMuteElement){
+ holder.appendChild(session.rpcs[vid.dataset.UUID].remoteMuteElement);
+ }
+
+ if (session.signalMeter){
+ if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].signalMeter){
+ session.rpcs[vid.dataset.UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
+ session.rpcs[vid.dataset.UUID].signalMeter.classList.remove("hidden");
+ session.rpcs[vid.dataset.UUID].signalMeter.id = "signalMeter_" + vid.dataset.UUID;
+ session.rpcs[vid.dataset.UUID].signalMeter.dataset.level = 0;
+ session.rpcs[vid.dataset.UUID].signalMeter.title = miscTranslations["signal-meter"];
+ holder.appendChild(session.rpcs[vid.dataset.UUID].signalMeter);
+ holder.signalMeter = session.rpcs[vid.dataset.UUID].signalMeter;
+ } else if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID].signalMeter){
+ if (!holder.signalMeter){
+ holder.appendChild(session.rpcs[vid.dataset.UUID].signalMeter);
+ holder.signalMeter = session.rpcs[vid.dataset.UUID].signalMeter;
+ }
+ }
+ }
+
+ if (session.volumeControl && session.rpcs[vid.dataset.UUID].videoElement && (vid.tagName != "VIDEO")){
+ if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].volumeControl){
+ session.rpcs[vid.dataset.UUID].volumeControl = getById("volumeControlTemplate").cloneNode(true);
+ session.rpcs[vid.dataset.UUID].volumeControl.classList.remove("hidden");
+ session.rpcs[vid.dataset.UUID].volumeControl.id = "volumeControl_" + vid.dataset.UUID;
+ session.rpcs[vid.dataset.UUID].volumeControl.value = parseInt(session.rpcs[vid.dataset.UUID].videoElement.volume*100);
+ session.rpcs[vid.dataset.UUID].volumeControl.dataset.UUID = vid.dataset.UUID;
+ session.rpcs[vid.dataset.UUID].volumeControl.title = miscTranslations["volume-control"];
+ session.rpcs[vid.dataset.UUID].volumeControl.oninput = function(){
+ if (this.dataset.UUID && session.rpcs[this.dataset.UUID] && session.rpcs[this.dataset.UUID].videoElement){
+ session.rpcs[this.dataset.UUID].videoElement.volume = parseFloat(this.value/100);
+ }
+ }
+ holder.appendChild(session.rpcs[vid.dataset.UUID].volumeControl);
+ holder.volumeControl = session.rpcs[vid.dataset.UUID].volumeControl;
+ } else if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID].volumeControl){
+ if (!holder.volumeControl && session.rpcs[vid.dataset.UUID].videoElement){
+ holder.appendChild(session.rpcs[vid.dataset.UUID].volumeControl);
+ holder.volumeControl = session.rpcs[vid.dataset.UUID].volumeControl;
+ session.rpcs[vid.dataset.UUID].volumeControl.value = parseInt(session.rpcs[vid.dataset.UUID].videoElement.volume*100);
+ }
+ }
+ }
+
+ if (session.showConnections){
+ if (!session.rpcs[vid.dataset.UUID].connectionDetails){
+ createConnectionDetailsEle(vid.dataset.UUID);
+ }
+ holder.appendChild(session.rpcs[vid.dataset.UUID].connectionDetails);
+ }
+
+ }
+
+ if (session.ruleOfThirds){
+ if (vid.id == "videosource"){
+ if (!holder.svg){
+ var svg = document.createElement("img");
+ svg.src = session.ruleOfThirds;
+ svg.style.width = "100%";
+ svg.style.height = "100%";
+ svg.style.position= "absolute";
+ svg.style.left = "0";
+ svg.style.top = "0";
+ svg.style.pointerEvents= "none";
+ holder.svg = svg;
+ holder.appendChild(svg);
+ }
+ }
+ }
+
+ try {
+ if (!(session.cleanOutput && session.cleanish==false)){
+ if (session.firstPlayTriggered===false){ // don't play unless needed; might cause clicking or who knows what else.
+ if (vid.tagName.toLowerCase()=="video"){ // we don't want to try playing an Iframe or Canvas.
+ if (vid.paused){
+ warnlog("VIDEO IS NOT PLAYING");
+ var playPromise = vid.play();
+ if (playPromise !== undefined){
+ playPromise.then(_ => {
+ // playing
+ //session.firstPlayTriggered=true; // global tracking. "user gesture obtained", so no longer needed if playing.
+ }).catch((err)=>{
+ var bigPlayButton = document.getElementById("bigPlayButton");
+ if (bigPlayButton){
+ bigPlayButton.innerHTML = '';
+ bigPlayButton.style.display="block";
+ }
+ });
+ } else {
+ session.firstPlayTriggered=true; // well, I don't know if it's playing, and so whatever. fail gracefully.
+ }
+ }
+ }
+ }
+ }
+ } catch(e) {
+ errorlog(e);
+ var bigPlayButton = document.getElementById("bigPlayButton");
+ if (bigPlayButton){
+ bigPlayButton.parentNode.removeChild(bigPlayButton);
+ }
+
+ }
+
+ if (vid.nodeName == "IFRAME"){ // I need to add this back in at some point.
+ i+=1;
+ return;
+ }
+
+ if (!session.cleanOutput && !session.nocursor){
+ if ((session.roomid!==false) && (session.scene===false)){
+ if (!((vid.id === "videosource") && (miniPreview))){
+
+ if (!holder.button){
+ var button = document.createElement("div");
+ holder.button = button;
+ holder.appendChild(button);
+ } else {
+ var button = holder.button;
+ }
+
+ button.id = "button_"+vid.id;
+ button.dataset.button = true;
+ if (soloVideo){
+ button.innerHTML = "
";
+ button.title = "Show all active videos togethers";
+ button.style.visibility = "visible";
+ } else if (mpl>1){
+ button.innerHTML = "
";
+ button.title = "Enlarge video and increase its clarity";
+ button.style.visibility = "visible";
+ } else {
+ button.style.visibility = "hidden";
+ }
+ button.style.transition = "opacity 0.3s"
+ button.style.width ="4vh";
+ button.style.height = "4vh";
+ button.style.maxWidth ="30px";
+ button.style.maxHeight = "30px";
+ button.style.minWidth ="15px";
+ button.style.minHeight = "15px";
+ button.style.position = "absolute";
+ button.style.display="none";
+ //button.style.opacity="10%";
+ button.style.zIndex="6";
+ button.style.right = "4vh";//(Math.ceil(w/rw) -30 - 30 + offsetx+Math.floor(((i%rw)+0)*w/rw))+"px";
+ button.style.top = "4vh";//( offsety + 30 + Math.floor((Math.floor(i/rw)+0)*h/rh + hi))+"px";
+ button.style.color = "white";
+ button.style.cursor = "pointer";
+
+ if (vid.id == "videosource"){
+ button.onclick = function(event){
+ if (session.infocus === true){
+ session.infocus = false;
+ } else {
+ session.infocus = true;
+ }
+ setTimeout(()=>updateMixer(),10);
+
+ if (session.fullscreenButton){
+ if (session.infocus){
+ fullscreenPageToggle(true);
+ } else {
+ fullscreenPageToggle(false);
+ }
+ }
+ };
+ } else {
+ button.dataset.UUID = vid.dataset.UUID;
+ button.onclick = function(event){
+ var target = event.currentTarget;
+ if (session.infocus === target.dataset.UUID){
+ //target.childNodes[0].className = 'las la-arrows-alt';
+ session.infocus = false;
+ } else {
+ //target.childNodes[0].className = 'las la-compress';
+ session.infocus = target.dataset.UUID;
+ //log("session:"+target.dataset.UUID);
+ }
+ if (session.fullscreenButton){
+ if (session.infocus){
+ fullscreenPageToggle(true);
+ } else {
+ fullscreenPageToggle(false);
+ }
+ }
+ setTimeout(()=>updateMixer(),10);
+ };
+
+ }
+ vid.onclick = function(event){
+ if (session.disableMouseEvents){return;}
+ button.style.display="block";
+ container.style.backgroundColor= "#4444";
+ button.style.opacity="100%";
+ };
+ button.onmouseenter = function(event){
+ if (session.disableMouseEvents){return;}
+ button.style.display="block";
+ container.style.backgroundColor= "#4444";
+ setTimeout(function(button){button.style.opacity="100%";},0,button);
+
+ };
+ container.onmouseenter = function(event){
+ if (session.disableMouseEvents){return;}
+ button.style.display="block";
+ container.style.backgroundColor= "#4444";
+ setTimeout(function(button){button.style.opacity="100%";},0,button);
+ };
+ container.onmouseleave = function(event){
+ if (session.disableMouseEvents){return;}
+ button.style.display="none";
+ container.style.backgroundColor= null;
+ button.style.opacity="10%";
+ };
+ } else if ((vid.id === "videosource") && miniPreview && soloVideo==true){
+
+ if (!holder.button){
+ var button = document.createElement("div");
+ holder.button = button;
+ holder.appendChild(button);
+ } else {
+ var button = holder.button;
+ }
+
+ button.id = "button_videosource";
+ button.dataset.button = true;
+ if (soloVideo){
+ button.innerHTML = "
";
+ button.title = "Show all active videos togethers";
+ button.style.display="unset";
+ } else {
+ button.style.visibility = "hidden";
+ button.style.display="none";
+ }
+ button.style.transition = "opacity 0.3s"
+ button.style.width ="4vh";
+ button.style.height = "4vh";
+ button.style.maxWidth ="30px";
+ button.style.maxHeight = "30px";
+ button.style.minWidth ="15px";
+ button.style.minHeight = "15px";
+ button.style.position = "absolute";
+ button.style.zIndex="6";
+ button.style.right = "4vh";//(Math.ceil(w/rw) -30 - 30 + offsetx+Math.floor(((i%rw)+0)*w/rw))+"px";
+ button.style.top = "4vh";//( offsety + 30 + Math.floor((Math.floor(i/rw)+0)*h/rh + hi))+"px";
+ button.style.color = "white";
+ button.style.cursor = "pointer";
+
+ button.onclick = function(event){
+ event.stopPropagation();
+ event.preventDefault();
+
+ if (session.infocus === true){
+ session.infocus = false;
+ setTimeout(()=>updateMixer(),10);
+ }
+
+ if (session.fullscreenButton){
+ if (session.infocus){
+ fullscreenPageToggle(true);
+ } else {
+ fullscreenPageToggle(false);
+ }
+ }
+ };
+
+ }
+ }
+ }
+ i+=1;
+ } catch(err){errorlog(err);}
+ });
+ updateUserList()
+}
+
+
+var translationBacklog = [];
+
+function miniTranslate(ele, ident = false, direct=false) {
+
+ if (!translation){
+ translationBacklog.push([ele,ident]);
+ log('Translation backlogged');
+ if (!direct || !ident){
+ return;
+ }
+ }
+
+ if (ident){
+ if (translation.innerHTML && (ident in translation.innerHTML)){
+ if (ele.querySelector('[data-translate]')){
+ ele.querySelector('[data-translate]').innerHTML = translation.innerHTML[ident];
+ ele.querySelector('[data-translate]').dataset.translate = ident;
+ } else {
+ ele.innerHTML = translation.innerHTML[ident];
+ }
+ return;
+ } else if (direct){
+ if (ele.querySelector('[data-translate]')){
+ ele.querySelector('[data-translate]').innerHTML = direct;
+ ele.querySelector('[data-translate]').dataset.translate = ident;
+ } else {
+ ele.innerHTML = direct;
+ }
+ return;
+ } else {
+ warnlog(ident + ": not found in translation file");
+ }
+ }
+
+ var allItems = ele.querySelectorAll('[data-translate]');
+ allItems.forEach(function(ele) {
+ if (ele.dataset.translate in translation.innerHTML) {
+ ele.innerHTML = translation.innerHTML[ele.dataset.translate];
+ } else if (ele.dataset.translate in translation.miscellaneous) {
+ ele.innerHTML = translation.miscellaneous[ele.dataset.translate];
+ }
+ });
+ var allTitles = ele.querySelectorAll('[title]');
+ allTitles.forEach(function(ele) {
+ var key = ele.title.replace(/[\W]+/g, "-").toLowerCase();
+ if (key in translation.titles) {
+ ele.title = translation.titles[key];
+ }
+ });
+ var allPlaceholders = ele.querySelectorAll('[placeholder]');
+ allPlaceholders.forEach(function(ele) {
+ var key = ele.placeholder.replace(/[\W]+/g, "-").toLowerCase();
+ if (key in translation.placeholders) {
+ ele.placeholder = translation.placeholders[key];
+ }
+ });
+
+ //Object.keys(miscTranslations).forEach(key => {
+ // if (key in translation.miscellaneous) {
+ // miscTranslations[key] = translation.miscellaneous[key];
+ // }
+ //});
+ ///
+}
+
+var controlBarTimeout = false;
+function showControl(e){
+ if (controlBarTimeout){
+ clearTimeout(controlBarTimeout);
+ }
+ if (session.mobile){
+ getById("controlButtons").classList.remove("partialFadeout");
+ } else {
+ getById("controlButtons").classList.remove("fadeout");
+ }
+ controlBarTimeout = setTimeout(function(){
+ if (session.mobile){
+ getById("controlButtons").classList.add("partialFadeout");
+ } else {
+ getById("controlButtons").classList.add("fadeout");
+ }
+ }, 5000);
+}
+
+async function changeLg(lang) {
+ log("changeLg: "+lang);
+ await fetchWithTimeout("./translations/" + lang + '.json',2000).then(async function(response) {
+ try{
+ if (response.status !== 200) {
+ logerror('Language translation file not found.' + response.status);
+ getById("mainmenu").style.opacity = 1;
+ return;
+ }
+ await response.json().then(async function(data) {
+ translation = data; // translation.innerHTML[ele.dataset.translate]
+ var trans = data.innerHTML;
+ var allItems = document.querySelectorAll('[data-translate]');
+ allItems.forEach(function(ele) {
+ if (ele.dataset.translate in trans) {
+ ele.innerHTML = trans[ele.dataset.translate];
+ }
+ });
+ trans = data.titles;
+ var allTitles = document.querySelectorAll('[title]');
+ allTitles.forEach(function(ele) {
+ var key = ele.title.replace(/[\W]+/g, "-").toLowerCase();
+ if (key in trans) {
+ ele.title = trans[key];
+ }
+ });
+ trans = data.placeholders;
+ var allPlaceholders = document.querySelectorAll('[placeholder]');
+ allPlaceholders.forEach(function(ele) {
+ var key = ele.placeholder.replace(/[\W]+/g, "-").toLowerCase();
+ if (key in trans) {
+ ele.placeholder = trans[key];
+ }
+ });
+ if ("miscellaneous" in data){
+ trans = data.miscellaneous;
+ Object.keys(miscTranslations).forEach(key => {
+ if (key in trans) {
+ miscTranslations[key] = trans[key];
+ }
+ });
+ }
+ if (translationBacklog.length){
+ for (var i=0;i
";
+ added=true;
+ }
+ }
+
+ if (!added){
+ getById("connectUsers").style.display = "none";
+ } else {
+ getById("connectUsers").style.display = "block";
+ }
+ } catch(e){}
+ updateUserListActive=false;
+ },200);
+}
+
+
+function resetCanvas(){
+ log("resetCanvas();");
+ if (!session.streamSrc){
+ checkBasicStreamsExist();
+ return;
+ }
+ session.streamSrc.getVideoTracks().forEach((track) => {
+ session.canvasSource.width = track.getSettings().width || 1280;
+ session.canvasSource.height = track.getSettings().height || 720;
+ });
+}
+
+var LaunchTFWorkerCallback = false;
+function TFLiteWorker(){
+ if (session.tfliteModule==false){
+ LaunchTFWorkerCallback=true
+ return;
+ }
+ if (TFLITELOADING){LaunchTFWorkerCallback=true;return;}
+ LaunchTFWorkerCallback=false;
+ log("TFLiteWorker() called");
+
+ if (!session.tfliteModule.img){
+ if (!session.selectImageTFLITE_contents){
+ session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
+ }
+ if (session.selectImageTFLITE_contents.querySelector("img")){
+ session.tfliteModule.img = session.selectImageTFLITE_contents.querySelector("img");
+ session.tfliteModule.img.classList.add("selectedTFImage");
+ } else if (session.defaultBackgroundImages && session.defaultBackgroundImages.length){
+ session.tfliteModule.img = document.createElement("img");
+ session.tfliteModule.img.onload = function(){
+ URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
+ }
+ session.tfliteModule.img.src = session.defaultBackgroundImages[0];
+ session.tfliteModule.img.classList.add("selectedTFImage");
+ } else {
+ session.tfliteModule.img = document.createElement("img");
+ session.tfliteModule.img.onload = function(){
+ URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
+ }
+ session.tfliteModule.img.src = "./media/bg_sample.webp";
+ }
+ }
+
+ if (session.tfliteModule.looping){return;}
+
+ const segmentationWidth = 256;
+ const segmentationHeight = 144;
+ const segmentationPixelCount = segmentationWidth * segmentationHeight;
+ const inputMemoryOffset = session.tfliteModule._getInputMemoryOffset() / 4;
+ const outputMemoryOffset = session.tfliteModule._getOutputMemoryOffset() / 4;
+ const segmentationMask = new ImageData(segmentationWidth, segmentationHeight);
+ const segmentationMaskCanvas = document.createElement('canvas');
+ segmentationMaskCanvas.width = segmentationWidth;
+ segmentationMaskCanvas.height = segmentationHeight;
+ const segmentationMaskCtx = segmentationMaskCanvas.getContext('2d');
+ session.tfliteModule.nowTime = new Date().getTime();
+ session.tfliteModule.offsetTime = 0;
+
+ function process(){
+ clearTimeout(session.tfliteModule.timeout);
+
+ if (!(session.effect=="3" || session.effect=="4" || session.effect=="5")){
+ session.tfliteModule.looping=false;
+ return;
+ }
+ if (session.tfliteModule.activelyProcessing){return;}
+
+ session.tfliteModule.activelyProcessing=true;
+
+ if (session.mobile){
+ if (screenWidth !== window.innerWidth){
+ screenWidth = window.innerWidth;
+ setTimeout(function(){
+ updateRenderOutpipe();
+ },200);
+ session.tfliteModule.looping=false;
+ session.tfliteModule.activelyProcessing=false;
+ return;
+ }
+ }
+
+ try{
+ segmentationMaskCtx.drawImage(
+ session.canvasSource,
+ 0,
+ 0,
+ session.canvasSource.width,
+ session.canvasSource.height,
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight
+ )
+
+ const imageData = segmentationMaskCtx.getImageData(
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight
+ );
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
+ }
+
+ session.tfliteModule._runInference();
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
+ const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
+ const shift = Math.max(background, person);
+ const backgroundExp = Math.exp(background - shift);
+ const personExp = Math.exp(person - shift);
+ segmentationMask.data[i * 4 + 3] = (255 * personExp) / (backgroundExp + personExp); // softmax
+ }
+ segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
+
+ session.canvasCtx.globalCompositeOperation = 'copy';
+
+ if (session.mobile && (session.roomid !==false)){
+ session.canvasCtx.filter = 'none';
+ } else {
+ session.canvasCtx.filter = 'blur(4px)';
+ }
+ session.canvasCtx.drawImage(
+ segmentationMaskCanvas,
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight,
+ 0,
+ 0,
+ session.canvasSource.width,
+ session.canvasSource.height
+ )
+
+ session.canvasCtx.globalCompositeOperation = 'source-in';
+ session.canvasCtx.filter = 'none';
+ session.canvasCtx.drawImage(session.canvasSource, 0, 0);
+
+ session.canvasCtx.globalCompositeOperation = 'destination-over';
+ if (session.effect=="4"){ // greenscreen
+ session.canvasCtx.filter = 'none';
+ session.canvasCtx.fillStyle = "#0F0";
+ session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
+ } else if (session.effect=="5"){
+ session.canvasCtx.filter = 'none';
+ if (session.tfliteModule.img.complete){
+ try {
+ session.canvasCtx.drawImage(session.tfliteModule.img, 0, 0, session.canvas.width, session.canvas.height);
+ } catch(e){}
+ }
+ } else if (session.effect=="3"){ // BLUR
+ if (session.effectValue){
+ session.canvasCtx.filter = 'blur('+(parseInt(session.effectValue)*2)+'px)';
+ } else {
+ session.canvasCtx.filter = 'blur(4px)'; // Does not work on Safari
+ }
+ session.canvasCtx.drawImage(session.canvasSource, 0, 0);
+ } else {
+ session.tfliteModule.activelyProcessing=false;
+ session.tfliteModule.looping=false;
+ return;
+ }
+ } catch (e){
+ errorlog(e);
+ session.tfliteModule.activelyProcessing=false;
+ session.tfliteModule.looping=false;
+ return;
+ }
+
+ session.tfliteModule.lastTime = session.tfliteModule.nowTime;
+ session.tfliteModule.nowTime = new Date().getTime();
+ var time = 33 - (session.tfliteModule.nowTime - session.tfliteModule.lastTime);
+ time = time + session.tfliteModule.offsetTime;
+ session.tfliteModule.activelyProcessing=false;
+ if (time <= 0 ){
+ session.tfliteModule.timeout = setTimeout(function(){process();},0);
+ session.tfliteModule.offsetTime = 0;
+ } else {
+ session.tfliteModule.timeout = setTimeout(function(){process();},time);
+ session.tfliteModule.offsetTime = time;
+ }
+ }
+
+ function processiOS(){
+ clearTimeout(session.tfliteModule.timeout);
+ if (!(session.effect=="3" || session.effect=="4" || session.effect=="5")){
+ session.tfliteModule.looping=false;
+ return;
+ }
+ if (session.tfliteModule.activelyProcessing){return;}
+ session.tfliteModule.activelyProcessing=true;
+
+ if (screenWidth !== window.innerWidth){
+ screenWidth = window.innerWidth;
+ setTimeout(function(){
+ updateRenderOutpipe();
+ },200);
+ session.tfliteModule.looping=false;
+ session.tfliteModule.activelyProcessing=false;
+ return;
+ }
+
+ try{
+ segmentationMaskCtx.drawImage(
+ session.canvasSource,
+ 0,
+ 0,
+ session.canvasSource.width,
+ session.canvasSource.height,
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight
+ )
+
+ var imageData = segmentationMaskCtx.getImageData(
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight
+ );
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
+ session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
+ }
+
+ session.tfliteModule._runInference();
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
+ const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
+ const shift = Math.max(background, person);
+ const backgroundExp = Math.exp(background - shift);
+ const personExp = Math.exp(person - shift);
+ segmentationMask.data[i * 4 + 3] = 255 - (255 * personExp) / (backgroundExp + personExp); // softmax
+ }
+
+ segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
+
+ session.canvasCtx.globalCompositeOperation = 'copy';
+ session.canvasCtx.drawImage(session.canvasSource, 0, 0);
+
+ session.canvasCtx.globalCompositeOperation = 'destination-out';
+ session.canvasCtx.drawImage(
+ segmentationMaskCanvas,
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight,
+ 0,
+ 0,
+ session.canvasSource.width,
+ session.canvasSource.height
+ );
+
+ session.canvasCtx.globalCompositeOperation = 'destination-over';
+
+ if (session.effect=="4"){ // greenscreen
+ session.canvasCtx.fillStyle = "#0F0";
+ session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
+ } else if (session.effect=="5"){
+ if (session.tfliteModule.img.complete){
+ try {
+ session.canvasCtx.drawImage(session.tfliteModule.img, 0, 0, session.canvas.width, session.canvas.height);
+ } catch(e){}
+ }
+ } else if (session.effect=="3"){ // BLUR
+
+ const width = canvasBG.width;
+ const height = canvasBG.height;
+ ctxBG.drawImage(session.canvasSource, 0, 0, width, height);
+ imageData = ctxBG.getImageData(0, 0, width, height);
+
+ const { data } = imageData;
+
+ // THE BELOW BLUR CODE polyfil is by David Enke
+ // MIT License: Copyright (c) 2019
+ // https://github.com/steveseguin/context-filter-polyfill/blob/master/src/filters/blur.filter.ts
+ const wm = width - 1;
+ const hm = height - 1;
+ const rad1 = amount + 1;
+ const r = [];
+ const g = [];
+ const b = [];
+ //const a = [];
+
+ const vmin = [];
+ const vmax = [];
+
+ let iterations = 3; // 1 - 3
+ let p, p1, p2;
+ while (iterations-- > 0) {
+ let yw = 0;
+ let yi = 0;
+
+ for (let y = 0; y < height; y++) {
+ let rsum = data[yw] * rad1;
+ let gsum = data[yw + 1] * rad1;
+ let bsum = data[yw + 2] * rad1;
+
+ for (let i = 1; i <= amount; i++) {
+ p = yw + (((i > wm ? wm : i)) << 2);
+ rsum += data[p++];
+ gsum += data[p++];
+ bsum += data[p++];
+ }
+
+ for (let x = 0; x < width; x++) {
+ r[yi] = rsum;
+ g[yi] = gsum;
+ b[yi] = bsum;
+
+ if (y === 0) {
+ vmin[x] = ((p = x + rad1) < wm ? p : wm) << 2;
+ vmax[x] = ((p = x - amount) > 0 ? p << 2 : 0);
+ }
+
+ p1 = yw + vmin[x];
+ p2 = yw + vmax[x];
+
+ rsum += data[p1++] - data[p2++];
+ gsum += data[p1++] - data[p2++];
+ bsum += data[p1++] - data[p2++];
+
+ yi++;
+ }
+ yw += (width << 2);
+ }
+
+ for (let x = 0; x < width; x++) {
+ let yp = x;
+ let rsum = r[yp] * rad1;
+ let gsum = g[yp] * rad1;
+ let bsum = b[yp] * rad1;
+
+ for (let i = 1; i <= amount; i++) {
+ yp += (i > hm ? 0 : width);
+ rsum += r[yp];
+ gsum += g[yp];
+ bsum += b[yp];
+ }
+
+ yi = x << 2;
+
+ for (let y = 0; y < height; y++) {
+ data[yi] = ((rsum * mulSum) >>> shgSum);
+ data[yi + 1] = ((gsum * mulSum) >>> shgSum);
+ data[yi + 2] = ((bsum * mulSum) >>> shgSum);
+
+ if (x === 0) {
+ vmin[y] = ((p = y + rad1) < hm ? p : hm) * width;
+ vmax[y] = ((p = y - amount) > 0 ? p * width : 0);
+ }
+
+ p1 = x + vmin[y];
+ p2 = x + vmax[y];
+
+ rsum += r[p1] - r[p2];
+ gsum += g[p1] - g[p2];
+ bsum += b[p1] - b[p2];
+ yi += width << 2;
+ }
+ }
+ }
+ ////////////// END OF BLUR CODE - MIT LICENCED.
+ ctxBG.putImageData(imageData, 0, 0);
+ session.canvasCtx.drawImage(canvasBG, 0, 0, width, height, 0, 0, session.canvas.width, session.canvas.height);
+ } else {
+ session.tfliteModule.activelyProcessing=false;
+ session.tfliteModule.looping=false;
+ return;
+ }
+ } catch (e){
+ session.tfliteModule.activelyProcessing=false;
+ session.tfliteModule.looping=false;
+ errorlog(e);
+ return;
+ }
+
+ session.tfliteModule.lastTime = session.tfliteModule.nowTime;
+ session.tfliteModule.nowTime = new Date().getTime();
+ var time = 33 - (session.tfliteModule.nowTime - session.tfliteModule.lastTime);
+ time = time + session.tfliteModule.offsetTime;
+ session.tfliteModule.activelyProcessing=false;
+ if (time <= 0 ){
+ session.tfliteModule.timeout = setTimeout(function(){processiOS();},0);
+ session.tfliteModule.offsetTime = 0;
+ } else {
+ session.tfliteModule.timeout = setTimeout(function(){processiOS();},time);
+ session.tfliteModule.offsetTime = time;
+ }
+ }
+ session.tfliteModule.looping=true;
+
+ var screenWidth = window.innerWidth;
+
+ if (iOS || iPad || SafariVersion){
+ var canvasBG = document.createElement("canvas");
+ var ctxBG = canvasBG.getContext("2d", {alpha: false});
+ var amount = 1.0;
+ var mulTable = [1, 57, 41, 21, 203, 34, 97, 73, 227, 91, 149, 62, 105, 45, 39, 137, 241, 107, 3, 173, 39, 71, 65, 238, 219, 101, 187, 87, 81, 151, 141, 133, 249, 117, 221, 209, 197, 187, 177, 169, 5, 153, 73, 139, 133, 127, 243, 233, 223, 107, 103, 99, 191, 23, 177, 171, 165, 159, 77, 149, 9, 139, 135, 131, 253, 245, 119, 231, 224, 109, 211, 103, 25, 195, 189, 23, 45, 175, 171, 83, 81, 79, 155, 151, 147, 9, 141, 137, 67, 131, 129, 251, 123, 30, 235, 115, 113, 221, 217, 53, 13, 51, 50, 49, 193, 189, 185, 91, 179, 175, 43, 169, 83, 163, 5, 79, 155, 19, 75, 147, 145, 143, 35, 69, 17, 67, 33, 65, 255, 251, 247, 243, 239, 59, 29, 229, 113, 111, 219, 27, 213, 105, 207, 51, 201, 199, 49, 193, 191, 47, 93, 183, 181, 179, 11, 87, 43, 85, 167, 165, 163, 161, 159, 157, 155, 77, 19, 75, 37, 73, 145, 143, 141, 35, 138, 137, 135, 67, 33, 131, 129, 255, 63, 250, 247, 61, 121, 239, 237, 117, 29, 229, 227, 225, 111, 55, 109, 216, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 48, 190, 47, 93, 185, 183, 181, 179, 178, 176, 175, 173, 171, 85, 21, 167, 165, 41, 163, 161, 5, 79, 157, 78, 154, 153, 19, 75, 149, 74, 147, 73, 144, 143, 71, 141, 140, 139, 137, 17, 135, 134, 133, 66, 131, 65, 129, 1];
+ var mulSum = mulTable[amount];
+ var shgTable = [0, 9, 10, 10, 14, 12, 14, 14, 16, 15, 16, 15, 16, 15, 15, 17, 18, 17, 12, 18, 16, 17, 17, 19, 19, 18, 19, 18, 18, 19, 19, 19, 20, 19, 20, 20, 20, 20, 20, 20, 15, 20, 19, 20, 20, 20, 21, 21, 21, 20, 20, 20, 21, 18, 21, 21, 21, 21, 20, 21, 17, 21, 21, 21, 22, 22, 21, 22, 22, 21, 22, 21, 19, 22, 22, 19, 20, 22, 22, 21, 21, 21, 22, 22, 22, 18, 22, 22, 21, 22, 22, 23, 22, 20, 23, 22, 22, 23, 23, 21, 19, 21, 21, 21, 23, 23, 23, 22, 23, 23, 21, 23, 22, 23, 18, 22, 23, 20, 22, 23, 23, 23, 21, 22, 20, 22, 21, 22, 24, 24, 24, 24, 24, 22, 21, 24, 23, 23, 24, 21, 24, 23, 24, 22, 24, 24, 22, 24, 24, 22, 23, 24, 24, 24, 20, 23, 22, 23, 24, 24, 24, 24, 24, 24, 24, 23, 21, 23, 22, 23, 24, 24, 24, 22, 24, 24, 24, 23, 22, 24, 24, 25, 23, 25, 25, 23, 24, 25, 25, 24, 22, 25, 25, 25, 24, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 23, 25, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 24, 22, 25, 25, 23, 25, 25, 20, 24, 25, 24, 25, 25, 22, 24, 25, 24, 25, 24, 25, 25, 24, 25, 25, 25, 25, 22, 25, 25, 25, 24, 25, 24, 25, 18];
+ var shgSum = shgTable[amount];
+
+ log("session.canvas: "+session.canvas.width+"x"+session.canvas.height);
+ canvasBG.width = parseInt(session.canvas.width/12);;
+ canvasBG.height = parseInt(session.canvas.height/12);;
+ ctxBG.width = canvasBG.width;
+ ctxBG.height = canvasBG.height;
+ processiOS();
+
+ } else {
+ process();
+ }
+}
+
+
+function mainMeshMask() {
+ if ((session.TFJSModel === null) || (session.TFJSModel === true)){
+ setTimeout(function(){mainMeshMask();},1000);
+ return;
+ }
+ function heatMapColorforValue(value){
+ var h = parseInt((1.0 - value) * 240);
+ if (h<0){h=0;}
+ if (h>240){h=240;}
+ return "hsl(" + h + ", 100%, 50%)";
+ }
+ async function process(){
+ if (session.TFJSModel.activelyProcessing){return;}
+ session.TFJSModel.activelyProcessing = true;
+
+ clearTimeout(session.TFJSModel.timeout);
+
+ if (session.effect!="6"){
+ //session.TFJSModel.looping=false;
+ session.TFJSModel.activelyProcessing = false;
+ return;
+ }
+
+ const predictions = await session.TFJSModel.estimateFaces({
+ input: session.canvasSource
+ });
+
+ var output = [];
+ if (predictions.length > 0) {
+ for (let j = 0; j < predictions.length; j++) {
+ const fp = predictions[j].annotations;
+ session.canvasCtx.fillStyle = "#000000";
+ session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
+ const keypoints = predictions[j].scaledMesh
+ for (let i = 0; i < keypoints.length; i++) {
+ var [x,y,z] = keypoints[i];
+ x=parseInt(x);
+ y=parseInt(y);
+ z=parseInt(z);
+ if (session.pushEffectsData){
+ output.push(x);
+ output.push(y);
+ }
+ session.canvasCtx.fillStyle = heatMapColorforValue((z+40)/60);
+ session.canvasCtx.fillRect(x, y, 5, 5);
+ }
+ }
+ }
+
+ if (session.pushEffectsData){
+ //output = FastIntegerCompression.compress(output);
+ //log(output);
+ if (isIFrame){
+ parent.postMessage({
+ "effectsData": output,
+ "eID": session.pushEffectsData
+ }, session.iframetarget);
+ } else {
+ for (var i in session.pcs){
+ if (!session.pcs[i].sendChannel.bufferedAmount){ // don't overload things.
+ session.sendMessage({"effectsData": output, "eID":session.effect},i);
+ }
+ }
+ }
+ }
+
+ if (document.hidden) {
+ session.TFJSModel.lastTime = session.TFJSModel.nowTime || new Date().getTime();
+ session.TFJSModel.nowTime = new Date().getTime();
+ var time = 33 - (session.TFJSModel.nowTime - session.TFJSModel.lastTime);
+ if (time <= 0 ){
+ session.TFJSModel.timeout = setTimeout(function(){process();},0);
+ } else {
+ session.TFJSModel.timeout = setTimeout(function(){process();},time);
+ }
+ session.TFJSModel.activelyProcessing = false;
+ } else {
+ session.TFJSModel.timeout = setTimeout(function(){process();},33);
+ session.TFJSModel.activelyProcessing = false;
+ window.requestAnimationFrame(process);
+ }
+
+ }
+ process();
+}
+
+var faceDetector = false;
+var faceAlignment=false;
+var activeDetection = false;
+function drawFace() {
+ if (session.effect !== "1"){return;}
+ if (faceAlignment){
+ faceAlignment();
+ return;
+ } else if (faceAlignment===null){
+ return;
+ }
+ faceAlignment = null;
+
+ var timers = {};
+ timers.activelyProcessing=false;
+ timers.activelyProcessingDraw = false;
+
+ var ctx = session.canvasCtx;
+
+ function fde1(){
+
+ warnlog("LOADED drawFace()");
+
+
+ var lastFace = {};
+
+ //session.canvasSource.width = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().width || 1280;
+ //session.canvasSource.height = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().height || 720;
+
+ lastFace.x = session.canvasSource.width / 2;
+ lastFace.y = session.canvasSource.height / 2;
+ lastFace.w = session.canvasSource.width;
+ lastFace.h = session.canvasSource.height;
+
+ session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
+ session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
+
+ function detectFace(){
+ if (activeDetection){return;}
+ activeDetection=true;
+ if (session.effect !== "1"){return;}
+ try {
+ faceDetector.detect(session.canvasSource).then(faces => {
+ if (faces.length){
+ for (let face of faces) {
+ lastFace.x = face.boundingBox.x;
+ lastFace.y = face.boundingBox.y;
+ lastFace.w = face.boundingBox.width;
+ lastFace.h = face.boundingBox.height;
+ break;
+ }
+ }
+ //setTimeout(function(){draw();},0);
+ }).catch((e) => {
+ //errorlog("Boo, Face Detection failed: " + e);
+ });
+ } catch(e){}
+ setTimeout(function(){detectFace();},200);
+ activeDetection = false;
+ }
+
+ var wh = null;
+ var xa = null;
+ var ya = null;
+
+
+ function draw() {
+ if (timers.activelyProcessingDraw){return;}
+ timers.activelyProcessingDraw = true;
+ clearTimeout(timers.timeoutDraw);
+ if (session.effect !== "1"){
+ timers.activelyProcessingDraw = false;
+ return;
+ }
+
+ try {
+ if (!session.canvasSource.width){
+ timers.timeoutDraw = setTimeout(function(){draw();},1000);
+ timers.activelyProcessingDraw = false;
+ return
+ }
+ if (wh === null && session.canvasSource.width){
+ wh = Math.pow(session.canvasSource.width * session.canvasSource.width/36,0.5);
+
+ xa = 0
+ ya = 0;
+ }
+
+ session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
+ session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
+
+ if (lastFace.w){
+ wh = wh * 0.999 + Math.pow(lastFace.w*lastFace.h,0.5)*0.001;
+
+ var w = wh*6;
+
+ if (w>session.canvasSource.width){
+ w = session.canvasSource.width;
+ }
+ if (session.canvasSource.height>session.canvasSource.width){
+ if (w>session.canvasSource.height && session.canvasSource.height>session.canvasSource.width){
+ w = session.canvasSource.height;
+ }
+ } else if (w>session.canvasSource.width){
+ w = session.canvasSource.width;
+ }
+
+ var h = (w/session.canvasSource.width) * session.canvasSource.height;
+
+
+ xa = xa*0.998 + 0.002*(lastFace.x + lastFace.w/2);
+ ya = ya*0.998 + 0.002*(lastFace.y + lastFace.h/2);
+
+ var x = xa - w/2;
+ var y = ya - h/2;
+
+ if (x<0){x=0;}
+ if (y<0){y=0;}
+
+
+ if (x>session.canvasSource.width-w){x=session.canvasSource.width-w;}
+ if (y>session.canvasSource.height-h){y=session.canvasSource.height-h;}
+
+ if (x<0){x=0;}
+ if (y<0){y=0;}
+
+ }
+ //console.log(x, y, w, h, session.canvasSource.width, session.canvasSource.height);
+
+ ctx.drawImage(session.canvasSource, x, y, w, h, 0, 0, session.canvasSource.width, session.canvasSource.height);
+ //ctx.beginPath();
+ //ctx.rect(lastFace.x, lastFace.y, lastFace.w, lastFace.h);
+ // ctx.stroke();
+ } catch(e){}
+
+ if (document.hidden){
+ timers.lastTimeDraw = timers.nowTimeDraw || new Date().getTime();
+ timers.nowTimeDraw = new Date().getTime();
+ var time = 33 - (timers.nowTimeDraw - timers.lastTimeDraw);
+ if (time <= 0 ){
+ timers.timeoutDraw = setTimeout(function(){draw();},0);
+ } else {
+ timers.timeoutDraw = setTimeout(function(){draw();},time);
+ }
+ timers.activelyProcessingDraw = false;
+ } else {
+ timers.timeoutDraw = setTimeout(function(){draw();},33);
+ timers.activelyProcessingDraw = false;
+ window.requestAnimationFrame(draw);
+ }
+ }
+
+ if (window.FaceDetector == undefined) {
+ if (!session.cleanOutput){
+ warnUser('Face Detection API not detected.\n\nYou may be able to enable it here: chrome://flags/#enable-experimental-web-platform-features');
+ }
+ faceDetector = false;
+ } else {
+ faceDetector = new FaceDetector();
+ }
+
+ function fde2(){
+ if (!timers.activelyProcessingDraw){
+ draw();
+ }
+ if (!activeDetection){
+ detectFace();
+ }
+ };
+ fde2();
+ return fde2;
+ };
+ faceAlignment = fde1();
+}
+//////// END CANVAS EFFECTS ///////////////////
+
+var getFacesActive = false;
+async function getFaces(){
+
+ if (getFacesActive){return;}
+ getFacesActive = true;
+
+ if (session.grabFaceData){
+ if (!faceDetector){
+ if (window.FaceDetector == undefined) {
+ if (!session.cleanOutput){
+ warnUser('Face Detection API not detected.\n\nYou may be able to enable it here: chrome://flags/#enable-experimental-web-platform-features');
+ }
+ session.grabFaceData = false;
+ faceDetector = false;
+
+ getFacesActive = false;
+ return;
+ } else {
+ session.grabFaceData = 1;
+ faceDetector = new FaceDetector();
+ }
+ }
+ try {
+ var videos = {};
+ for (var UUID in session.rpcs){
+ if (session.rpcs[UUID].videoElement){
+ await faceDetector.detect(session.rpcs[UUID].videoElement).then(faces => {
+ videos[session.rpcs[UUID].streamID] = {};
+ videos[session.rpcs[UUID].streamID].videoWidth = session.rpcs[UUID].videoElement.videoWidth
+ videos[session.rpcs[UUID].streamID].videoHeight = session.rpcs[UUID].videoElement.videoHeight
+ videos[session.rpcs[UUID].streamID].faces = faces;
+ }).catch((e) => {
+ //errorlog("Boo, Face Detection failed: " + e);
+ });
+ }
+ }
+ log(videos);
+ } catch(e){}
+ pokeIframeAPI('face-tracking-data',videos);
+ setTimeout(function(){getFaces();},200);
+ }
+
+ getFacesActive=false;
+}
+//////
+
+var digitalZoomMain=false;
+function digitalZoom() {
+ if (session.effect !== "7"){return;}
+ if (digitalZoomMain){
+ digitalZoomMain();
+ return;
+ } else if (digitalZoomMain===null){
+ return;
+ }
+ digitalZoomMain = null;
+
+ var timers = {};
+ timers.activelyProcessing=false;
+ timers.activelyProcessingDraw = false;
+
+ var ctx = session.canvasCtx;
+
+ function fde1(){
+ try{
+ warnlog("LOADED digitalZoom()");
+
+ session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
+ session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
+
+ var xa = null;
+ var ya = null;
+ var zz = 1;
+
+
+ function draw() {
+ if (timers.activelyProcessingDraw){return;}
+ timers.activelyProcessingDraw = true;
+ clearTimeout(timers.timeoutDraw);
+ if (session.effect !== "7"){
+ zz = 1.0;
+ xa = 0;
+ ya = 0;
+ timers.activelyProcessingDraw = false;
+ return;
+ }
+ try {
+ if (!session.canvasSource.width){
+ timers.timeoutDraw = setTimeout(function(){draw();},1000);
+ timers.activelyProcessingDraw = false;
+ return
+ }
+ if (xa === null && session.canvasSource.width){
+ xa = 0;
+ ya = 0;
+ }
+
+ session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
+ session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
+
+
+
+ if (session.effectValue){
+ zz = 0.9*zz + session.effectValue *0.1;
+
+ xa = xa*0.9+ 0.1*(session.canvasSource.width - session.canvasSource.width/zz);
+ ya = ya*0.9 + 0.1*(session.canvasSource.height - session.canvasSource.height/zz);
+
+ }
+ //console.log(parseInt(zz), parseInt(xa), parseInt(ya), parseInt(session.canvasSource.width-xa*2), parseInt(session.canvasSource.height-ya*2));
+
+ ctx.drawImage(session.canvasSource, xa, ya, session.canvasSource.width-xa*2, session.canvasSource.height-ya*2, 0,0,session.canvasSource.width, session.canvasSource.height);
+ //ctx.beginPath();
+ //ctx.rect(lastFace.x, lastFace.y, lastFace.w, lastFace.h);
+ // ctx.stroke();
+ } catch(e){errorlog(e);}
+
+ if (document.hidden){
+ timers.lastTimeDraw = timers.nowTimeDraw || new Date().getTime();
+ timers.nowTimeDraw = new Date().getTime();
+ var time = 33 - (timers.nowTimeDraw - timers.lastTimeDraw);
+ if (time <= 0 ){
+ timers.timeoutDraw = setTimeout(function(){draw();},0);
+ } else {
+ timers.timeoutDraw = setTimeout(function(){draw();},time);
+ }
+ timers.activelyProcessingDraw = false;
+ } else {
+ timers.timeoutDraw = setTimeout(function(){draw();},33);
+ timers.activelyProcessingDraw = false;
+ window.requestAnimationFrame(draw);
+ }
+
+ }
+ } catch(e){
+ errorlog(e);
+ timers.activelyProcessingDraw = false;
+ }
+
+ function fde2(){
+ if (!timers.activelyProcessingDraw){
+ draw();
+ }
+ };
+ fde2();
+
+ return fde2;
+ };
+ digitalZoomMain = fde1();
+}
+//////// END CANVAS EFFECTS ///////////////////
+
+
+function getNativeOutputResolution(){
+ var tracks = session.videoElement.srcObject.getVideoTracks();
+ if (tracks.length && tracks[0].getSettings){
+ return tracks[0].getSettings();
+ } else {
+ return false;
+ }
+}
+
+function toggleSceneStats(button){
+
+ var UUID = button.dataset.UUID;
+
+ if (button.value==1){
+ button.value = 0;
+ button.classList.remove("pressed");
+ session.rpcs[UUID].allowGraphs = false;
+ } else {
+ button.value = 1;
+ button.classList.add("pressed");
+ session.rpcs[UUID].allowGraphs = true;
+ }
+
+ if (button.value==1){
+ getById("container_" + UUID).querySelectorAll('[data-no-scenes]').forEach(ele=>{
+ ele.classList.remove("hidden");
+ if (ele.dataset.message){
+ ele.innerHTML = "Requesting data ..";
+ }
+ });
+
+ if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]')){
+ getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]').classList.remove("hidden");
+ }
+ if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]')){
+ getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]').classList.remove("hidden");
+ }
+ session.sendRequest({'requestStatsContinuous':true, }, UUID);
+ } else {
+ session.sendRequest({'requestStatsContinuous':false, }, UUID);
+ if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]')){
+ getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]').classList.add("hidden");
+ }
+ if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]')){
+ getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]').classList.add("hidden");
+ }
+ }
+}
+function getColor(value) {
+ var hue = ((value) * 120).toString(10);
+ return ["hsl(", hue, ",100%,50%)"].join("");
+}
+
+function plotData(info, UUID, uuid) { // type = "bitrate" or "nacks"
+ log("plot data");
+
+ var container = getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]');
+
+ if (!container){
+ log("container not found");
+ return;
+ }
+ var canvas = getById("container_" + UUID).querySelector('canvas[data-uid="'+uuid+'"]');
+ var canvasNew = false
+ if (!canvas){
+ canvasNew = true;
+ canvas = document.createElement("canvas");
+ canvas.height = 50;
+ canvas.width = 124;
+ canvas.className = "canvasStats";
+ canvas.history_nacks = [];
+ canvas.history_bitrate = [];
+ canvas.target = 4000;
+ if (info.scene){
+ canvas.title = "Scene: "+info.scene+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
+ } else if (info.label){
+ canvas.title = "Label: "+info.label+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
+ } else {
+ canvas.title = "Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
+ }
+
+ canvas.dataset.uid = uuid;
+ container.appendChild(canvas);
+ }
+
+ selfDestructElement(UUID, uuid);
+
+ var context = canvas.getContext("2d");
+
+ var bitrate = 0;
+ if ("video_bitrate_kbps" in info){
+ bitrate = info.video_bitrate_kbps;
+ }
+ if (isNaN(bitrate)) {
+ bitrate = 0;
+ }
+
+ if (bitrate<0){bitrate = 0;}
+
+ var nacks = 0;
+ if ("nacks_per_second" in info){
+ nacks = info.nacks_per_second;
+ }
+ if (isNaN(nacks)) {
+ nacks = 0;
+ }
+ if (nacks<0){nacks = 0;}
+
+ var height = context.canvas.height;
+ var width = context.canvas.width;
+
+ canvas.history_nacks.push(nacks);
+ canvas.history_bitrate.push(bitrate);
+
+ canvas.history_nacks = canvas.history_nacks.slice(-125);
+ canvas.history_bitrate = canvas.history_bitrate.slice(-125);
+
+ var maxBitrate = Math.max(...canvas.history_bitrate);
+
+ var target = canvas.target || 4000;
+ if (target && (maxBitrate > target)){
+
+ canvas.target = maxBitrate*1.5; // set it higher than it needs to be, so it doens't jump around a lot
+ var yScale = height / canvas.target;
+ context.clearRect(0, 0, width, height);
+ var x = width - 1;
+ var w = 1;
+
+ for (var i = 0; i
Remote Publisher Disconnected";
+ return;
+ }
+
+ var statsObj = session.rpcs[UUID].stats;
+ var streamID = session.rpcs[UUID].streamID;
+ var scrollLeft = menu.scrollLeft;
+ var scrollTop = menu.scrollTop;
+ menu.innerHTML = "StreamID: " + streamID + "
";
+ menu.innerHTML += printValues(statsObj);
+ menu.scrollTop = scrollTop;
+ menu.scrollLeft = scrollLeft;
+}
+
+
+function plotDataSimple(canvas, bitrate, nacks=0) {
+ canvas.height = 50;
+ canvas.width = 124;
+ canvas.className = "canvasStats";
+ var context = canvas.getContext("2d");
+ if (isNaN(bitrate)) {
+ bitrate = 0;
+ }
+ if (isNaN(nacks)) {
+ nacks = 0;
+ }
+ var height = context.canvas.height;
+ var width = context.canvas.width;
+
+ var val = (10-nacks)/10;
+ if (val>1){val=1;}
+ else if (val<0){val=0;}
+
+ var yScale = height / 4000;
+ var x = width - 1;
+ var y = height - bitrate * yScale;
+ var w = 1;
+
+ context.fillStyle = getColor(val);;
+ context.fillRect(x, y, w, height);
+ context.fillStyle = "#FFFFFF55";
+ context.fillRect(x, y-2, w, 4);
+
+ if (y-5>0){
+ context.fillStyle = "#FFFFFF44";
+ context.fillRect(x, y+2, w, 1);
+ }
+
+ context.putImageData(context.getImageData(1, 0, width - 1, height), 0, 0);
+ context.clearRect(width - 1, 0, 1, height);
+}
+
+function printValues(obj) { // see: printViewStats
+ var out = "";
+ for (var key in obj) {
+ if (typeof obj[key] === "object") {
+ if (obj[key] != null) {
+ var tmp = key;
+ tmp = sanitizeChat((tmp));
+ out += "" + tmp + "
(Stereo-mode)";
+ if (value == 3) {
+ value = "3 (outbound hi-fi)
Use Headphones";
+ } else if (value == 1) {
+ value = "1 (in & out hi-fi)
Use Headphones";
+ } else if (value == 2) {
+ value = "3 (inbound hi-fi)";
+ } else if (value == 4) {
+ value = "3 (multichannel)
Use Headphones";
+ } else if (value == 5) {
+ value = "5 (auto-mode)
Use Headphones";
+ }
+ }
+ else if (value === false) {
+ continue
+ }
+ else if (value === "false") {
+ continue
+ }
+ else {
+ stat = stat.replaceAll("_", " ");
+ }
+ out += "" + tmp + "
";
+ }
+ });
+ keys.forEach(key=>{
+ if (typeof obj[key] !== "object") {
+ if (key.startsWith("_")){return;}
+
+ var stat = sanitizeChat(key);
+ var value = obj[key];
+ if (typeof value == "string") {
+ value = sanitizeChat((value));
+ }
+
+ if (value === false){return;}
+
+ if (key == 'useragent') {
+ value = ""+value+""
+ }
+
+ if (key == 'local_relay_IP') {
+ value = "" + value + "";
+ }
+ if (key == 'remote_relay_IP') {
+ value = "" + value + "";
+ }
+ if ((key == 'local_candidateType') && (value == "relay")){
+ value = "💸
";
+ }
+ for (var uuid in session.pcs) {
+ printViewValues(session.pcs[uuid].stats);
+ menu.innerHTML += "
";
+ }
+ if ((iOS) || (iPad)){
+ menu.innerHTML += "
";
+ }
+ try {
+ getById("menuStatsBox").scrollLeft = scrollLeft;
+ getById("menuStatsBox").scrollTop = scrollTop;
+ } catch (e) {}
+}
+
+function updateLocalStats(){
+
+ var totalBitrate = 0;
+ var totalBitrate2 = 0;
+ var cpuLimited = false;
+ var relayUsed = false;
+ var totalVideo = 0;
+ var totalAudio = 0;
+ var totalScenes = 0;
+ var meshcastActive = false;
+ var nackRate = 0;
+ var totalStreams = 0;
+
+ if (session.mc && session.mc.getSenders && session.mc.stats){
+ try {
+ var atot = 0;
+ var senders = session.mc.getSenders(); // for any connected peer, update the video they have if connected with a video already.
+ 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 && sender.track.kind == "video" && sender.track.enabled) {
+ meshcastActive = true;
+ } else if (sender.track && sender.track.kind == "audio" && sender.track.enabled && !session.muted) {
+ meshcastActive = true;
+ }
+ });
+ //totalAudio += atot;
+
+ if ("video_bitrate_kbps" in session.mc.stats){
+ totalBitrate+=session.mc.stats.video_bitrate_kbps || 0;
+ }
+ if ("audio_bitrate_kbps" in session.mc.stats){
+ totalBitrate+=session.mc.stats.audio_bitrate_kbps || 0;
+ }
+ if ("total_sending_bitrate_kbps" in session.mc.stats){
+ totalBitrate2+=session.mc.stats.total_sending_bitrate_kbps || 0;
+ }
+
+ if ("quality_limitation_reason" in session.mc.stats){
+ if (session.mc.stats.quality_limitation_reason == "cpu"){
+ cpuLimited=true;
+ }
+ }
+
+
+ if ("nacks_per_second" in session.mc.stats){
+ nackRate += session.mc.stats.nacks_per_second;
+ totalStreams += 1;
+ }
+
+ //if ("local_candidateType" in session.pcs[uuid].stats){
+ // if (session.pcs[uuid].stats.local_candidateType == "relay"){
+ // if (session.pcs[uuid].startTime && (Date.now() - session.pcs[uuid].startTime > 30000)){
+ // relayUsed=true;
+ // }
+ // }
+ //}
+
+
+ setTimeout(function(){
+
+ if (!session.mc){return;}
+ session.mc.getStats().then(function(stats) {
+ if ("audio_bitrate_kbps" in session.mc.stats){
+ session.mc.stats.audio_bitrate_kbps=0;
+ }
+
+ var nominatedCandidate = false;
+ var candidates = {};
+
+ stats.forEach(stat => {
+
+ if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
+
+ if (stat.type == "transport"){
+ if ("bytesSent" in stat) {
+ if ("_bytesSent" in session.mc.stats){
+ if (session.mc.stats._timestamp){
+ if (stat.timestamp){
+ session.mc.stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - session.mc.stats._bytesSent)/(stat.timestamp - session.mc.stats._timestamp));
+ }
+ }
+ }
+ session.mc.stats._bytesSent = stat.bytesSent;
+ }
+ if ("timestamp" in stat) {
+ session.mc.stats._timestamp = stat.timestamp;
+ }
+ } else if (stat.type == "outbound-rtp") {
+ if (stat.kind == "video") {
+ if ("framesPerSecond" in stat) {
+ session.mc.stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
+ } else if ("frameHeight" in stat){
+
+ if (("framesEncoded" in stat) && stat.timestamp){
+ var lastFramesEncoded = 0;
+ var lastTimestamp = 0;
+ try{
+ lastFramesEncoded = session.mc.stats._framesEncoded;
+ lastTimestamp = session.mc.stats._timestamp;
+ } catch(e){}
+ session.mc.stats._FPS = parseInt(10*(stat.framesEncoded - lastFramesEncoded)/(stat.timestamp/1000 - lastTimestamp))/10 || "?";
+ session.mc.stats._framesEncoded = stat.framesEncoded;
+ session.mc.stats._timestamp = stat.timestamp/1000;
+ session.mc.stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + session.mc.stats._FPS;
+ } else {
+ session.mc.stats.resolution = stat.frameWidth + " x " + stat.frameHeight;
+ }
+ }
+ if ("encoderImplementation" in stat) {
+ session.mc.stats.video_encoder = stat.encoderImplementation;
+ if (stat.encoderImplementation=="ExternalEncoder"){
+ session.mc.stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
+ session.mc.encoder = true;
+ } else if (stat.encoderImplementation=="MediaFoundationVideoEncodeAccelerator"){
+ session.mc.stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
+ session.mc.encoder = true;
+ } else {
+ session.mc.encoder = false; // this may not be actually accurate, but lets assume so.
+ }
+ }
+ if ("qualityLimitationReason" in stat) {
+ if (session.mc.stats.quality_limitation_reason){
+ if (session.mc.stats.quality_limitation_reason !== stat.qualityLimitationReason){
+ try{
+ var miniInfo = {};
+ miniInfo.qlr = stat.qualityLimitationReason;
+ if ("_hardwareEncoder" in session.mc.stats){
+ miniInfo.hw_enc = session.mc.stats._hardwareEncoder;
+ } else {
+ miniInfo.hw_enc = null;
+ }
+ session.sendMessage({"miniInfo":miniInfo});
+ } catch(e){warnlog(e);}
+ }
+ }
+ session.mc.stats.quality_limitation_reason = stat.qualityLimitationReason;
+ }
+
+ if ("bytesSent" in stat) {
+ if ("_bytesSentVideo" in session.mc.stats){
+ if (session.mc.stats._timestamp1){
+ session.mc.stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - session.mc.stats._bytesSentVideo)/(stat.timestamp - session.mc.stats._timestamp1));
+ if (stat.timestamp){
+ }
+ }
+ }
+ session.mc.stats._bytesSentVideo = stat.bytesSent;
+ }
+
+ if ("nackCount" in stat) {
+ if ("_nackCount" in session.mc.stats){
+ if (session.mc.stats._timestamp1){
+ if (stat.timestamp){
+ session.mc.stats.nacks_per_second = parseInt(10000*(stat.nackCount - session.mc.stats._nackCount)/(stat.timestamp - session.mc.stats._timestamp1))/10;
+
+ }
+ }
+ }
+ }
+ if ("retransmittedBytesSent" in stat) {
+ if ("_retransmittedBytesSent" in session.mc.stats){
+ if (session.mc.stats._timestamp1){
+ if (stat.timestamp){
+ session.mc.stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - session.mc.stats._retransmittedBytesSent)/(stat.timestamp - session.mc.stats._timestamp1));
+ }
+ }
+ }
+ }
+
+ if ("nackCount" in stat) {
+ session.mc.stats._nackCount = stat.nackCount;
+ }
+
+ if ("retransmittedBytesSent" in stat) {
+ session.mc.stats._retransmittedBytesSent = stat.retransmittedBytesSent;
+
+ }
+
+ if ("timestamp" in stat) {
+ session.mc.stats._timestamp1 = stat.timestamp;
+ }
+
+ if ("pliCount" in stat) {
+ session.mc.stats.total_pli_count = stat.pliCount;
+ }
+ if ("keyFramesEncoded" in stat) {
+ session.mc.stats.total_key_frames_encoded = stat.keyFramesEncoded;
+ }
+
+
+ } else if (stat.kind == "audio") {
+ if ("bytesSent" in stat) {
+ if (session.mc.stats._bytesSentAudio){
+ if (session.mc.stats._timestamp2){
+ if (stat.timestamp){
+ if ("audio_bitrate_kbps" in session.mc.stats){
+ session.mc.stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - session.mc.stats._bytesSentAudio)/(stat.timestamp - session.mc.stats._timestamp2));
+ } else {
+ session.mc.stats.audio_bitrate_kbps=0;
+ }
+ }
+ }
+ }
+ }
+ if ("timestamp" in stat) {
+ session.mc.stats._timestamp2 = stat.timestamp;
+ }
+
+ if ("bytesSent" in stat) {
+ session.mc.stats._bytesSentAudio = stat.bytesSent;
+
+ }
+ }
+ } else if (stat.type == "remote-candidate") {
+ candidates[stat.id] = stat;
+ } else if (stat.type == "local-candidate") {
+ candidates[stat.id] = stat;
+ } else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
+ if (!nominatedCandidate){
+ nominatedCandidate = stat;
+ } else if (nominatedCandidate.priority < stat.priority){
+ nominatedCandidate = stat;
+ }
+ } else if (Firefox && ("mimeType" in stat) && ("type" in stat) && (stat.type=="codec")) {
+ if (stat.mimeType.includes("video")){
+ session.mc.stats.video_codec = stat.mimeType.split("video/")[1];
+ } else if (stat.mimeType.includes("audio")){
+ session.mc.stats.audio_codec = stat.mimeType.split("audio/")[1];
+ }
+ }
+ return;
+ });
+
+ if (nominatedCandidate){
+ if ("availableOutgoingBitrate" in nominatedCandidate){
+ session.mc.stats.available_outgoing_bitrate_kbps = parseInt(nominatedCandidate.availableOutgoingBitrate/1024);
+ if (session.maxBandwidth!==false){
+ session.limitMaxBandwidth(session.mc.stats.available_outgoing_bitrate_kbps, session.pcs[UUID], false);
+ }
+ }
+ if ("totalRoundTripTime" in nominatedCandidate){
+ if ("responsesReceived" in nominatedCandidate){
+ session.mc.stats.average_roundTripTime_ms = parseInt((nominatedCandidate.totalRoundTripTime/nominatedCandidate.responsesReceived)*1000);
+ }
+ }
+ }
+ if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
+ if (candidates[nominatedCandidate.remoteCandidateId]){
+ var candidate = candidates[nominatedCandidate.remoteCandidateId];
+ if ("candidateType" in candidate) {
+ session.mc.stats.remote_candidateType = candidate.candidateType;
+ if (candidate.candidateType === "relay"){
+ if ("ip" in candidate) {
+ session.mc.stats.remote_relay_IP = candidate.ip;
+ }
+ if ("relayProtocol" in candidate) {
+ session.mc.stats.remote_relay_protocol = candidate.relayProtocol;
+ }
+ } else {
+ try {
+ delete session.mc.stats.remote_relay_IP;
+ delete session.mc.stats.remote_relay_protocol;
+ } catch(e){}
+ }
+ }
+ }
+ }
+ if (nominatedCandidate && nominatedCandidate.localCandidateId){
+ if (candidates[nominatedCandidate.localCandidateId]){
+ var candidate = candidates[nominatedCandidate.localCandidateId];
+ if ("candidateType" in candidate) {
+ session.mc.stats.local_candidateType = candidate.candidateType;
+
+ if (candidate.candidateType === "relay"){
+ if ("ip" in candidate) {
+ session.mc.stats.local_relay_IP = candidate.ip;
+ }
+ if ("relayProtocol" in candidate) {
+ session.mc.stats.local_relay_protocol = candidate.relayProtocol;
+ }
+ } else {
+ try {
+ delete session.mc.stats.local_relay_IP;
+ delete session.mc.stats.local_relay_protocol;
+ } catch(e){}
+ }
+
+ }
+ }
+ }
+
+
+ return;
+ });
+ }, 0);
+ } catch(e){errorlog(e);}
+ }
+
+ for (var uuid in session.pcs) {
+ if (!session.pcs[uuid].stats){continue;}
+
+ var atot = 0;
+ var senders = getSenders2(uuid); // for any connected peer, update the video they have if connected with a video already.
+ 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 && sender.track.kind == "video" && sender.track.enabled) {
+ totalVideo+=1
+ } else if (sender.track && sender.track.kind == "audio" && sender.track.enabled && !session.muted) {
+ atot=1;
+ }
+ });
+ totalAudio += atot;
+
+ if ("scene" in session.pcs[uuid]){
+ if (session.pcs[uuid].scene!==false){
+ totalScenes+=1;
+ }
+ }
+
+ if ("video_bitrate_kbps" in session.pcs[uuid].stats){
+ totalBitrate+=session.pcs[uuid].stats.video_bitrate_kbps || 0;
+ }
+ if ("audio_bitrate_kbps" in session.pcs[uuid].stats){
+ totalBitrate+=session.pcs[uuid].stats.audio_bitrate_kbps || 0;
+ }
+ if ("total_sending_bitrate_kbps" in session.pcs[uuid].stats){
+ totalBitrate2+=session.pcs[uuid].stats.total_sending_bitrate_kbps || 0;
+ }
+
+ if ("quality_limitation_reason" in session.pcs[uuid].stats){
+ if (session.pcs[uuid].stats.quality_limitation_reason == "cpu"){
+ cpuLimited=true;
+ }
+ }
+
+ if ("nacks_per_second" in session.pcs[uuid].stats){
+ nackRate += session.pcs[uuid].stats.nacks_per_second;
+ totalStreams += 1;
+ }
+
+ //if ("local_candidateType" in session.pcs[uuid].stats){
+ // if (session.pcs[uuid].stats.local_candidateType == "relay"){
+ // if (session.pcs[uuid].startTime && (Date.now() - session.pcs[uuid].startTime > 30000)){
+ // relayUsed=true;
+ // }
+ // }
+ //}
+
+ if (uuid in session.rpcs){
+ if (session.pcs[uuid].stats.label){
+ session.pcs[uuid].stats.label = session.rpcs[uuid].label;
+ }
+ if (session.pcs[uuid].stats.streamID){
+ session.pcs[uuid].stats.streamID = session.rpcs[uuid].streamID;
+ }
+ }
+
+ setTimeout(function(UUID) {
+ if (!( session.pcs[UUID])){return;}
+ session.pcs[UUID].getStats().then(function(stats) {
+
+ if (!(UUID in session.pcs)){return;}
+
+ if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
+ session.pcs[UUID].stats.audio_bitrate_kbps=0;
+ }
+
+ var nominatedCandidate = false;
+ var candidates = {};
+
+ stats.forEach(stat => {
+
+ if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
+
+ if (stat.type == "transport"){
+ if ("bytesSent" in stat) {
+ if ("_bytesSent" in session.pcs[UUID].stats){
+ if (session.pcs[UUID].stats._timestamp3){
+ if (stat.timestamp){
+ session.pcs[UUID].stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp3));
+ }
+ }
+ }
+ session.pcs[UUID].stats._bytesSent = stat.bytesSent;
+ }
+ if ("timestamp" in stat) {
+ session.pcs[UUID].stats._timestamp3 = stat.timestamp;
+ }
+ } else if (stat.type == "outbound-rtp") {
+ if (stat.kind == "video") {
+
+ if ("framesPerSecond" in stat) {
+ session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
+ } else if ("frameHeight" in stat){
+
+ if (("framesEncoded" in stat) && stat.timestamp){
+ var lastFramesEncoded = 0;
+ var lastTimestamp = 0;
+ try{
+ lastFramesEncoded = session.pcs[UUID].stats._framesEncoded;
+ lastTimestamp = session.pcs[UUID].stats._timestamp;
+ } catch(e){}
+ session.pcs[UUID].stats._FPS = parseInt(10*(stat.framesEncoded - lastFramesEncoded)/(stat.timestamp - lastTimestamp))/10;
+ session.pcs[UUID].stats._framesEncoded = stat.framesEncoded;
+ session.pcs[UUID].stats._timestamp = stat.timestamp;
+ session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + session.pcs[UUID].stats._FPS;
+ } else {
+ session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight;
+ }
+ }
+
+ var miniInfo = {};
+ var sendMini = false;
+
+ if ("encoderImplementation" in stat) {
+ session.pcs[UUID].stats.video_encoder = stat.encoderImplementation;
+
+ if (stat.encoderImplementation=="ExternalEncoder"){
+ session.pcs[UUID].stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
+ if (session.pcs[UUID].encoder !== true){
+ session.pcs[UUID].encoder = true;
+ miniInfo.hw_enc = true;
+ sendMini = true;
+ }
+ } else if (stat.encoderImplementation=="MediaFoundationVideoEncodeAccelerator"){
+ session.pcs[UUID].stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
+ if (session.pcs[UUID].encoder !== true){
+ session.pcs[UUID].encoder = true;
+ miniInfo.hw_enc = true;
+ sendMini = true;
+ }
+ } else {
+ if (session.pcs[UUID].encoder === true){
+ session.pcs[UUID].encoder = false;
+ miniInfo.hw_enc = false;
+ sendMini = true;
+ }
+ }
+ }
+
+ if ("qualityLimitationReason" in stat) {
+ if (session.pcs[UUID].stats.quality_limitation_reason){
+ if (session.pcs[UUID].stats.quality_limitation_reason !== stat.qualityLimitationReason){
+ try{
+ sendMini = true;
+ miniInfo.qlr = stat.qualityLimitationReason;
+ } catch(e){warnlog(e);}
+ }
+ }
+ session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
+ }
+
+ if (sendMini){
+ session.sendMessage({"miniInfo":miniInfo}, UUID);
+ }
+
+ if ("bytesSent" in stat) {
+ if ("_bytesSentVideo" in session.pcs[UUID].stats){
+ if (session.pcs[UUID].stats._timestamp1){
+ if (stat.timestamp){
+ session.pcs[UUID].stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentVideo)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
+ }
+ }
+ }
+ session.pcs[UUID].stats._bytesSentVideo = stat.bytesSent;
+ }
+
+ if ("nackCount" in stat) {
+ if ("_nackCount" in session.pcs[UUID].stats){
+ if (session.pcs[UUID].stats._timestamp1){
+ if (stat.timestamp){
+ session.pcs[UUID].stats.nacks_per_second = parseInt(10000*(stat.nackCount - session.pcs[UUID].stats._nackCount)/(stat.timestamp - session.pcs[UUID].stats._timestamp1))/10;
+
+ }
+ }
+ }
+ }
+ if ("retransmittedBytesSent" in stat) {
+ if ("_retransmittedBytesSent" in session.pcs[UUID].stats){
+ if (session.pcs[UUID].stats._timestamp1){
+ if (stat.timestamp){
+ session.pcs[UUID].stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - session.pcs[UUID].stats._retransmittedBytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
+ }
+ }
+ }
+ }
+
+ if ("nackCount" in stat) {
+ session.pcs[UUID].stats._nackCount = stat.nackCount;
+ }
+
+ if ("retransmittedBytesSent" in stat) {
+ session.pcs[UUID].stats._retransmittedBytesSent = stat.retransmittedBytesSent;
+
+ }
+
+ if ("timestamp" in stat) {
+ session.pcs[UUID].stats._timestamp1 = stat.timestamp;
+ }
+
+ if ("pliCount" in stat) {
+ session.pcs[UUID].stats.total_pli_count = stat.pliCount;
+ }
+ if ("keyFramesEncoded" in stat) {
+ session.pcs[UUID].stats.total_key_frames_encoded = stat.keyFramesEncoded;
+ }
+
+
+ } else if (stat.kind == "audio") {
+ if ("bytesSent" in stat) {
+ if (session.pcs[UUID].stats._bytesSentAudio){
+ if (session.pcs[UUID].stats._timestamp2){
+ if (stat.timestamp){
+ if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
+ session.pcs[UUID].stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentAudio)/(stat.timestamp - session.pcs[UUID].stats._timestamp2));
+ } else {
+ session.pcs[UUID].stats.audio_bitrate_kbps=0;
+ }
+ }
+ }
+ }
+ }
+ if ("timestamp" in stat) {
+ session.pcs[UUID].stats._timestamp2 = stat.timestamp;
+ }
+
+ if ("bytesSent" in stat) {
+ session.pcs[UUID].stats._bytesSentAudio = stat.bytesSent;
+
+ }
+ }
+ } else if (stat.type == "remote-candidate") {
+ candidates[stat.id] = stat;
+ } else if (stat.type == "local-candidate") {
+ candidates[stat.id] = stat;
+ } else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
+ if (!nominatedCandidate){
+ nominatedCandidate = stat;
+ } else if (nominatedCandidate.priority < stat.priority){
+ nominatedCandidate = stat;
+ }
+ } else if (Firefox && ("mimeType" in stat) && ("type" in stat) && (stat.type=="codec")) {
+ if (stat.mimeType.includes("video")){
+ session.pcs[UUID].stats.video_codec = stat.mimeType.split("video/")[1];
+ } else if (stat.mimeType.includes("audio")){
+ session.pcs[UUID].stats.audio_codec = stat.mimeType.split("audio/")[1];
+ }
+ }
+ return;
+ });
+
+ if (nominatedCandidate){
+ if ("availableOutgoingBitrate" in nominatedCandidate){
+ session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(nominatedCandidate.availableOutgoingBitrate/1024);
+ if (session.maxBandwidth!==false){
+ session.limitMaxBandwidth(session.pcs[UUID].stats.available_outgoing_bitrate_kbps, session.pcs[UUID], false);
+ }
+ }
+ if ("totalRoundTripTime" in nominatedCandidate){
+ if ("responsesReceived" in nominatedCandidate){
+ session.pcs[UUID].stats.average_roundTripTime_ms = parseInt((nominatedCandidate.totalRoundTripTime/nominatedCandidate.responsesReceived)*1000);
+ }
+ }
+ }
+ if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
+ if (candidates[nominatedCandidate.remoteCandidateId]){
+ var candidate = candidates[nominatedCandidate.remoteCandidateId];
+ if ("candidateType" in candidate) {
+ session.pcs[UUID].stats.remote_candidateType = candidate.candidateType;
+ if (candidate.candidateType === "relay"){
+ if ("ip" in candidate) {
+ session.pcs[UUID].stats.remote_relay_IP = candidate.ip;
+ }
+ if ("relayProtocol" in candidate) {
+ session.pcs[UUID].stats.remote_relay_protocol = candidate.relayProtocol;
+ }
+ } else {
+ try {
+ delete session.pcs[UUID].stats.remote_relay_IP;
+ delete session.pcs[UUID].stats.remote_relay_protocol;
+ } catch(e){}
+ }
+ }
+ }
+ }
+ if (nominatedCandidate && nominatedCandidate.localCandidateId){
+ if (candidates[nominatedCandidate.localCandidateId]){
+ var candidate = candidates[nominatedCandidate.localCandidateId];
+ if ("candidateType" in candidate) {
+ session.pcs[UUID].stats.local_candidateType = candidate.candidateType;
+
+ if (candidate.candidateType === "relay"){
+ if ("ip" in candidate) {
+ session.pcs[UUID].stats.local_relay_IP = candidate.ip;
+ }
+ if ("relayProtocol" in candidate) {
+ session.pcs[UUID].stats.local_relay_protocol = candidate.relayProtocol;
+ }
+ } else {
+ try {
+ delete session.pcs[UUID].stats.local_relay_IP;
+ delete session.pcs[UUID].stats.local_relay_protocol;
+ } catch(e){}
+ }
+
+ }
+ }
+ }
+
+ return;
+ });
+ }, 0, uuid);
+ }
+
+
+
+ try{
+ var totalCon = Object.keys(session.pcs).length || 0;
+ var headerStats = "🔗 ";
+ headerStats += totalCon
+ if (meshcastActive){
+ if (totalAudio){
+ headerStats += ", 👂 "+totalAudio;
+ }
+ if (totalVideo){
+ headerStats += ", 👀 "+totalVideo;
+ }
+ headerStats += ", 📡Broadcast";
+ } else {
+ headerStats += ", 👂 "+totalAudio;
+ headerStats += ", 👀 "+totalVideo;
+ }
+ if (session.roomid){
+ headerStats += ", 🎬 "+totalScenes+"";
+ }
+
+ var changed = false;
+ if (!session.info.out){
+ session.info.out = {};
+ session.info.out.v = totalVideo;
+ session.info.out.a = totalAudio;
+ session.info.out.c = totalCon;
+ changed = true;
+ } else {
+ if (session.info.out.a !== totalAudio){
+ session.info.out.a = totalAudio;
+ // changed = true; // I'm not sending this data, so why bother
+ }
+ if (session.info.out.v !== totalVideo){
+ session.info.out.v = totalAudio;
+ //changed = true; // I'm not sending this data, so why bother
+ }
+ if (session.info.out.c !== totalCon){
+ if (session.info.out.c){
+ changed = true; // update if I'm not the first one
+ }
+ session.info.out.c = totalCon;
+ }
+ }
+ } catch(e){}
+ //session.info.out = {};
+
+
+ var uploadQuality = nackRate / totalStreams || 0;
+ if (totalStreams === 0){
+ uploadQuality = "title='Connection seems good' style='color: transparent; text-shadow: 0 0 0 #4d9bff;'";
+ } else if (uploadQuality === 0){
+ uploadQuality = "title='Connection seems good' style='color: transparent; text-shadow: 0 0 0 #0F0;'";
+ } else if (uploadQuality <= 1){
+ uploadQuality = "title='Mild connection issues' style='color: transparent; text-shadow: 0 0 0 yellow;'";
+ } else if (uploadQuality <= 5){
+ uploadQuality = "title='Moderate connection issues' style='color: transparent; text-shadow: 0 0 0 orange;'";
+ } else {
+ uploadQuality = "title='Severe connection issues' style='color: transparent; text-shadow: 0 0 0 #F00;'";
+ }
+
+ if (Firefox && (totalBitrate===0 && totalBitrate2===0)){
+ // does not support the current stats system
+ } else if (totalBitrate > totalBitrate2){
+ headerStats += ", 🔺 "+(Math.round(totalBitrate/10.24)/100) + "-mbps";
+ } else if (totalBitrate2>1000){
+ headerStats += ", 🔺 "+(Math.round(totalBitrate2/10.24)/100) + "-mbps";
+ } else{
+ headerStats += ", 🔺 "+totalBitrate2 + "-kbps";
+ }
+
+
+ if (session.director || !session.roomid){ // show stats if the director or if not in a group room
+ if (cpuLimited){
+ headerStats += ", 🔥 CPU Overloaded";
+ }
+ //if (relayUsed){
+ // headerStats += " 💸";
+ //}
+ }
+
+ var miniInfo = {}
+ if (changed){
+ miniInfo.out = {};
+ miniInfo.out.c = session.info.out.c
+ }
+
+ if (session.cpuLimited!==cpuLimited){
+ session.cpuLimited = cpuLimited;
+ miniInfo.cpu = cpuLimited;
+ changed = true;
+ }
+
+ if (changed){
+ for (var uuid in session.pcs) {
+ session.sendMessage({"miniInfo":miniInfo}, uuid); // lets send it to everyone.
+ }
+ }
+
+ try{
+ if (Object.keys(session.pcs).length){
+ getById("head5").classList.remove("hidden");
+ }
+ } catch(e){}
+ getById("head5").innerHTML = headerStats;
+ getById("head5").onclick = function(){
+ var [menu, innerMenu] = statsMenuCreator();
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+ printMyStats(innerMenu);
+ }
+}
+
+
+function updateStats(obsvc = false) {
+ if (document.getElementById('previewWebcam')) {
+ var ele = document.getElementById('previewWebcam');
+ var wcs = "webcamstats";
+ } else if (document.getElementById('videosource')) {
+ var ele = document.getElementById('videosource');
+ var wcs = "webcamstats3";
+ } else {
+ return;
+ }
+
+ try {
+ getById(wcs).innerHTML = "";
+ ele.srcObject.getVideoTracks().forEach(
+ function(track) {
+ if ((obsvc) && (parseInt(track.getSettings().frameRate) == 30)) {
+ getById(wcs).innerHTML = "Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + " @ up to 60fps";
+ } else {
+ var frameRateFPS = track.getSettings().frameRate;
+ if (frameRateFPS){
+ getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + "@" + (parseInt(frameRateFPS * 100) / 100.0) + "fps";
+ } else {
+ getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0);
+ }
+ }
+ }
+ );
+
+ } catch (e) {
+ errorlog(e);
+ }
+}
+
+function toggleControlBar() {
+ if (getById("controlButtons").style.display != 'none') {
+ // Dont hardcode style here. Copy it over to data-style before changing to none;
+ getById("controlButtons").dataset.style = getById("controlButtons").style.display;
+ getById("controlButtons").style.display = 'none';
+ } else {
+ // Copy the style over from the data-style attribute.
+ getById("controlButtons").style.display = getById("controlButtons").dataset.style;
+ };
+}
+
+
+
+
+function toggleMute(apply = false, event=false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
+
+ var mouseUp = null;
+ var touchEnd = null;
+ var timeStart = Date.now();
+ if (event){
+ mouseUp = document.onmouseup;
+ touchEnd = document.ontouchend;
+ document.onmouseup = function(){
+ document.onmouseup = mouseUp;
+ document.ontouchend = touchEnd;
+ if (Date.now() - timeStart < 500){
+ return;
+ } else {
+ toggleMute();
+ }
+ }
+ document.ontouchend = function(){
+ document.onmouseup = mouseUp;
+ document.ontouchend = touchEnd;
+ if (Date.now() - timeStart < 300){
+ return;
+ } else {
+ toggleMute();
+ }
+ }
+ }
+
+ if (session.director) {
+ if (!session.directorEnabledPPT) {
+ log("Director doesn't have PPT enabled yet");
+ // director has not enabled PTT yet.
+ return;
+ }
+ }
+
+ if (apply) {
+ session.muted = !session.muted; // we flip here as we are going to flip again in a second.
+ }
+ //try{var ptt = getById("press2talk");} catch(e){var ptt=false;}
+
+
+
+ if (session.muted == false) {
+ session.muted = true;
+ getById("mutetoggle").className = "las la-microphone-slash my-float toggleSize";
+ if (!(session.cleanOutput)){
+ getById("mutebutton").classList.remove("float");
+ getById("mutebutton").classList.add("float2");
+ getById("mutebutton").classList.add("red");
+ getById("mutebutton").classList.add("puslate");
+ getById("header").classList.add('red');
+
+ if (session.localMuteElement){
+ session.localMuteElement.style.display = "block";
+ }
+
+ }
+ if (session.streamSrc) {
+ session.streamSrc.getAudioTracks().forEach((track) => {
+ track.enabled = false;
+ });
+ }
+ if ((iOS || iPad) && session.videoElement && session.videoElement.srcObject) {
+ session.videoElement.srcObject.getAudioTracks().forEach((track) => {
+ track.enabled = false;
+ });
+ }
+
+ } else {
+ session.muted = false;
+ getById("mutetoggle").className = "las la-microphone my-float toggleSize";
+ if (!(session.cleanOutput)){
+
+ getById("mutebutton").classList.add("float");
+ getById("mutebutton").classList.remove("float2");
+ getById("mutebutton").classList.remove("red");
+ getById("mutebutton").classList.remove("puslate");
+
+ getById("header").classList.remove('red');
+
+ if (session.localMuteElement){
+ session.localMuteElement.style.display = "none";
+ }
+
+ }
+ if (session.streamSrc) {
+ session.streamSrc.getAudioTracks().forEach((track) => {
+ track.enabled = true;
+ });
+ }
+ if ((iOS || iPad) && session.videoElement && session.videoElement.srcObject) {
+ session.videoElement.srcObject.getAudioTracks().forEach((track) => {
+ track.enabled = true;
+ });
+ }
+ //if (ptt){
+ // ptt.innerHTML = "🔴 Push to Mute";
+ //}
+ }
+
+ postMessageIframe(document.getElementById("screensharesource"), {"mic":!session.muted});
+
+ if (!apply) { // only if they are changing states do we bother to spam.
+ data = {};
+ data.muteState = session.muted;
+ session.sendMessage(data);
+ log("SEND MUTE STATE TO PEERS");
+ pokeIframeAPI('mic-mute-state', session.muted);
+ pokeAPI("muted", session.muted);
+ }
+}
+
+function postMessageIframe(iFrameEle, message){ // iframes seem to only have the contentWindow work on the last placed iframe object, so this checks the dom first.
+ if (iFrameEle){
+ try{
+ if (iFrameEle.id && document.getElementById(iFrameEle.id)){
+ document.getElementById(iFrameEle.id).contentWindow.postMessage(message, '*');
+ } else {
+ iFrameEle.contentWindow.postMessage(message, '*');
+ }
+ } catch(e){errorlog(e);}
+ }
+}
+
+function toggleSpeakerMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
+
+ if (CtrlPressed) {
+ resetupAudioOut();
+ }
+
+ if (apply) {
+ session.speakerMuted = !session.speakerMuted;
+ }
+ if (session.speakerMuted == false) { // mute output
+ session.speakerMuted = true;
+ getById("mutespeakertoggle").className = "las la-volume-mute my-float toggleSize";
+ if (!(session.cleanOutput)){
+ getById("mutespeakerbutton").className = "float2 red";
+ }
+ var sounds = document.getElementsByTagName("video");
+
+ if (iOS || iPad){
+ for (var i = 0; i < sounds.length; ++i) {
+ sounds[i].muted = !sounds[i].muted;
+ sounds[i].muted = session.speakerMuted;
+ }
+ } else {
+ for (var i = 0; i < sounds.length; ++i) {
+ sounds[i].muted = session.speakerMuted;
+ }
+ }
+
+ } else {
+ session.speakerMuted = false; // unmute output
+
+ getById("mutespeakertoggle").className = "las la-volume-up my-float toggleSize";
+ if (!(session.cleanOutput)){
+ getById("mutespeakerbutton").className = "float";
+ }
+ var sounds = document.getElementsByTagName("video");
+
+ if (iOS || iPad){ // attempting to fix an iOS bug
+ for (var i = 0; i < sounds.length; ++i) {
+ sounds[i].muted = !sounds[i].muted;
+ if (sounds[i].id === "videosource") { // don't unmute ourselves. feedback galore if so.
+ sounds[i].muted = true;
+ continue;
+ } else if (sounds[i].id === "previewWebcam") {
+ sounds[i].muted = true;
+ continue;
+ } else if (sounds[i].id === "screenshare") {
+ sounds[i].muted = true;
+ continue;
+ } else {
+ sounds[i].muted = session.speakerMuted;
+ }
+ }
+ } else {
+ for (var i = 0; i < sounds.length; ++i) {
+
+ if (sounds[i].id === "videosource") { // don't unmute ourselves. feedback galore if so.
+ continue;
+ } else if (sounds[i].id === "previewWebcam") {
+ continue;
+ } else if (sounds[i].id === "screenshare") {
+ continue;
+ } else {
+ sounds[i].muted = session.speakerMuted;
+ }
+ }
+ }
+
+ }
+
+ for (var UUID in session.rpcs) {
+ applyMuteState(UUID);
+ postMessageIframe(session.rpcs[UUID].iframeEle, {"mute":session.speakerMuted});
+ }
+
+ pokeIframeAPI("audio-mute-state", session.speakerMuted);
+
+ if (!apply) {
+ pokeAPI("speakerMuted", session.speakerMuted);
+ }
+
+ if ((iOS) || (iPad)) {
+ resetupAudioOut();
+ }
+}
+
+function toggleFileshare(UUID=false, event = null){
+ if (UUID===false){
+ var string = 'Share a file with the group
';
+ } else if (session.directorList.indexOf(UUID)>=0){
+ var string = 'The director requested you share a file with them.
';
+ } else {
+ var string = 'Someone has requested you share a file with them.
';
+ }
+ warnUser(string, false, false);
+ if (session.hostedFiles){
+ if (session.hostedFiles.length){
+ getById("activeShares").innerHTML += "
(only guests can see this feed)
(only guests can see this feed)This is you, a co-director.
You are also a performer.This is you, the director.
You are also a performer.
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
+ details = details.split(' ').join('+');
+ details = details.split('&').join('%26');
+ var linkToOpen = "https://calendar.google.com/calendar/r/eventedit?text="+title+"&details="+details;
+ //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
+
+ window.open(linkToOpen);
+
+}
+
+function addToOutlookCalendar(){
+ var title = "Live Stream";
+ var linkout = getById("director_block_1").innerText;
+ var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
+ details = details.split(' ').join('%20');
+ details = details.split('&').join('%26');
+
+
+ var linkToOpen = "https://outlook.live.com/owa/?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&subject="+title+"&body="+details;
+ //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
+
+ window.open(linkToOpen);
+}
+
+function addToYahooCalendar(){
+ var title = "Live Stream";
+ var linkout = getById("director_block_1").innerText;
+ var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
+ details = details.split(' ').join('%20');
+ details = details.split('&').join('%26');
+ var linkToOpen = "https://calendar.yahoo.com?v60&title="+title+"&desc="+details;
+ //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
+
+ window.open(linkToOpen);
+}
+
+function toggle(ele, tog = false, inline = true) {
+ var x = ele;
+ if (x.style.display === "none") {
+ if (inline) {
+ x.style.display = "inline-block";
+ } else {
+ x.style.display = "block";
+ }
+ } else {
+ x.style.display = "none";
+ }
+ if (tog) {
+ if (tog.dataset.saved) {
+ tog.innerHTML = tog.dataset.saved;
+ delete(tog.dataset.saved);
+ } else {
+ tog.dataset.saved = tog.innerHTML;
+ tog.innerHTML = "Hide This";
+ }
+ }
+}
+
+function toggleByDataset(filter) {
+ var elements = document.querySelectorAll('[data-cluster="'+filter+'"]'); // ie: .cluster1
+ for (var i = 0; i < elements.length; i++) {
+ elements[i].classList.toggle('hidden');
+ }
+}
+
+
+var SelectedAudioOutputDevices = false; // session.sink
+var SelectedAudioInputDevices = []; // ..
+var SelectedVideoInputDevices = []; // ..
+
+async function enumerateDevices() {
+
+ log("enumerated start");
+
+ if (typeof navigator.enumerateDevices === "function") {
+ log("enumerated failed 1");
+ return await navigator.enumerateDevices();
+ } else if (typeof navigator.mediaDevices === "object" && typeof navigator.mediaDevices.enumerateDevices === "function") {
+ return await navigator.mediaDevices.enumerateDevices();
+ } else {
+ return await new Promise((resolve, reject) => {
+ try {
+ if (window.MediaStreamTrack == null || window.MediaStreamTrack.getSources == null) {
+ throw new Error();
+ }
+ window.MediaStreamTrack.getSources((devices) => {
+ resolve(devices
+ .filter(device => {
+ return device.kind.toLowerCase() === "video" || device.kind.toLowerCase() === "videoinput";
+ })
+ .map(device => {
+ return {
+ deviceId: device.deviceId != null ? device.deviceId : ""
+ , groupId: device.groupId
+ , kind: "videoinput"
+ , label: device.label
+ , toJSON: /* istanbul ignore next */ function() {
+ return this;
+ }
+ };
+ }));
+ });
+ } catch (e) {
+ errorlog(e);
+ }
+ });
+ }
+}
+
+function requestOutputAudioStream() {
+ try {
+ //warnlog("GET USER MEDIA");
+ return navigator.mediaDevices.getUserMedia({
+ audio: true
+ , video: false
+ }).then(function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
+ log("get media sources; request audio stream");
+ return enumerateDevices().then(function(deviceInfos) {
+ stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
+ track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
+ });
+ const audioOutputSelect = getById('outputSourceScreenshare');
+ audioOutputSelect.remove(0);
+ audioOutputSelect.removeAttribute("onclick");
+
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ const deviceInfo = deviceInfos[i];
+ if (deviceInfo == null) {
+ continue;
+ }
+ const option = document.createElement('option');
+ option.value = deviceInfo.deviceId;
+ if (deviceInfo.kind === 'audiooutput') {
+ const option = document.createElement('option');
+ if (audioOutputSelect.length === 0) {
+ option.dataset.default = true;
+ } else {
+ option.dataset.default = false;
+ }
+ option.value = deviceInfo.deviceId || "default";
+ if (option.value == session.sink) {
+ option.selected = "true";
+ }
+ option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
+ audioOutputSelect.appendChild(option);
+ } else {
+ log('Some other kind of source/device: ', deviceInfo);
+ }
+ }
+ });
+ });
+ } catch (e) {
+ if (!(session.cleanOutput)) {
+ if (window.isSecureContext) {
+ warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
+ } else if ((iOS) || (iPad)) {
+ warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
+ } else {
+ warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
+ }
+ }
+ }
+}
+
+
+function requestAudioStream() {
+ try {
+ //warnlog("GET USER MEDIA");
+ return navigator.mediaDevices.getUserMedia({
+ audio: true
+ , video: false
+ }).then(function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
+ log("get media sources; request audio stream");
+ return enumerateDevices().then(function(deviceInfos) {
+ stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
+ track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
+ });
+ log("updating audio");
+ const audioInputSelect = getById('audioSourceScreenshare');
+ audioInputSelect.remove(1);
+ audioInputSelect.removeAttribute("onchange");
+
+
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ const deviceInfo = deviceInfos[i];
+ if (deviceInfo == null) {
+ continue;
+ }
+ const option = document.createElement('option');
+ option.value = deviceInfo.deviceId;
+ if (deviceInfo.kind === 'audioinput') {
+ option.text = deviceInfo.label || `Microphone ${audioInputSelect.length + 1}`;
+ audioInputSelect.appendChild(option);
+ } else {
+ log('Some other kind of source/device: ', deviceInfo);
+ }
+ }
+ audioInputSelect.style.minHeight = ((audioInputSelect.childElementCount + 1) * 1.15 * 16) + 'px';
+ audioInputSelect.style.minWidth = "342px";
+ });
+ });
+ } catch (e) {
+ if (!(session.cleanOutput)) {
+ if (window.isSecureContext) {
+ warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
+ } else if ((iOS) || (iPad)) {
+ warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
+ } else {
+ warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
+ }
+ }
+ }
+}
+
+function saveSettings(){
+ if (session.store){
+ try {
+ var tmp = {};
+ tmp.SelectedAudioInputDevices = SelectedAudioInputDevices;
+ if (session.sink && (session.sink!="default")){
+ tmp.SelectedAudioOutputDevices = session.sink;
+ } else if (!session.sink && SelectedAudioOutputDevices && (SelectedAudioOutputDevices!="default")){
+ tmp.SelectedAudioOutputDevices = SelectedAudioOutputDevices;
+ }
+ tmp.SelectedVideoInputDevices = SelectedVideoInputDevices;
+ setStorage("session_store", JSON.stringify(tmp));
+ log("Saving settings");
+ } catch(e){errorlog(e);}
+ }
+}
+
+function loadSettings(){
+ if (session.store){
+ try {
+ session.store = getStorage("session_store");
+ if (session.store){
+ session.store = JSON.parse(session.store);
+ } else {
+ session.store = {};
+ }
+ log("Loading saved settings");
+ log(session.store);
+
+ if (session.store && session.store.SelectedAudioOutputDevices){
+ if (typeof session.store.SelectedAudioOutputDevices == "string"){
+ SelectedAudioOutputDevices = session.store.SelectedAudioOutputDevices;
+ } else if (typeof session.store.SelectedAudioOutputDevices == "object"){
+ if (session.store.SelectedAudioOutputDevices.length){
+ SelectedAudioOutputDevices = session.store.SelectedAudioOutputDevices[0];
+ }
+ }
+ }
+ if (session.store && session.store.SelectedAudioInputDevices){
+ SelectedAudioInputDevices = session.store.SelectedAudioInputDevices;
+ }
+ if (session.store && session.store.SelectedVideoInputDevices){
+ SelectedVideoInputDevices = session.store.SelectedVideoInputDevices;
+ }
+ } catch(e){}
+ }
+}
+
+function gotDevices(deviceInfos) {
+
+ log("got devices!1");
+ log(deviceInfos);
+ try {
+ const audioInputSelect = document.getElementById('audioSource') || document.getElementById('audioSource3');
+ const videoSelect = document.getElementById('videoSourceSelect') || document.getElementById('videoSource3');
+ const audioOutputSelect = document.getElementById('outputSource') || document.getElementById('outputSource3');
+ const multiselectTrigger = document.getElementById('multiselect-trigger') || document.getElementById('multiselect-trigger3');
+ const multiselect2 = document.getElementById('multiselect2') || document.getElementById('multiselect2a');
+
+
+ var option = document.createElement('input');
+ option.type = "checkbox";
+ option.value = "ZZZ";
+ option.name = "multiselect1";
+ option.id = "multiselect1";
+ option.style.display = "none";
+ option.checked = true;
+
+
+ var label = document.createElement('label');
+ label.for = option.name;
+ label.innerHTML = ' No Audio';
+
+ var listele = document.createElement('li');
+ listele.appendChild(option);
+ listele.appendChild(label);
+
+ //return;
+
+ audioInputSelect.innerHTML = "";
+ audioInputSelect.appendChild(listele);
+
+ audioOutputSelect.innerHTML = "";
+
+
+
+ option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
+ if (!(getById("multiselect1").checked)) {
+ getById("multiselect1").checked = true;
+
+ if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
+ SelectedAudioInputDevices.push(event.currentTarget.value);
+ }
+
+ log("CHECKED 1");
+ } else {
+ var list = audioInputSelect.querySelectorAll("li>input");
+ for (var i = 0; i < list.length; i++) {
+ if (list[i].id !== "multiselect1") {
+ list[i].checked = false;
+ }
+ }
+
+ while (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {
+ SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(event.currentTarget.value), 1);
+ }
+ }
+ saveSettings();
+ };
+
+ multiselectTrigger.dataset.state = '0';
+ multiselectTrigger.classList.add('closed');
+ multiselectTrigger.classList.remove('open');
+ getById('chevarrow1').classList.add('bottom');
+
+
+ const selectors = [videoSelect];
+
+ const values = selectors.map(select => select.value);
+ selectors.forEach(select => {
+ while (select.firstChild) {
+ select.removeChild(select.firstChild);
+ }
+ });
+
+
+ function comp(a, b) {
+ if (a.kind === 'audioinput') {
+ return 0;
+ } else if (a.kind === 'audiooutput') {
+ return 0;
+ }
+ const labelA = a.label.toUpperCase();
+ const labelB = b.label.toUpperCase();
+ if (labelA > labelB) {
+ return 1;
+ } else if (labelA < labelB) {
+ return -1;
+ }
+ return 0;
+ }
+ //deviceInfos.sort(comp); // I like this idea, but it messes with the defaults. I just don't know what it will do.
+ var deviceInfo;
+
+ // This is to hide NDI from default device. NDI Tools fucks up.
+ var tmp = [];
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if (!((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek")))) {
+ tmp.push(deviceInfo);
+ }
+ }
+
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek"))) {
+ tmp.push(deviceInfo);
+ log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
+ }
+ }
+ deviceInfos = tmp;
+
+ if (typeof session.audioDevice == "object") { // this sorts according to users's manual selection
+ var tmp = [];
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'audioinput') && session.audioDevice.filter(ele => deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(ele)).length) {
+ tmp.push(deviceInfo);
+ log("A DEVICE FOUND = " + deviceInfo.label);
+ } else if ((deviceInfo.kind === 'audioinput') && session.audioDevice.includes(deviceInfo.deviceId)) {
+ tmp.push(deviceInfo);
+ log("EXACT A DEVICE FOUND: "+session.audioDevice);
+ }
+ }
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if (!((deviceInfo.kind === 'audioinput') && session.audioDevice.filter(ele => deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(ele)).length)) { // opposite of previous loop
+ if (!(((deviceInfo.kind === 'audioinput') && session.audioDevice.includes(deviceInfo.deviceId)))){ // ditto
+ tmp.push(deviceInfo);
+ }
+ }
+ }
+ deviceInfos = tmp;
+ } else if (session.store && session.store.SelectedAudioInputDevices){
+ var matched = [];
+ var notmatch = [];
+ for (let i = 0; i < deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if (session.store.SelectedAudioInputDevices.includes(deviceInfo.deviceId)){
+ matched.push(deviceInfo);
+ log("EXACT V DEVICE FOUND -- from saved session");
+ } else {
+ notmatch.push(deviceInfo);
+ }
+ }
+
+ deviceInfos = matched.concat(notmatch);
+ delete session.store.SelectedAudioInputDevices;
+ }
+
+
+ if (session.sink || SelectedAudioOutputDevices) { // this sorts according to users's manual selection
+ var matched = [];
+ var notmatch = [];
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'audiooutput') && (deviceInfo.deviceId === session.sink)){
+ matched.push(deviceInfo);
+ } else if (!session.sink && (deviceInfo.kind === 'audiooutput') && (deviceInfo.deviceId === SelectedAudioOutputDevices)){
+ matched.push(deviceInfo);
+ } else {
+ notmatch.push(deviceInfo);
+ }
+ }
+ deviceInfos = matched.concat(notmatch);
+ }
+
+
+
+ if ((session.videoDevice) && (session.videoDevice !== 1)){ // this sorts according to users's manual selection
+ var tmp = [];
+ var tmp2 = [];
+ var tmp3 = [];
+
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().startsWith(session.videoDevice))) {
+ tmp.push(deviceInfo);
+ log("Starts With V DEVICE FOUND");
+ } else if (deviceInfo.deviceId === session.videoDevice){
+ tmp.push(deviceInfo);
+ log("EXACT V DEVICE FOUND");
+ } else if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.videoDevice))) {
+ tmp2.push(deviceInfo);
+ log("Includes With V DEVICE FOUND");
+ } else {
+ tmp3.push(deviceInfo);
+ }
+ }
+
+ if (tmp2.length){
+ tmp = tmp.concat(tmp2);
+ }
+ if (tmp3.length){
+ tmp = tmp.concat(tmp3);
+ }
+
+ deviceInfos = tmp;
+ log("VDECICE:" + session.videoDevice);
+ log(deviceInfos);
+ } else if ((session.videoDevice===false) && session.facingMode){
+ var tmp = [];
+ if (session.facingMode=="environment"){
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes("back"))) {
+ tmp.push(deviceInfo);
+ log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
+ } else if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes("rear"))) {
+ tmp.push(deviceInfo);
+ log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
+ }
+ }
+ } else if (session.facingMode=="user"){
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes("front"))) {
+ tmp.push(deviceInfo);
+ log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
+ }
+ }
+ }
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if (!((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.videoDevice)))) {
+ if (deviceInfo.deviceId !== session.videoDevice){
+ tmp.push(deviceInfo);
+ }
+ }
+ }
+ deviceInfos = tmp;
+ log("VDECICE:" + session.videoDevice);
+ log(deviceInfos);
+ } else if (session.store && session.store.SelectedVideoInputDevices){
+ var matched = [];
+ var notmatch = [];
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ deviceInfo = deviceInfos[i];
+ if (session.store.SelectedVideoInputDevices.includes(deviceInfo.deviceId)){
+ matched.push(deviceInfo);
+ log("EXACT V DEVICE FOUND -- from saved session");
+ } else {
+ notmatch.push(deviceInfo);
+ }
+ }
+ deviceInfos = matched.concat(notmatch);
+ delete session.store.SelectedVideoInputDevices;
+ }
+ var adMatch = session.audioDevice;
+ var counter = 1;
+ for (let i = 0; i !== deviceInfos.length; ++i) {
+ const deviceInfo = deviceInfos[i];
+ if (deviceInfo == null) {
+ continue;
+ }
+
+ if (deviceInfo.kind === 'audioinput') {
+ option = document.createElement('input');
+ option.type = "checkbox";
+ counter++;
+ listele = document.createElement('li');
+ listele.style.display = "none";
+
+ if (typeof adMatch == "object"){
+ for (var j = 0;j
+ ${sources.map(({id, name, thumbnail, display_id, appIcon}) => `
+
+
+ ${name}
+
Cancel
+ ${sources.map(({id, name, thumbnail, display_id, appIcon}) => `
+
+
+ ${name}
+
Include Desktop Audio
Audio capture not
supported on macOS
Capture ONLY
Desktop Audio
CancelStatistics
";
+ var menuCloseBtn = document.createElement("button");
+ menuCloseBtn.className="close";
+ menuCloseBtn.innerHTML="×";
+ menu.appendChild(menuCloseBtn);
+
+ var innerMenu = document.createElement("div");
+ menu.appendChild(innerMenu);
+
+ menuCloseBtn.addEventListener('click', function(eve) {
+ clearInterval(menu.interval);
+ eve.currentTarget.parentNode.remove();
+ eve.preventDefault();
+ eve.stopPropagation();
+ });
+ return [menu, innerMenu];
+}
+
+
+// WEBCAM
+session.publishStream = function(v){ // stream is used to generated an SDP
+ log("STREAM SETUP");
+
+ if (session.transcript){
+ setTimeout(function(){setupClosedCaptions();},1000);
+ }
+
+ if (!session.streamSrc){
+ checkBasicStreamsExist();
+ }
+
+ session.streamSrc.oninactive = function streamoninactive() {
+ warnlog('Stream inactive');
+ if (session.videoElement.recording){
+ session.videoElement.recorder.stop();
+ }
+ };
+
+ if (session.streamSrc.getVideoTracks().length==0){
+ warnlog("NO VIDEO TRACK INCLUDED");
+ }
+
+ if (session.streamSrc.getAudioTracks().length==0){
+ warnlog("NO AUDIO TRACK INCLUDED");
+ }
+
+
+ var container = document.createElement("div");
+ v.container = container;
+ container.id = "container";
+
+
+ if (session.cleanOutput){
+ container.style.height = "100%";
+ v.style.maxWidth = "100%";
+ v.style.boxShadow = "none";
+ }
+
+ if (session.cover){
+ container.style.setProperty('height', '100%', 'important');
+ }
+
+ //container.className = "vidcon";
+ getById("gridlayout").appendChild(container);
+
+ v.className = "tile"; //"tile task"; TODO: get working (will add task later on instead)
+
+
+ v.muted = true;
+ v.autoplay = true;
+ if (session.mobile){
+ v.controls = true;
+ } else {
+ v.controls = session.showControls || false;
+ }
+ v.setAttribute("playsinline","");
+ v.id = "videosource"; // could be set to UUID in the future
+ v.oncanplay = null;
+
+ session.videoElement = v;
+
+ container.appendChild(v);
+
+ toggleMute(true);
+
+ if (session.nopreview){
+ v.style.display="none";
+ container.style.display="none";
+ }
+
+
+ if (((session.roomid===false || session.roomid==="") && (session.quality===false)) || session.forceMediaSettings){
+ try {
+ if ((session.quality_wb!==false) && (session.quality===false)){
+ getById("webcamquality3").elements.namedItem("resolution").value = session.quality_wb;
+ } else if (session.quality!==false){
+ getById("webcamquality3").elements.namedItem("resolution").value = session.quality;
+ }
+ getById("gear_webcam3").style.display = "inline-block";
+ getById("webcamquality3").onchange = function(event) {
+ if (parseInt(getById("webcamquality3").elements.namedItem("resolution").value) == 2) {
+ if (session.maxframeRate===false){
+ session.maxframeRate = 30;
+ session.maxframeRate_q2 = true;
+ }
+ } else if (session.maxframeRate_q2){
+ session.maxframeRate = false;
+ session.maxframeRate_q2 = false;
+ }
+ activatedPreview = false;
+ session.quality_wb = parseInt(getById("webcamquality3").elements.namedItem("resolution").value);
+ grabVideo(session.quality_wb, "videosource", "select#videoSource3");
+ };
+ } catch (e) {errorlog(e);}
+ }
+
+
+ var bigPlayButton = document.getElementById("bigPlayButton");
+ if (bigPlayButton){
+ bigPlayButton.parentNode.removeChild(bigPlayButton);
+ }
+
+ if (session.streamID){
+ session.videoElement.dataset.sid = session.streamID;
+ }
+
+ if (session.statsMenu){
+ var [menu, innerMenu] = statsMenuCreator();
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+ printMyStats(innerMenu);
+ }
+
+ if (session.director){ // the director doesn't load a webcam by default anyways.
+ // audio is not mucked with
+ } else if (session.scene!==false){ // it's a scene, and there are no previews in a scene.
+ //setTimeout(function(){updateMixer();},10);
+ } else if (session.roomid!==false){
+ if (session.roomid===""){
+ if (!session.view || (session.view==="")){
+ if (session.fullscreen){
+ session.windowed = false;
+ } else {
+ v.className = "myVideo"; //"myVideo task"; TODO: get working
+ session.windowed = true;
+ container.classList.add("vidcon");
+ }
+ getById("mutespeakerbutton").classList.add("hidden");
+
+ applyMirror(session.mirrorExclude);
+
+ container.style.width="100%";
+ //container.style.height="100%";
+
+ container.style.alignItems = "center";
+ container.backgroundColor = "#666";
+
+ setTimeout(function (){dragElement(v);},1000);
+ play();
+ } else {
+ session.windowed = false;
+ applyMirror(session.mirrorExclude);
+ play();
+ //setTimeout(function(){updateMixer();},10);
+ }
+ } else {
+ //session.cbr=0; // we're just going to override it
+ if (session.stereo==5){ // not a scene or director, so we will assume its a guest. changing to stereo=3
+ session.stereo=3;
+ }
+ session.windowed = false;
+ applyMirror(session.mirrorExclude);
+
+ if (session.include.length){
+ play();
+ }
+
+ //setTimeout(function(){updateMixer();},10);
+ }
+ } else {
+
+ if (session.fullscreen){
+ session.windowed = false;
+ } else {
+ v.className = "myVideo"; //"myVideo task"; TODO: get working
+ container.classList.add("vidcon");
+ session.windowed = true;
+ }
+ getById("mutespeakerbutton").classList.add("hidden");
+
+ applyMirror(session.mirrorExclude);
+
+ container.style.width="100%";
+ //container.style.height="100%";
+ //container.style.display = "flex";
+
+ container.style.alignItems = "center";
+ container.backgroundColor = "#666";
+
+ setTimeout(function (){dragElement(v);},1000);
+
+ }
+
+ v.onpause = (event) => { // prevent things from pausing; human or other
+ if (!((event.ctrlKey) || (event.metaKey) )){
+ log("Video paused; auto playing");
+ event.currentTarget.play().then(_ => {
+ log("playing 10");
+ }).catch(warnlog);
+ }
+ };
+
+ v.addEventListener('click', function(e) {
+ log("click");
+ try {
+ if ((e.ctrlKey)||(e.metaKey)){
+ e.preventDefault();
+
+ var [menu, innerMenu] = statsMenuCreator();
+
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+
+ printMyStats(innerMenu);
+ e.stopPropagation();
+ return false;
+ }
+ } catch(e){errorlog(e);}
+ });
+
+ v.touchTimeOut = null;
+ v.touchLastTap = 0;
+ v.touchCount = 0;
+
+ v.addEventListener('touchend', function(event) {
+ if (session.disableMouseEvents){return;}
+ log("touched");
+
+ //document.ontouchup = null;
+ //document.onmouseup = null;
+ document.onmousemove = null;
+ document.ontouchmove = null;
+
+ var currentTime = new Date().getTime();
+ var tapLength = currentTime - v.touchLastTap;
+ clearTimeout(v.touchTimeOut);
+ if (tapLength < 500 && tapLength > 0) {
+ ///
+ log("double touched");
+ v.touchCount+=1;
+ event.preventDefault();
+ if (v.touchCount<5){
+ v.touchLastTap = currentTime;
+ return false;
+ }
+ v.touchLastTap = 0;
+ v.touchCount=0;
+
+ var [menu, innerMenu] = statsMenuCreator();
+
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+
+ printMyStats(innerMenu);
+ event.stopPropagation();
+ return false;
+ //////
+ } else {
+ v.touchCount=1;
+ v.touchLastTap = currentTime;
+
+ v.touchTimeOut = setTimeout(function(vv) {
+ clearTimeout(vv.touchTimeOut);
+ vv.touchLastTap = 0;
+ vv.touchCount=0;
+ }, 5000, v);
+
+ }
+
+ });
+
+ updateReshareLink();
+ pokeIframeAPI('started-camera'); // depreciated
+ pokeIframeAPI('camera-share', true);
+
+ if (session.videoMutedFlag){
+ session.videoMuted = true;
+ toggleVideoMute(true);
+ }
+
+ if (!gotDevices2AlreadyRan){
+ enumerateDevices().then(gotDevices2); // this is needed for iOS; was previous set to timeout at 100ms, but would be useful everywhere I think
+ }
+
+ v.dataset.menu = "context-menu-video";
+ if (!session.cleanOutput){
+ v.classList.add("task"); // this adds the right-click menu
+ }
+
+ session.postPublish();
+
+ if (session.autorecord || session.autorecordlocal){
+ log("AUTO RECORD START");
+ setTimeout(function(v){
+ if (session.director){
+ recordVideo(document.querySelector("[data-action-type='recorder-local'][data-sid='"+session.streamID+"']"), null, session.recordLocal)
+ } else if (v.stopWriter || v.recording){
+
+ } else if (v.startWriter){
+ v.startWriter();
+ } else {
+ recordLocalVideo(null, session.recordLocal, v)
+ }
+ },2000, v);
+ }
+
+ setTimeout(function(){updateMixer();},10);
+
+}; // publishStream
+
+session.postPublish = async function(){
+ log("Post publish");
+ if (session.welcomeMessage){
+ getChatMessage(session.welcomeMessage, false, true, true);
+ }
+
+ if (session.welcomeImage){
+ var welcomeoverlay = document.createElement("img");
+ welcomeoverlay.src = session.welcomeImage;
+ welcomeoverlay.className = "fadein";
+ welcomeoverlay.id = "welcomeImage";
+ document.body.appendChild(welcomeoverlay);
+ await sleep(2000);
+ setTimeout(function(welcomeoverlay){
+ welcomeoverlay.style = "animation: fadeout 1s;"
+ setTimeout(function(welcomeoverlay){
+ welcomeoverlay.remove();
+ },990,welcomeoverlay);
+ }, 1000, welcomeoverlay);
+ }
+
+ clearInterval(session.updateLocalStatsInterval);
+ session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
+
+ session.seeding=true;
+ session.seedStream();
+}
+
+
+async function publishScreen2(constraints, audioList=[], audio=true, overrideFramerate=false){ // webcam stream is used to generated an SDP
+ log("SCREEN SHARE SETUP");
+
+ if (!navigator.mediaDevices.getDisplayMedia){
+ setTimeout(function(){
+ if (iOS || iPad){
+ warnUser("Sorry, but your iOS browser does not support screen-sharing.\n\nPlease see this guide for an alternative method to do so.", false, false);
+ } else if (session.mobile){
+ warnUser("Sorry, your browser does not support screen-sharing.\n\nThe Android native app should support it though.", false, false);
+ } else {
+ warnUser("Sorry, your browser does not support screen-sharing.\n\nPlease use the desktop versions of Firefox or Chrome instead.");
+ }
+ },1);
+ return false;
+ }
+ if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
+ if (!ElectronDesktopCapture){
+ if (!(session.cleanOutput && session.cleanish==false)){
+ warnUser("Enable Elevated Privileges to allow screen-sharing. (right click this window to see that option)");
+ }
+ return false;
+ }
+ }
+
+ var streams = [];
+ for (var i=1; i
"+fileinfo.name+"
Do you trust them?
"+fileinfo.name+"
"+fileinfo.name+"
"+fileinfo.name+"
"+fileinfo.name+"
"+fileinfo.name+"Camera/mic permissions denied
\nPlease ensure you have allowed the mic/camera permissions in your browser, such as like:\n\n
\n\nFor further help on how to resolve this issue, please refer to:\n\nhttps://docs.vdo.ninja/common-errors-and-known-issues/enable-camera-microphone-permissions.", false, false);
+ } else {
+ warnUser("Permission access to the camera or microphone was denied.\n\nPlease ensure you have allowed the mic/camera permissions in your browser.\n\nFor guides on how to resolve this issue, please refer to:\n\nhttps://docs.vdo.ninja/common-errors-and-known-issues/enable-camera-microphone-permissions.", false, false);
+ }
+ }, 1);
+ }
+ return;
+ } else if (err.name == "TypeError" || err.name == "TypeError") {
+ //empty constraints object
+ } else {
+ //permission denied in browser
+ if (!(session.cleanOutput)) {
+ setTimeout(function() {
+ warnUser(err);
+ }, 1);
+ }
+ }
+ errorlog("trying to list webcam again");
+
+ if (callback){
+ callback();
+ }
+
+ });
+ } catch (e) {
+ errorlog(e);
+ if (!(session.cleanOutput)) {
+ if (window.isSecureContext) {
+ warnUser("An error has occured when trying to access the webcam or microphone. The reason is not known.");
+ } else if ((iOS) || (iPad)) {
+ warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
+ } else {
+ warnUser("Error acessing camera or microphone.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
+ }
+ }
+ }
+}
+
+
+function copyFunction(copyText, evt = false) {
+ if (evt){
+ if ("buttons" in evt) {
+ if (evt.buttons !== 0){return;}
+ } else if ("which" in evt){
+ if (evt.which !== 0){return;}
+ }
+ popupMessage(evt);
+ evt.preventDefault();
+ evt.stopPropagation();
+ }
+
+ try {
+ copyText.select();
+ copyText.setSelectionRange(0, 99999);
+ document.execCommand("copy");
+ } catch (e) {
+ var dummy = document.createElement('input');
+ document.body.appendChild(dummy);
+ dummy.value = copyText;
+ dummy.select();
+ document.execCommand('copy');
+ document.body.removeChild(dummy);
+ }
+ return false;
+}
+
+function generateQRPage() {
+ var pass = sanitizePassword(getById("invite_password").value);
+ if (pass.length) {
+ return generateHash(pass + session.salt, 4).then(function(hash) {
+ generateQRPageCallback(hash);
+ }).catch(errorlog);
+ } else {
+ generateQRPageCallback("");
+ }
+}
+
+async function updateLinkWelcome(arg, input) {
+ if (input.checked){
+ var response = await promptAlt("Enter the message you'd like the guests to see");
+ response = encodeURIComponent(response);
+ var param = input.dataset.param.split("=")[0];
+ input.dataset.param = param + "=" + response;
+ }
+ updateLink(arg, input);
+}
+
+
+function updateLinkWebP(arg, input) {
+ if (input.checked){
+ if (!((getById("director_block_" + arg).dataset.raw.includes("&broadcast")) || (getById("director_block_" + arg).dataset.raw.includes("?broadcast")))){
+ getById("broadcastSlider").checked=true;
+ updateLink(arg, getById("broadcastSlider"));
+ }
+ }
+ updateLink(arg, input);
+}
+
+var soloLinkAppended = "";
+function updateLink(arg, input, solo=false) {
+ log("updateLink");
+ log(input.dataset.param);
+ if (input.checked) {
+
+ getById("director_block_" + arg).dataset.raw += input.dataset.param;
+
+ if (solo){
+ soloLinkAppended += input.dataset.param;
+ }
+
+ var string = getById("director_block_" + arg).dataset.raw;
+
+ if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
+ string = obfuscateURL(string);
+ }
+
+ getById("director_block_" + arg).href = string;
+ getById("director_block_" + arg).innerText = string;
+ } else {
+ var string = getById("director_block_" + arg).dataset.raw + "&";
+ string = string.replace(input.dataset.param + "&", "&");
+ string = string.substring(0, string.length - 1);
+ getById("director_block_" + arg).dataset.raw = string;
+
+ if (solo){
+ soloLinkAppended += "&";
+ soloLinkAppended = soloLinkAppended.replace(input.dataset.param + "&", "&");
+ soloLinkAppended = soloLinkAppended.substring(0, soloLinkAppended.length - 1);
+ }
+
+ if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
+ string = obfuscateURL(string);
+ }
+
+ // document.querySelector("soloLink")
+ // soloLink
+
+ getById("director_block_" + arg).href = string;
+ getById("director_block_" + arg).innerText = string;
+ }
+ if (solo){
+ document.querySelectorAll("a.soloLink").forEach(ele=>{
+ try {
+ var href = ele.getAttribute("value") + soloLinkAppended;
+ ele.href = href;
+ ele.innerHTML = href;
+ } catch(e){
+ errorlog(e);
+ }
+ });
+ }
+
+ saveDirectorSettings();
+}
+
+function changeURL(changeURL){
+ window.focus();
+ if (session.consent){
+ hangup();
+ window.location.href = changeURL;
+ } else {
+ confirmAlt(miscTranslations["director-redirect-1"]+changeURL+miscTranslations["director-redirect-2"]).then(res=>{
+ if (res){
+ hangup();
+ window.location.href = changeURL;
+ };
+ });
+ }
+}
+
+function updateLinkInverse(arg, input) {
+ log("updateLinkInverse");
+ log(input.dataset.param);
+ if (!(input.checked)) {
+
+ getById("director_block_" + arg).dataset.raw += input.dataset.param;
+
+ var string = getById("director_block_" + arg).dataset.raw;
+
+ if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
+ string = obfuscateURL(string);
+ }
+
+
+ getById("director_block_" + arg).href = string;
+ getById("director_block_" + arg).innerText = string;
+ } else {
+ var string = getById("director_block_" + arg).dataset.raw + "&";
+ string = string.replace(input.dataset.param + "&", "&");
+ string = string.substring(0, string.length - 1);
+ getById("director_block_" + arg).dataset.raw = string;
+
+ if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
+ string = obfuscateURL(string);
+ }
+
+ getById("director_block_" + arg).href = string;
+ getById("director_block_" + arg).innerText = string;
+ }
+}
+
+function updateLinkScene(arg, input) {
+ log("updateLinkScene");
+ var string = getById("director_block_" + arg).dataset.raw;
+
+ if (input.checked) {
+ string = changeParam(string, "scene", "0");
+ } else {
+ string = changeParam(string, "scene", "1");
+ }
+ getById("director_block_" + arg).dataset.raw = string;
+
+ if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
+ string = obfuscateURL(string);
+ }
+
+ getById("director_block_" + arg).href = string;
+ getById("director_block_" + arg).innerText = string;
+}
+
+function fullscreenPageToggle(state=null){
+ try {
+ if (!document.fullscreenElement) { // not currently full screen
+ if (state!==false){ // if state is false, we are already not full screen
+ if (document.documentElement.requestFullscreen){
+ document.documentElement.requestFullscreen();
+ } else if (document.documentElement.webkitRequestFullscreen){
+ document.documentElement.webkitRequestFullscreen();
+ }
+ }
+ } else if (document.exitFullscreen) {
+ if (!state){ // if toggle mode or state=false
+ document.exitFullscreen();
+ }
+ } else if (document.webkitExitFullscreen) {
+ if (!state){ // if toggle mode or state=false
+ document.webkitExitFullscreen();
+ }
+ }
+ //updateMixer(); // we will do this on the event for this instead
+ } catch(e){errorlog(e);}
+}
+
+function resetGen() {
+ getById("gencontent").style.display = "block";
+ getById("gencontent2").style.display = "none";
+ getById("gencontent2").className = ""; //container-inner
+ getById("gencontent").className = "container-inner"; //
+ getById("gencontent2").innerHTML = "";
+ getById("videoname4").focus();
+}
+
+function generateQRPageCallback(hash) {
+ try {
+ var title = getById("videoname4").value;
+ if (title.length) {
+ title = title.replace(/[\W]+/g, "_").replace(/_+/g, '_'); // but not what others might get. TODO: allow for non-alphanumeric characters; santitize, then URL encode instead,
+ title = "&label=" + title;
+ }
+ var sid = session.generateStreamID();
+
+ var viewstr = "";
+ var sendstr = "";
+
+ if (getById("invite_bitrate").checked) {
+ viewstr += "&bitrate=20000";
+ }
+ if (getById("invite_vp9").checked) {
+ viewstr += "&codec=vp9";
+ }
+ if (getById("invite_stereo").checked) {
+ viewstr += "&stereo";
+ sendstr += "&stereo";
+ }
+ if (getById("invite_automic").checked) {
+ sendstr += "&audiodevice=1";
+ }
+ if (getById("invite_automic").checked) {
+ sendstr += "&audiodevice=1";
+ }
+ if (getById("invite_effects").checked) {
+ sendstr += "&effects";
+ }
+
+ if (getById("invite_remotecontrol").checked) { //
+ var remote_gen_id = session.generateStreamID();
+ sendstr += "&remote=" + remote_gen_id; // security
+ viewstr += "&remote=" + remote_gen_id;
+ }
+
+ if (getById("invite_joinroom").value.trim().length) {
+ sendstr += "&ssid&room=" + getById("invite_joinroom").value.trim();
+ viewstr += "&solo&room=" + getById("invite_joinroom").value.trim();
+ }
+
+ if (getById("invite_password").value.trim().length) {
+ sendstr += "&hash=" + hash;
+ viewstr += "&password=" + sanitizePassword(getById("invite_password").value.trim());
+ }
+
+ if (getById("invite_group_chat_type").value) { // 0 is default
+ if (getById("invite_group_chat_type").value == 1) { // no video
+ sendstr += "&novideo";
+ } else if (getById("invite_group_chat_type").value == 2) { // no view or audio
+ sendstr += "&view";
+ }
+ }
+
+ if (getById("invite_quality").value) {
+ if (getById("invite_quality").value == 0) {
+ sendstr += "&quality=0";
+ } else if (getById("invite_quality").value == 1) {
+ sendstr += "&quality=1";
+ } else if (getById("invite_quality").value == 2) {
+ sendstr += "&quality=2";
+ }
+ }
+
+ var wss = "";
+
+ if (session.customWSS && session.wssSetViaUrl){
+ if (session.customWSS!==true){
+ wss = "&pie="+session.customWSS;
+ } else {
+ wss = "&wss="+session.wss;
+ }
+ }
+
+ var hoststr = "";
+ if (getById("invite_hostlink").checked) {
+ hoststr = 'https://' + location.host + location.pathname + '?push=' + sid + "_hostlink" + "&view="+ sid + sendstr + "&bitrate=500" + title + wss;
+ sendstr = 'https://' + location.host + location.pathname + '?push=' + sid + "&view="+sid + "_hostlink" + sendstr + "&bitrate=1200" + title + wss;
+ } else {
+ sendstr = 'https://' + location.host + location.pathname + '?push=' + sid + sendstr + title + wss;
+ }
+
+ if (getById("invite_obfuscate").checked) {
+ sendstr = obfuscateURL(sendstr);
+ }
+
+ viewstr = 'https://' + location.host + location.pathname + '?view=' + sid + viewstr + title + wss;
+ getById("gencontent").style.display = "none";
+ getById("gencontent").className = ""; //
+ getById("gencontent2").style.display = "block";
+ getById("gencontent2").className = "container-inner";
+
+ getById("gencontent2").innerHTML = '
\
+
and don\'t forget theOBS Browser Source Link:
' + viewstr + ' \
+ ';
+
+ if (hoststr){
+ getById("gencontent2").innerHTML += 'Host Chat Link:
' + hoststr + ' ';
+ }
+
+ getById("gencontent2").innerHTML += '
\
+ \
+
\
+
";
+ textOverlay.appendChild(spanOverlay);
+ textOverlay.style.display = "block";
+ var showtime = msg.length * 200 + 3000;
+ if (showtime > 8000) {
+ showtime = 8000;
+ }
+ setTimeout(function(ele) {
+ ele.parentNode.removeChild(ele);
+ }, showtime, spanOverlay);
+ }
+ }
+ }
+
+ if (isIFrame) {
+ parent.postMessage({
+ "gotChat": data
+ }, session.iframetarget);
+ }
+
+ if (session.chatbutton===false){return;} // messages can still appear as overlays ^
+
+ messageList.push(data);
+ messageList = messageList.slice(-100);
+
+ if (session.beepToNotify) {
+ playtone();
+ showNotification("new message", msg);
+ }
+ updateMessages();
+
+ if (session.chat == false) {
+ getById("chattoggle").className = "las la-comments my-float toggleSize puslate";
+ getById("chatbutton").className = "float";
+
+ if (getById("chatNotification").value) {
+ getById("chatNotification").value = getById("chatNotification").value + 1;
+ } else {
+ getById("chatNotification").value = 1;
+ }
+ getById("chatNotification").classList.add("notification");
+ }
+
+
+ if (session.broadcastChannel !== false) {
+ session.broadcastChannel.postMessage(data); /* send */
+ }
+
+}
+
+function updateClosedCaptions(msg, label, UUID) {
+ msg.counter = parseInt(msg.counter);
+ var temp = document.createElement('div');
+ temp.innerText = msg.transcript;
+ temp.innerText = temp.innerHTML;
+ var transcript = temp.textContent || temp.innerText || "";
+
+ if (transcript == "") {
+ return;
+ }
+
+ transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
+ //transcript = transcript.substr(-1, 5000); // keep it from being too long
+
+
+ if (label && (!(session.view && !session.view_set))) {
+ label = sanitizeLabel(label);
+ label = "" + label + ": ";
+ } else {
+ label = "";
+ }
+
+ var textOverlay = getById("overlayMsgs");
+ if (textOverlay) {
+ if (document.getElementById(UUID + "_" + msg.counter)) {
+ var spanOverlay = document.getElementById(UUID + "_" + msg.counter);
+ } else {
+ var spanOverlay = document.createElement("span");
+ spanOverlay.id = UUID + "_" + msg.counter;
+ textOverlay.appendChild(spanOverlay);
+ textOverlay.style.height = "unset";
+ textOverlay.style.textAlign = "left";
+ textOverlay.style.display = "block";
+ textOverlay.style.position = "fixed";
+ textOverlay.style.bottom = "0";
+
+ }
+ spanOverlay.innerHTML = label + transcript + "
";
+ spanOverlay.style.fontSize = (parseInt(session.labelsize || 100) / 100.0 * 4.5) + "vh";
+ spanOverlay.style.lineHeight = (parseInt(session.labelsize || 100) / 100 * 6) + "vh";
+ spanOverlay.style.margin = (parseInt(session.labelsize || 100) / 100.0 * 0.75) + "vh";
+
+ if (msg.isFinal) {
+ var showtime = 3000;
+ clearTimeout(spanOverlay.timeout);
+ spanOverlay.timeout = setTimeout(function(ele) {
+ ele.parentNode.removeChild(ele);
+ }, showtime, spanOverlay);
+ } else {
+ clearTimeout(spanOverlay.timeout);
+ spanOverlay.timeout = setTimeout(function(ele) {
+ ele.parentNode.removeChild(ele);
+ }, 30000, spanOverlay);
+ }
+
+ }
+}
+
+var chatUpdateTimeout = null;
+function updateMessages(){
+ if (session.chatbutton===false){return;}
+ document.getElementById("chatBody").innerHTML = "";
+ for (var i in messageList) {
+
+ var time = timeSince(messageList[i].time) || "";
+ time = " - "+time+"";
+ var msg = document.createElement("div");
+ var message = replaceURLs(messageList[i].msg);
+
+ if (messageList[i].type == "sent") {
+ msg.innerHTML = message + "" + time + "";
+ msg.classList.add("outMessage");
+ } else if ((messageList[i].type == "recv") || (messageList[i].type == "action")) {
+ var label = "";
+ if (messageList[i].label) {
+ label = messageList[i].label;
+ }
+ msg.innerHTML = label + message + "" + time + "";
+ msg.classList.add("inMessage");
+ } else if (messageList[i].type == "alert") {
+ msg.innerHTML = message + "" + time + "";
+ msg.classList.add("inMessage");
+ } else {
+ msg.innerHTML = message;
+ msg.classList.add("outMessage");
+ }
+
+ document.getElementById("chatBody").appendChild(msg);
+ }
+ showDownloadLinks();
+ for (var i in msgTransferList) {
+ var time = timeSince(msgTransferList[i].time) || "";
+ time = " - "+time+"";
+
+ var msg = document.createElement("div");
+ if ("idx" in msgTransferList[i]){
+ msg.id = "transfer_"+msgTransferList[i].idx;
+ }
+ if (msgTransferList[i].type == "sent") {
+ msg.innerHTML = msgTransferList[i].msg + "" + time + "";
+ msg.classList.add("outMessage");
+ } else if ((msgTransferList[i].type == "recv") || (msgTransferList[i].type == "action")) {
+ var label = "";
+ if (msgTransferList[i].label) {
+ label = msgTransferList[i].label;
+ }
+ msg.innerHTML = label + msgTransferList[i].msg + "" + time + "";
+ msg.classList.add("inMessage");
+ } else if (msgTransferList[i].type == "alert") {
+ msg.innerHTML = msgTransferList[i].msg + "" + time + "";
+ msg.classList.add("inMessage");
+ } else {
+ msg.innerHTML = msgTransferList[i].msg;
+ msg.classList.add("outMessage");
+ }
+ document.getElementById("chatBody").appendChild(msg);
+ }
+ if (chatUpdateTimeout) {
+ clearInterval(chatUpdateTimeout);
+ }
+ document.getElementById("chatBody").scrollTop = document.getElementById("chatBody").scrollHeight;
+ chatUpdateTimeout = setTimeout(function() {
+ updateMessages();
+ }, 60000);
+}
+
+function EnterButtonChat(event) {
+ // Number 13 is the "Enter" key on the keyboard
+ var key = event.which || event.keyCode;
+ if (key === 13) {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ sendChatMessage();
+ }
+}
+
+function showCustomizer(arg, ele) {
+ //getById("directorLinksButton").innerHTML=' LINKS (GUEST INVITES & SCENES)'
+ getById("showCustomizerButton1").style.backgroundColor = "";
+ getById("showCustomizerButton2").style.backgroundColor = "";
+ getById("showCustomizerButton3").style.backgroundColor = "";
+ getById("showCustomizerButton4").style.backgroundColor = "";
+ getById("showCustomizerButton1").style.boxShadow = "";
+ getById("showCustomizerButton2").style.boxShadow = "";
+ getById("showCustomizerButton3").style.boxShadow = "";
+ getById("showCustomizerButton4").style.boxShadow = "";
+
+
+ if (getById("customizeLinks" + arg).style.display != "none") {
+ getById("customizeLinks").style.display = "none";
+ getById("customizeLinks" + arg).style.display = "none";
+ } else {
+ //directorLinks").style.display="none";
+ getById("showCustomizerButton" + arg).style.backgroundColor = "#1e0000";
+ getById("showCustomizerButton" + arg).style.boxShadow = "inset 0px 0px 1px #b90000";
+ getById("customizeLinks1").style.display = "none";
+ getById("customizeLinks3").style.display = "none";
+ getById("customizeLinks").style.display = "block";
+ getById("customizeLinks" + arg).style.display = "block";
+ }
+}
+
+var PPTHotkey = getStorage("PPTHotkey") || false;
+if (PPTHotkey){
+ var key = "";
+ if (PPTHotkey.ctrl){
+ key += "Control";
+ }
+ if (PPTHotkey.meta){
+ if (key){
+ key += " + ";
+ }
+ key += "Meta";
+ }
+ if (PPTHotkey.alt){
+ if (key){
+ key += " + ";
+ }
+ key += "Alt";
+ }
+
+ if (PPTHotkey.key=="Control"){
+ //
+ } else if (PPTHotkey.key=="Alt"){
+ //
+ } else if (PPTHotkey.key=="Meta"){
+ //
+ } else if (PPTHotkey.key !== false){
+ if (key){
+ key += " + ";
+ }
+ if (PPTHotkey.key === " "){
+ key += "Space"
+ } else {
+ key += PPTHotkey.key;
+ }
+ } else if (key && (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1)){
+ getById("pptHotKey").title = "Note: Global hot-keys can't simply be Control, Alt, or Meta keys.";
+ }
+ getById("pptHotKey").value = key;
+
+ try {
+ if (window.electronApi && window.electronApi.updatePPT){
+ window.electronApi.updatePPT(PPTHotkey);
+ } else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
+ if (!ipcRenderer){
+ ipcRenderer = require('electron').ipcRenderer;
+ }
+ if (ipcRenderer){
+ ipcRenderer.send('PPTHotkey', PPTHotkey);
+ }
+ }
+ } catch(e){errorlog(e);}
+}
+
+function setHotKey(keyinput=true){
+ if (!keyinput){ // clears if false
+ getById("pptHotKey").value = "";
+ PPTHotkey = false;
+ removeStorage("PPTHotkey");
+
+ try {
+ if (window.electronApi && window.electronApi.updatePPT){
+ window.electronApi.updatePPT(PPTHotkey);
+ } else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
+ if (!ipcRenderer){
+ ipcRenderer = require('electron').ipcRenderer;
+ }
+ if (ipcRenderer){
+ ipcRenderer.send('PPTHotkey', PPTHotkey);
+ }
+ }
+ } catch(e){errorlog(e);}
+
+ return;
+ }
+
+ PPTHotkey = {
+ ctrl:false,
+ alt: false,
+ meta: false,
+ key: false
+ };
+
+ log(event);
+ var key = "";
+ if (event.ctrlKey){
+ key += "Control";
+ PPTHotkey.ctrl = true;
+ }
+ if (event.metaKey){
+ if (key){
+ key += " + ";
+ }
+ key += "Meta";
+ PPTHotkey.meta = true;
+ }
+ if (event.altKey){
+ if (key){
+ key += " + ";
+ }
+ key += "Alt";
+ PPTHotkey.alt = true;
+ }
+
+ if (event.key=="Control"){
+ //
+ } else if (event.key=="Alt"){
+ //
+ } else if (event.key=="Meta"){
+ //
+ } else if (event.key || (event.key === " " || (event.key===0))){
+ if (key){
+ key += " + ";
+ }
+ if (event.key === " "){
+ key += "Space"
+ } else {
+ key += event.key;
+ }
+ PPTHotkey.key = event.key;
+ }
+ setStorage("PPTHotkey", PPTHotkey, 99999);
+ event.target.value = key;
+
+ try {
+ if (window.electronApi && window.electronApi.updatePPT){
+ window.electronApi.updatePPT(PPTHotkey);
+ } else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
+ if (!ipcRenderer){
+ ipcRenderer = require('electron').ipcRenderer;
+ }
+ if (ipcRenderer){
+ ipcRenderer.send('PPTHotkey', PPTHotkey);
+ }
+ }
+ } catch(e){errorlog(e);}
+
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+}
+
+var recordingBitratePromise = false;
+var defaultRecordingBitrate = false;
+async function recordVideo(target, event = null, videoKbps = false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
+
+ var UUID = target.dataset.UUID;
+
+ if (!UUID){return;}
+
+ var video = session.rpcs[UUID].videoElement;
+
+ if (!video){return;}
+
+ if (video.stopWriter){
+ video.stopWriter();
+ updateLocalRecordButton(UUID, -1);
+ return;
+ } else if (video.startWriter){
+ await video.startWriter();
+ updateLocalRecordButton(UUID, 0);
+ return;
+ }
+
+
+ var audioKbps = false;
+
+ if (event === null) {
+ if (defaultRecordingBitrate === null) {
+ updateLocalRecordButton(UUID, -1);
+ return;
+ }
+ } else if ((event.ctrlKey) || (event.metaKey)) {
+ updateLocalRecordButton(UUID, -3);
+ Callbacks.push([recordVideo, target, null, false]);
+ log("Record Video queued");
+ defaultRecordingBitrate = false;
+ recordingBitratePromise = false;
+ return;
+ } else {
+ defaultRecordingBitrate = false;
+ recordingBitratePromise = false;
+ }
+
+ log("Record Video Clicked");
+ if ("recording" in video) {
+ log("ALREADY RECORDING!");
+ updateLocalRecordButton(UUID, -2);
+ video.recorder.stop();
+ session.requestRateLimit(35, UUID); // 100kbps
+ if (session.audiobitrate===false){
+ session.requestAudioRateLimit(-1,UUID);
+ }
+
+ var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.add("pressed");
+ }
+ var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.remove("pressed");
+ }
+ var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.remove("pressed");
+ }
+ return;
+ } else {
+ updateLocalRecordButton(UUID, 0);
+ //target.style.backgroundColor = "#FCC";
+ //target.innerHTML = " Download";
+ video.recording = true;
+ }
+
+ video.recorder = {};
+
+ if (videoKbps == false) {
+ if (defaultRecordingBitrate == false) {
+ videoKbps = 4000; // 4mbps recording bitrate
+
+ if (!recordingBitratePromise){
+ window.focus();
+ recordingBitratePromise = promptAlt(miscTranslations["press-ok-to-record"], false, false, videoKbps);
+ }
+ videoKbps = await recordingBitratePromise;
+ log("videoKbps: "+videoKbps+", UUID:"+UUID);
+ if (videoKbps === null) {
+ //target.style.backgroundColor = null;
+ //target.innerHTML = ' record local';
+ updateLocalRecordButton(UUID, -1);
+ target.style.backgroundColor = "";
+ delete(video.recorder);
+ delete(video.recording);
+ defaultRecordingBitrate = null;
+ return;
+ }
+ videoKbps = parseInt(videoKbps);
+ defaultRecordingBitrate = videoKbps;
+ } else {
+ videoKbps = defaultRecordingBitrate;
+ }
+ }
+
+ if (videoKbps <= 0) {
+ audioKbps = videoKbps * (-1);
+ videoKbps = false;
+ if (session.audiobitrate===false){
+ if ((audioKbps>0) && (audioKbps>=128)){
+ session.requestAudioRateLimit(128,UUID); // no point going higher
+ } else if (audioKbps==0){
+ session.requestAudioRateLimit(256,UUID); // PCM
+ } else {
+ session.requestAudioRateLimit(parseInt(audioKbps),UUID); // exact? sure. why not.
+ }
+ }
+ } else if (videoKbps < 50) { // this just makes sure you can't set 0 on the record bitrate.
+ videoKbps = 50;
+ session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
+ } else {
+ session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
+
+ if (videoKbps>4000){
+ if (session.audiobitrate===false){
+ if (session.pcm){
+ session.requestAudioRateLimit(256,UUID);
+ } else {
+ session.requestAudioRateLimit(128,UUID);
+ }
+ }
+ } else if (videoKbps>2500){
+ if (session.audiobitrate===false){
+ if (session.pcm){
+ session.requestAudioRateLimit(256,UUID);
+ } else {
+ session.requestAudioRateLimit(80,UUID);
+ }
+ }
+ }
+
+ }
+
+ var timestamp = Date.now();
+ var filename = "";
+ if (session.rpcs[UUID].label || session.rpcs[UUID].streamID) {
+ filename = session.rpcs[UUID].label || session.rpcs[UUID].streamID;
+ filename = filename.replace(/[\W]+/g, "_");
+ filename = filename.substring(0, 200);
+ }
+
+ filename += "_" + timestamp.toString();
+
+ var cancell = false;
+ if (typeof video.srcObject === "undefined" || !video.srcObject) {
+ return;
+ }
+
+ video.recorder.stop = function(restart = false, notify = false) {
+ if (!video.recording) {
+ errorlog("ALREADY STOPPED");
+ updateLocalRecordButton(UUID, -1);
+ return;
+ }
+
+ if (notify){
+ if (!session.cleanOutput){
+ warnUser("A local recording has stopped unexpectedly.");
+ }
+ if (session.beepToNotify){
+ playtone();
+
+ }
+ target.classList.remove("shake");
+ setTimeout(function(target){target.classList.add("shake");},10, target);
+ }
+
+ video.recording = false;
+ updateLocalRecordButton(UUID, -2);
+ try {
+ if (video.recorder.mediaRecorder.state !== "inactive") {
+ video.recorder.mediaRecorder.stop();
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+
+ session.requestRateLimit(35, UUID); // 100kbps
+ if (session.audiobitrate===false){
+ session.requestAudioRateLimit(-1,UUID);
+ }
+ var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.add("pressed");
+ }
+ var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.remove("pressed");
+ }
+ var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ elements[0].classList.remove("pressed");
+ }
+
+ cancell = true;
+ // log('Recorded Blobs: ', recordedBlobs);
+ // download();
+ setTimeout((writer1,UUID1,video1) => {
+ try{
+ writer1.close();
+ } catch(e){}
+ updateLocalRecordButton(UUID1, -1);
+ delete(video1.recorder);
+ delete(video1.recording);
+ }, 1200, writer, UUID, video);
+ };
+
+ const {readable, writable} = new TransformStream({
+ transform: (chunk, ctrl) => chunk.arrayBuffer().then(b => ctrl.enqueue(new Uint8Array(b)))
+ });
+ var writer = writable.getWriter();
+ readable.pipeTo(streamSaver.createWriteStream(filename.toString() + '.webm', video.recorder.stop));
+ video.recorder.writer = writer;
+ pokeIframeAPI("recording-started");
+
+ let options = {};
+
+ if (videoKbps) {
+ var tryCodec = false;
+ if (session.recordingVideoCodec){
+ tryCodec = session.recordingVideoCodec;
+ }
+ if (tryCodec && MediaRecorder.isTypeSupported('video/webm;codecs='+tryCodec)) {
+ if (!session.cleanOutput){
+ warnUser("The browser 'says' it supports "+tryCodec);
+ }
+ options.mimeType = 'video/webm;codecs='+tryCodec;
+ if (session.pcm){
+ if (MediaRecorder.isTypeSupported('video/webm;codecs="'+tryCodec+', pcm"')){
+ options.mimeType = 'video/webm;codecs="'+tryCodec+', pcm"';
+ } else {
+ options.mimeType = "video/webm;codecs=pcm";
+ }
+ }
+ } else {
+ if (session.pcm){
+ if (MediaRecorder.isTypeSupported("video/webm;codecs=pcm")) {
+ options.mimeType = "video/webm;codecs=pcm";
+ } else {
+ options.mimeType = "video/webm";
+ }
+ } else {
+ options.mimeType = "video/webm";
+ }
+ }
+ if (videoKbps < 1000) {
+ options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
+ } else {
+ options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
+ }
+ video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
+ } else {
+ options.mimeType = 'audio/webm';
+ if (audioKbps == 0) {
+ if (MediaRecorder.isTypeSupported("audio/webm;codecs=pcm")) {
+ options.mimeType = "audio/webm;codecs=pcm";
+ }
+ } else {
+ options.bitsPerSecond = parseInt(audioKbps * 1024);
+ }
+ var stream = createMediaStream();
+ video.srcObject.getAudioTracks().forEach((track) => {
+ stream.addTrack(track, video.srcObject);
+ });
+ video.recorder.mediaRecorder = new MediaRecorder(stream, options);
+ }
+ log(options);
+
+ function download() {
+ const blob = new Blob(recordedBlobs, {
+ type: "video/webm"
+ });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ a.href = url;
+ a.download = filename + ".webm";
+ document.body.appendChild(a);
+ a.click();
+ setTimeout(function(uu,aa){
+ document.body.removeChild(aa);
+ window.URL.revokeObjectURL(uu);
+ }, 100, url,a);
+ }
+
+ function handleDataAvailable(event) {
+ if (event.data && event.data.size > 0) {
+ //recordedBlobs.push(event.data);
+ try{
+ writer.write(event.data); ////////////
+ if (video.recording) {
+ updateLocalRecordButton(UUID, (parseInt((Date.now() - timestamp) / 1000) || 0));
+ }
+ } catch(e){warnlog("Stream recording error or ended");}
+ }
+ }
+
+ video.recorder.mediaRecorder.ondataavailable = handleDataAvailable;
+
+ video.recorder.mediaRecorder.onerror = function(event) {
+ errorlog(event);
+ video.recorder.stop();
+ session.requestRateLimit(35, UUID);
+ if (!(session.cleanOutput)) {
+ setTimeout(function() {
+ warnUser("an error occured with the media recorder; stopping recording");
+ }, 1);
+ }
+ };
+
+ video.srcObject.ended = function(event) {
+ video.recorder.stop();
+ session.requestRateLimit(35, UUID);
+ if (!(session.cleanOutput)) {
+ setTimeout(function() {
+ warnUser("stream ended! stopping recording");
+ }, 1);
+ }
+ };
+
+
+ setTimeout(function(v) {
+ v.recorder.mediaRecorder.start(1000);
+ }, 500, video); // 100ms chunks
+
+ return;
+}
+
+function updateRemoteRecordButton(UUID, recorder) {
+ var elements = document.querySelectorAll('[data-action-type="recorder-remote"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ var time = parseInt(recorder) || 0;
+ if (time == -4) {
+ if (!session.cleanOutput){
+ warnUser("A remote recording has stopped unexpectedly.\n\nDid a user cancel the file downlaod?");
+ }
+ if (session.beepToNotify){
+ playtone();
+ }
+ elements[0].classList.add("pressed");
+ elements[0].classList.remove("shake");
+ elements[0].innerHTML = ' stopping...';
+ setTimeout(function(ele){ele.classList.add("shake");},10,elements[0]);
+ } else if (time == -3) {
+ elements[0].classList.remove("pressed");
+ elements[0].disabled = true;
+ elements[0].innerHTML = ' Not Supported';
+ if (!(session.cleanOutput)) {
+ setTimeout(function() {
+ warnUser('The remote browser does not support recording.\n\nPerhaps try local recording instead.');
+ }, 0);
+ }
+ } else if (time == -5) {
+ if (!(session.cleanOutput)) {
+ setTimeout(function() {
+ warnUser('The remote browser has only experimental support for media recording.\n\nAlso, when this download stops, the remote user may be asked to download the file for it to save.');
+ }, 0);
+ }
+ } else if (time == -2) {
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' stopping...';
+ } else if (time == -1) {
+ elements[0].classList.remove("pressed");
+ elements[0].innerHTML = ' Record Remote';
+ } else {
+ var minutes = Math.floor(time / 60);
+ var seconds = time - minutes * 60;
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' ' + (minutes) + "m : " + zpadTime(seconds) + "s";
+ }
+ }
+}
+
+function updateLocalRecordButton(UUID, recorder) {
+ var elements = document.querySelectorAll('[data-action-type="recorder-local"][data--u-u-i-d="' + UUID + '"]');
+ if (elements[0]) {
+ var time = parseInt(recorder) || 0;
+
+ //target.innerHTML = ' ARMED';
+ //
+ if (time == -3) {
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' ARMED';
+ elements[0].style.backgroundColor = "#BF3F3F";
+ } else if (time == -2) {
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' stopping...';
+ elements[0].style.backgroundColor = "";
+ } else if (time == -1) {
+ elements[0].classList.remove("pressed");
+ elements[0].innerHTML = ' Record Local';
+ elements[0].style.backgroundColor = "";
+ } else {
+ var minutes = Math.floor(time / 60);
+ var seconds = time - minutes * 60;
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' ' + (minutes) + "m : " + zpadTime(seconds) + "s";
+ elements[0].style.backgroundColor = "";
+ }
+ }
+}
+
+function recordLocalVideoToggle() {
+ if (!session.videoElement){return;}
+ log("recordLocalVideoToggle()");
+
+ var ele = getById("recordLocalbutton");
+ if (ele.dataset.state == "0") {
+ ele.dataset.state = "1";
+ ele.style.backgroundColor = "red";
+ ele.innerHTML = '';
+ if ("recording" in session.videoElement) {
+
+ } else {
+ recordLocalVideo("start");
+ }
+
+ if (session.director){
+ var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
+ if (elements[0]) {
+ elements[0].classList.add("pressed");
+ elements[0].innerHTML = ' Record';
+ }
+ }
+ return true;
+ } else {
+ if ("recording" in session.videoElement) {
+ recordLocalVideo("stop");
+ }
+ ele.dataset.state = "0";
+ ele.style.backgroundColor = "";
+ ele.innerHTML = '';
+
+ if (session.director){
+ var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
+ if (elements[0]) {
+ elements[0].classList.remove("pressed");
+ elements[0].innerHTML = ' Record';
+ }
+ }
+ return false;
+ }
+}
+
+function setupSensorData(pollrate = 30) {
+ session.sensors = {};
+ session.sensors.data = {};
+
+ if (window.Accelerometer && session.sensorDataFilter.includes("acc")) {
+ session.sensors.data.acc = {};
+ session.sensors.Accelerometer = new Accelerometer({
+ frequency: pollrate
+ });
+ session.sensors.Accelerometer.addEventListener('reading', e => {
+ session.sensors.data.acc.x = session.sensors.Accelerometer.x.toFixed(5);
+ session.sensors.data.acc.y = session.sensors.Accelerometer.y.toFixed(5);
+ session.sensors.data.acc.z = session.sensors.Accelerometer.z.toFixed(5);
+ session.sensors.data.acc.t = parseInt(Math.round(session.sensors.Accelerometer.timestamp));
+ });
+ session.sensors.Accelerometer.start();
+ }
+ if (window.Gyroscope && session.sensorDataFilter.includes("gyro")) {
+ session.sensors.data.gyro = {};
+ session.sensors.Gyroscope = new Gyroscope({
+ frequency: pollrate
+ });
+ session.sensors.Gyroscope.addEventListener('reading', e => {
+ session.sensors.data.gyro.x = session.sensors.Gyroscope.x.toFixed(5);
+ session.sensors.data.gyro.y = session.sensors.Gyroscope.y.toFixed(5);
+ session.sensors.data.gyro.z = session.sensors.Gyroscope.z.toFixed(5);
+ session.sensors.data.gyro.t = parseInt(Math.round(session.sensors.Gyroscope.timestamp));
+ });
+ session.sensors.Gyroscope.start();
+ }
+ if (window.Magnetometer && session.sensorDataFilter.includes("mag")) {
+ session.sensors.data.mag = {};
+ session.sensors.Magnetometer = new Magnetometer({
+ frequency: pollrate
+ });
+ session.sensors.Magnetometer.addEventListener('reading', e => {
+ session.sensors.data.mag.x = session.sensors.Magnetometer.x.toFixed(5);
+ session.sensors.data.mag.y = session.sensors.Magnetometer.y.toFixed(5);
+ session.sensors.data.mag.z = session.sensors.Magnetometer.z.toFixed(5);
+ session.sensors.data.mag.t = parseInt(Math.round(session.sensors.Magnetometer.timestamp));
+
+ });
+ session.sensors.Magnetometer.start();
+ session.sensors.deviceorientation = false;
+ } else if (session.sensorDataFilter.includes("ori")){
+ try{
+ window.addEventListener('deviceorientation', e => {
+ session.sensors.data.ori = {};
+ try{
+ session.sensors.data.ori.d = e.absolute;
+ } catch(event){}
+ session.sensors.data.ori.a = e.alpha.toFixed(5);
+ session.sensors.data.ori.b = e.beta.toFixed(5);
+ session.sensors.data.ori.g = e.gamma.toFixed(5);
+ session.sensors.data.ori.t = parseInt(Math.round(e.timestamp)) || Date.now();
+ });
+ session.sensors.deviceorientation = true;
+ } catch(e){
+ session.sensors.deviceorientation = false;
+ }
+ }
+ if (window.LinearAccelerationSensor && session.sensorDataFilter.includes("lin")) {
+ session.sensors.data.lin = {};
+ session.sensors.LinearAccelerationSensor = new LinearAccelerationSensor({
+ frequency: pollrate
+ });
+ session.sensors.LinearAccelerationSensor.addEventListener('reading', e => {
+ session.sensors.data.lin.x = session.sensors.LinearAccelerationSensor.x.toFixed(5);
+ session.sensors.data.lin.y = session.sensors.LinearAccelerationSensor.y.toFixed(5);
+ session.sensors.data.lin.z = session.sensors.LinearAccelerationSensor.z.toFixed(5);
+ session.sensors.data.lin.t = parseInt(Math.round(session.sensors.LinearAccelerationSensor.timestamp));
+ });
+ session.sensors.LinearAccelerationSensor.start();
+ }
+
+ if (navigator.geolocation && session.sensorDataFilter.includes("pos")){
+ navigator.geolocation.watchPosition(function(pos){
+ try {
+ session.sensors.data.pos = {};
+ session.sensors.data.pos.speed = pos.coords.speed.toFixed(3);
+ session.sensors.data.pos.alt = pos.coords.altitude.toFixed(3);
+ session.sensors.data.pos.t = pos.timestamp;
+ }catch(e){}
+ }, errorlog, {
+ enableHighAccuracy: true,
+ timeout: 5000,
+ maximumAge: 0
+ });
+ }
+
+ setInterval(function() {
+ session.sendMessage({sensors: session.sensors.data});
+ }, parseInt(1000 / pollrate));
+}
+
+
+function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
+ var audioKbps = false;
+ if (remote){
+ var video = remote;
+ if (remote.id === "videosource"){
+ remote = false;
+ }
+ } else {
+ var video = session.videoElement;
+ }
+ log(video.id);
+
+ if (!video){return;}
+
+ if ("recording" in video) {
+ if (action == "estop") {
+ video.recorder.eStop();
+ log("EMERGENCY Stopping RECORDING!");
+ video.recorder.stop();
+ delete(video.recorder);
+ delete(video.recording);
+ return;
+ } else if (action == "stop") {
+ log("Stopping RECORDING!");
+ video.recorder.stop();
+ delete(video.recorder);
+ delete(video.recording);
+ return;
+ } else if (action == "start") {
+ log("ALREADY RECORDING!");
+ if (remote){
+ getById("recordLocalbutton").dataset.state = "1";
+ getById("recordLocalbutton").style.backgroundColor = "red";
+ getById("recordLocalbutton").innerHTML = '';
+ }
+ return;
+ } else {
+ log("STOPPING RECORDING by default toggle!");
+ video.recorder.stop();
+ return;
+ }
+ return;
+ } else if (action == "start") {
+ if (!MediaRecorder) {
+ var msg = {};
+ msg.recorder = -3;
+ for (var i = 0;i
'+group;
+ eleGroup.appendChild(ele);
+ ele.onclick = function(){
+ changeGroupDirectorAPI(this.dataset.group);
+ }
+ }
+
+ if (state===true){
+ if (eleGroup.showDirector){
+ ele.classList.add("pressed");
+ } else {
+ ele.classList.add("green");
+ }
+ if (index === -1){
+ session.group.push(group);
+ }
+ } else if (state === false){
+ if (eleGroup.showDirector){
+ ele.classList.remove("pressed");
+ } else {
+ ele.classList.remove("green");
+ }
+ if (index > -1){
+ session.group.splice(index, 1);
+ }
+ } else if (ele.classList.contains("green")){
+ if (eleGroup.showDirector){
+ ele.classList.remove("pressed");
+ } else {
+ ele.classList.remove("green");
+ }
+ if (index > -1){
+ session.group.splice(index, 1);
+ }
+ } else {
+ if (eleGroup.showDirector){
+ ele.classList.add("pressed");
+ } else {
+ ele.classList.add("green");
+ }
+ if (index === -1){
+ session.group.push(group);
+ }
+ }
+ if (update){
+ if (session.group.length || session.allowNoGroup){
+ session.sendMessage({"group":session.group.join(",")});
+ } else {
+ session.sendMessage({"group":false});
+ }
+ }
+ if (session.group.indexOf(group)===-1){
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+function changeGroup(ele, state=null){
+
+ var group = ele.dataset.group;
+
+ var index = session.rpcs[ele.dataset.UUID].group.indexOf(group);
+
+ if (state===true){
+ ele.classList.add("pressed");
+ if (index === -1){
+ session.rpcs[ele.dataset.UUID].group.push(group);
+ }
+ } else if (state === false){
+ ele.classList.remove("pressed");
+ if (index > -1){
+ session.rpcs[ele.dataset.UUID].group.splice(index, 1);
+ }
+ } else if (ele.classList.contains("pressed")){
+ ele.classList.remove("pressed");
+ if (index > -1){
+ session.rpcs[ele.dataset.UUID].group.splice(index, 1);
+ }
+ } else {
+ ele.classList.add("pressed");
+ if (index === -1){
+ session.rpcs[ele.dataset.UUID].group.push(group);
+ }
+ }
+ if (session.rpcs[ele.dataset.UUID].group.length){
+ session.sendRequest({"group":session.rpcs[ele.dataset.UUID].group.join(",")}, ele.dataset.UUID);
+ } else {
+ session.sendRequest({"group":false}, ele.dataset.UUID);
+ }
+ syncDirectorState(ele);
+
+ if (session.rpcs[ele.dataset.UUID].group.indexOf(group)===-1){
+ return false;
+ } else {
+ return true;
+ }
+}
+
+function changeChannelOffset(UUID, channel){
+ var ele = document.querySelectorAll('[data-action-type="add-channel"][data--u-u-i-d="' + UUID + '"]');
+ for (var i=0;i
This effect will not work",4000,false);
+ return;
+ }
+ } else if (gpgpuSupport == "Google SwiftShader"){
+ if (!session.cleanOutput){
+ warnUser("Hardware acceleration isn't detected.
Please enable it for this effect to work correctly.
Settings -> Advanced -> System -> Use hardware-accleration", false, false);
+ }
+ return;
+ }
+ loadTensorflowJS();
+ updateRenderOutpipe();
+ //mainMeshMask();
+ } else {
+ //loadEffect(session.effect);
+ updateRenderOutpipe();
+ }
+
+ if ((session.permaid===false) && (session.roomid===false) && (session.view===false) && (session.director===false)){
+ updateURL("effects");
+ }
+}
+
+function loadTFLITEImages(){
+ if (session.effect!=="5"){return;} // only load if effects 5 is set.
+
+ if (session.defaultBackgroundImages){
+ try {
+ session.defaultBackgroundImages.reverse();
+ }catch(e){
+ errorlog("Could not process image list");
+ session.defaultBackgroundImages = false;
+ session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
+ return;
+ }
+ session.defaultBackgroundImages.forEach(imgSrc=>{
+ try {
+ var img = document.createElement("img");
+ img.onerror = function(){this.style.display="none";}; // hide images that fail to load
+ img.crossOrigin = "Anonymous";
+ img.src = imgSrc;
+ img.style="max-width:130px;max-height:73.5px;display:inline-block;margin:10px;cursor:pointer;";
+ img.onclick=function(event){changeTFLiteImage(event, this);};
+ getById("selectImageTFLITE_contents").prepend(img);
+ } catch(e){};
+ });
+ session.defaultBackgroundImages = false;
+ session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
+ } else if (!session.selectImageTFLITE_contents){
+ session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
+ }
+ if (document.getElementById("selectImageTFLITE")){
+ document.getElementById("selectImageTFLITE").style.display = "block";
+ document.getElementById("selectImageTFLITE").appendChild(session.selectImageTFLITE_contents);
+ session.selectImageTFLITE_contents.classList.remove("hidden");
+ } else if (document.getElementById("selectImageTFLITE3")){
+ document.getElementById("selectImageTFLITE3").style.display = "block";
+ document.getElementById("selectImageTFLITE3").appendChild(session.selectImageTFLITE_contents);
+ session.selectImageTFLITE_contents.classList.remove("hidden");
+ }
+}
+
+var effectsLoaded = {};
+var JEELIZFACEFILTER = null;
+async function loadEffect(effect){
+ warnlog("effect:"+effect);
+ var filename = effect.replace(/\W/g, '');
+ if (effectsLoaded[filename]){
+ effectsLoaded[filename]();
+ return;
+ } else {
+ effectsLoaded[filename] = function(){};
+ }
+ warnlog("Loading Effect: "+effect);
+ var script = document.createElement('script');
+ script.onload = async function() {
+ log("LOADED EFFECT");
+ effectsLoaded[filename] = await effectsEngine(filename);
+ log("effectsEngine();");
+ if (gpgpuSupport == "Google SwiftShader"){
+ if (!session.cleanOutput){
+ warnUser("Hardware acceleration isn't detected.
Please enable it for better performance.
Settings -> Advanced -> System -> Use hardware-accleration", false, false);
+ }
+ }
+ effectsLoaded[filename]();
+ }
+ script.src = "./filters/"+filename+".js?"+parseInt(1000*Math.random());
+ document.head.appendChild(script);
+ warnUser("Loading custom effects model...",1000);
+}
+
+async function loadScript(url, callback=false){
+ var res = null;
+ var rej = null;
+ var promise = new Promise((resolve, reject) => {
+ res = resolve;
+ rej = reject;
+ });
+
+ var check = document.querySelector("script[src='"+url+"']");
+ if (check){
+ if(callback){callback();}
+ } else {
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ script.onload = function(){
+ res();
+ if(callback){
+ callback();
+ }
+ };
+ document.head.appendChild(script);
+ }
+ return await promise;
+}
+
+function loadTensorflowJS(){
+ if (session.TFJSModel!=null){
+ return;
+ }
+ log("loadTensorflowJS()");
+ session.TFJSModel=true;
+ var script = document.createElement('script');
+ var script2 = document.createElement('script');
+ var script3 = document.createElement('script');
+ var script4 = document.createElement('script');
+ script.onload = function() {
+ document.head.appendChild(script2);
+ }
+ script2.onload = function() {
+ document.head.appendChild(script3);
+ }
+ script3.onload = function() {
+ document.head.appendChild(script4);
+ }
+ script4.onload = function() {
+ async function loadModel(){
+ session.TFJSModel = await faceLandmarksDetection.load(faceLandmarksDetection.SupportedPackages.mediapipeFacemesh);
+ closeModal();
+ warnUser("Almost done loading model...",3000);
+ }
+ loadModel();
+
+ }
+ script.src = "./thirdparty/tfjs/tf-core.js";
+ script2.src = "./thirdparty/tfjs/tf-converter.js";
+ script3.src = "./thirdparty/tfjs/tf-backend-webgl.js";
+ script4.src = "./thirdparty/tfjs/face-landmarks-detection.js";
+ warnUser("Downloading a big effects model... may take a minute",15000);
+
+ script.type = 'text/javascript';script2.type = 'text/javascript';script3.type = 'text/javascript';script4.type = 'text/javascript';
+ document.head.appendChild(script);
+}
+
+
+
+var TFLITELOADING = false;
+function attemptTFLiteJsFileLoad(){
+ if (session.tfliteModule!==false){
+ return true;
+ }
+ warnUser("Loading effects model...");
+ TFLITELOADING=true;
+ session.tfliteModule={};
+
+ if (!document.getElementById("tflitesimdjs")){
+ var tmpScript = document.createElement('script');
+ tmpScript.onload = loadTFLiteModel;
+ tmpScript.type = 'text/javascript';
+ tmpScript.src = "./thirdparty/tflite/tflite-simd.js?ver=2";
+ tmpScript.id = "tflitesimdjs";
+ document.head.appendChild(tmpScript);
+ }
+
+ return false;
+}
+async function changeTFLiteImage(ev, ele){
+ if (ele.files && ele.files[0]) {
+ if (session.tfliteModule.img){
+ session.tfliteModule.img.classList.remove("selectedTFImage");
+ }
+ session.tfliteModule.img = document.createElement("img");
+ session.tfliteModule.img.style="max-width:130px;max-height:73.5px;display:inline-block;margin:10px;cursor:pointer;";
+ session.tfliteModule.img.onclick=function(event){changeTFLiteImage(event, this);};
+ ele.parentNode.parentNode.insertBefore(session.tfliteModule.img, ele.parentNode);
+ session.tfliteModule.img.onload = () => {
+ URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
+ }
+ session.tfliteModule.img.src = URL.createObjectURL(ele.files[0]); // set src to blob url
+ session.tfliteModule.img.classList.add("selectedTFImage");
+
+ } else if (ele.tagName.toLowerCase() == "img"){
+ session.tfliteModule.img.classList.remove("selectedTFImage");
+ session.tfliteModule.img = ele
+ session.tfliteModule.img.classList.add("selectedTFImage");
+ }
+}
+async function changeEffectAmount(ev, ele){
+ session.effectValue = ele.value;
+ if (ele.id === "selectEffectAmountInput"){
+ getById("selectEffectAmountInput3").value = ele.value
+ }
+ log("session.effectValue: "+session.effectValue);
+}
+async function loadTFLiteModel(){
+ try {
+
+ if (session.tfliteModule && (session.tfliteModule.img)){
+ var img = session.tfliteModule.img;
+ session.tfliteModule = await createTFLiteSIMDModule();
+ session.tfliteModule.img = img;
+ } else {
+ session.tfliteModule = {};
+ session.tfliteModule = await createTFLiteSIMDModule();
+ }
+ if (!session.tfliteModule.simd){
+ var elements = document.querySelectorAll('[data-warnSimdNotice]')
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].style.display = "inline-block";
+ }
+ }
+ } catch(e){
+ warnlog("TF-LITE FAILED TO LOAD");
+ closeModal();
+ return;
+ }
+ const modelResponse = await fetch("./thirdparty/tflite/segm_full_v679.tflite");
+ session.tfliteModule.model = await modelResponse.arrayBuffer();
+
+ session.tfliteModule.HEAPU8.set(new Uint8Array(session.tfliteModule.model), session.tfliteModule._getModelBufferMemoryOffset());
+ session.tfliteModule._loadModel(session.tfliteModule.model.byteLength);
+ session.tfliteModule.activelyProcessing = false;
+ TFLITELOADING = false;
+ closeModal();
+ if (LaunchTFWorkerCallback){TFLiteWorker();}
+}
+function smdInfo(){
+ warnUser("For improved performance, use Chrome v87 or newer with SIMD support enabled.
Enable SIMD here: chrome://flags/#enable-webassembly-simd", false, false);
+}
+
+function getGuestTarget(type, id){
+ var element = document.querySelectorAll('[data-action-type="'+type+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
+ if (!element.length){
+ return element = getRightOrderedElement('[data-action-type="'+type+'"][data--u-u-i-d]', id);
+ } else {
+ element = element[0];
+ }
+ return element;
+}
+
+function getGuestTargetScene(scene, id){
+ var element = document.querySelectorAll('[data-action-type="addToScene"][data-scene="'+scene+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
+ if (!element.length){
+ return element = getRightOrderedElement('[data-action-type="addToScene"][data-scene="'+scene+'"][data--u-u-i-d]', id);
+ } else {
+ element = element[0];
+ }
+ return element;
+}
+function getGuestTargetGroup(group, id){
+ var element = document.querySelectorAll('[data-action-type="toggle-group"][data-group="'+group+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
+ if (!element.length){
+ return getRightOrderedElement('[data-action-type="toggle-group"][data-group="'+group+'"][data--u-u-i-d]', id);
+ } else {
+ element = element[0];
+ }
+ return element;
+}
+
+function targetGuest(target, action, value=null){
+ if (target){
+ if ((target == (parseInt(target)+"")) && target<100){
+ target -=1;
+ }
+ } else {
+ target=1;
+ }
+ warnlog("target "+target);
+ warnlog("action "+action);
+ warnlog("value "+value);
+ if ((action == 0) || (action == "forward")) {
+ var element = getGuestTarget("forward", target);
+ if (element) {
+ directMigrate(element, true, value); // if value is set, it will auto transfer the guest to that room.
+ }
+ } else if ((action == 1) || (action == "addScene")) {
+ var scene = 1;
+ if (value == "null" || value == null || value == "toggle"){
+ scene = 1;
+ } else if ((value !== true) && (value !== false)){
+ scene = value;
+ }
+ var element = getGuestTargetScene(scene, target); // oscid/action/target/value 1/1/scene
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true); // false or true return
+ }
+ } else if ((action == 2) || (action == "muteScene")) {
+ var element = getGuestTarget("mute-scene", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directMute(element, true); // false/true
+ }
+ } else if ((action == 3) || (action == "mic")) {
+ var element = getGuestTarget("mute-guest", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return remoteMute(element, true); // false/true
+ }
+ } else if ((action == 4) || (action == "hangup")) {
+ var element = getGuestTarget("hangup", target);
+ if (element) {
+ return directHangup(element, true); // false or true; false if confirmed no
+ }
+ } else if ((action == 5) || (action == "soloChat")) { // see soloChatBidirectional action=9 for two-way
+ var element = getGuestTarget("solo-chat", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return session.toggleSoloChat(element.dataset.UUID);
+ }
+ } else if ((action == 6) || (action == "speaker")) {
+ var element = getGuestTarget("toggle-remote-speaker", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return remoteSpeakerMute(element);
+ }
+ } else if ((action == 7) || (action == "display")) {
+ var element = getGuestTarget("toggle-remote-display", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return remoteDisplayMute(element);
+ }
+ } else if ((action == 8) || (action == "group")) {
+ if (value == "null" || value == null){
+ value = 1;
+ }
+ var element = getGuestTargetGroup(value, target);
+ if (element) {
+ return changeGroup(element, null, value);
+ }
+ } else if ((action == 9) || (action == "soloChatBidirectional")) {
+ var element = getGuestTarget("solo-chat", target);
+ if (element) {
+ var ctrl = {};
+ ctrl.ctrlKey = true;
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return session.toggleSoloChat(element.dataset.UUID, ctrl);
+ }
+ } else if ((action == 12) || (action == "addScene2")) {
+ var element = getGuestTargetScene(2, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 13) || (action == "addScene3")) {
+ var element = getGuestTargetScene(3, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 14) || (action == "addScene4")) {
+ var element = getGuestTargetScene(4, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 15) || (action == "addScene5")) {
+ var element = getGuestTargetScene(5, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 16) || (action == "addScene6")) {
+ var element = getGuestTargetScene(6, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 17) || (action == "addScene7")) {
+ var element = getGuestTargetScene(7, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 18) || (action == "addScene8")) {
+ var element = getGuestTargetScene(8, target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return directEnable(element, true)
+ }
+ } else if ((action == 19) || (action == "forceKeyframe")) {
+ var element = getGuestTarget("force-keyframe", target);
+ if (element) {
+ return requestKeyframeScene(element);
+ }
+ } else if ((action == 20) || (action == "soloVideo")) {
+ var element = getGuestTarget("solo-video", target);
+ if (element) {
+ if (value===true){
+ element.value = 1;
+ } else if (value===false){
+ element.value = 0;
+ }
+ return requestInfocus(element);
+ }
+ } else if ((action == 21) || (action == "sendChat")) {
+ var element = getGuestTarget("solo-video", target); // just something that probably exists.
+ if (element) {
+ return sendChat(value, element.dataset.UUID);
+ }
+ } else if ((action == 22) || (action == "sendDirectorChat")) {
+ var element = getGuestTarget("solo-video", target); // just something that probably exists.
+ if (element) {
+ return sendChat(value, element.dataset.UUID, true);
+ }
+ } else if ((action == 27) || (action == "volume")){
+ var element = getGuestTarget("volume", target);
+ if (element) {
+ element.value = parseInt(value) || 100;
+ return remoteVolume(element);
+ }
+ }
+ return false;
+}
+
+
+function whipClient(){ // api.vdo.ninja api OSC (websocket / https API hotkey support). The iFrame API method provides greater customization.
+ if (!session.whip){return;}
+ warnlog("WHIP Client started");
+
+ var socket = null;
+ var connecting = false;
+ var failedCount = 0;
+
+ function connect(){
+ clearTimeout(connecting);
+ if (socket){
+ if (socket.readyState === socket.OPEN){return;}
+ try{
+ socket.close();
+ } catch(e){}
+ }
+ console.log("Trying to load websocket...");
+
+ socket = new WebSocket("wss://whip.vdo.ninja");
+
+ socket.onclose = function (){
+ failedCount+=1;
+ clearTimeout(connecting);
+ connecting = setTimeout(function(){connect();},100*(failedCount-1));
+
+ };
+
+ socket.onerror = function (e){
+ console.error(e);
+ failedCount+=1;
+ clearTimeout(connecting);
+ connecting = setTimeout(function(){connect();},100*failedCount);
+ };
+
+ socket.onopen = function (){
+ failedCount = 0;
+ try{
+ var settings = {};
+ socket.send(JSON.stringify({"join":session.whip}));
+ } catch(e){
+ connecting = setTimeout(function(){connect();},1);
+ }
+ };
+
+ socket.addEventListener('message', async function (event) {
+ if (event.data){
+
+ var data = JSON.parse(event.data);
+
+ if ("sdp" in data){
+ var resp = await processWHIP(data);
+ if (resp){
+ var ret = {};
+ data.result = resp;
+ ret.callback = data;
+ log(ret);
+ socket.send(JSON.stringify(ret));
+ }
+ }
+ }
+ });
+ }
+ connect();
+}
+
+async function processWHIP(data){
+ var msg = {};
+ msg.description = {};
+ msg.description.type = "offer";
+ msg.description.sdp = data.sdp;
+ // msg.session = session.generateRandomString(5);
+ msg.UUID = session.generateRandomString(25); // fake
+
+ if (data.streamID){
+ msg.streamID = data.streamID;
+ } else {
+ msg.streamID = session.generateRandomString(15); // fake
+ }
+ await session.setupIncoming(msg); // could end up setting up the peer the wrong way.
+
+ var callback = null;
+ var promise = new Promise((resolve, reject) => {
+ callback = resolve;
+ });
+ console.log(session.rpcs[msg.UUID]);
+ session.rpcs[msg.UUID].whipCallback = callback;
+ session.connectPeer(msg);
+ return await promise; // return SDP answer for the remote WHIP request
+}
+
+var queuedSendingAPIMsgs = [];
+function pokeAPI(action, data, streamID = null){
+ if (!session.api){return;}
+
+ if (session.apiSocket){
+ try {
+ var msg = {};
+ msg.update = {};
+ msg.update.streamID = streamID || session.streamID || null;
+ msg.update.action = action;
+ msg.update.value = data;
+ session.apiSocket.send(JSON.stringify(msg));
+ } catch(e){
+ errorlog(e);
+ }
+ } else if (session.apiSocket!==null){
+ queuedSendingAPIMsgs.push([action, data, streamID]);
+ if (queuedSendingAPIMsgs.length>20){
+ queuedSendingAPIMsgs.shift();
+ }
+ }
+}
+
+function oscClient(){ // api.vdo.ninja api OSC (websocket / https API hotkey support). The iFrame API method provides greater customization.
+ if (!session.api){return;}
+ warnlog("oscClient started");
+
+ var socket = null;
+ var connecting = false;
+ var failedCount = 0;
+
+ function connect(){
+ clearTimeout(connecting);
+ if (socket){
+ if (socket.readyState === socket.OPEN){return;}
+ try{
+ socket.close();
+ } catch(e){}
+ }
+ socket = new WebSocket(session.apiserver);
+
+ socket.onclose = function (){
+ session.apiSocket = false;
+ failedCount+=1;
+ clearTimeout(connecting);
+ connecting = setTimeout(function(){connect();},100*(failedCount-1));
+
+ };
+
+ socket.onerror = function (){
+ failedCount+=1;
+ clearTimeout(connecting);
+ connecting = setTimeout(function(){connect();},100*failedCount);
+ };
+
+ socket.onopen = function (){
+ failedCount = 0;
+ try{
+ socket.send(JSON.stringify({"join":session.api}));
+ session.apiSocket = socket;
+ if (queuedSendingAPIMsgs.length){
+ queuedSendingAPIMsgs.forEach(msg=>{
+ pokeAPI(msg[0],msg[1], msg[2]);
+ });
+ queuedSendingAPIMsgs = [];
+ }
+ pokeAPI("details", getDetailedState(session.streamID));
+ } catch(e){
+ connecting = setTimeout(function(){connect();},1);
+ }
+
+ };
+
+ socket.addEventListener('message', async function (event) {
+ if (event.data){
+
+ var data = JSON.parse(event.data);
+
+ if ("msg" in data){
+ data = data.msg
+ }
+
+ if ("value" in data){
+ if (("action" in data) && (data.action == "layout")){
+ try {
+ data.value = JSON.parse(data.value) || data.value;
+ } catch(e){}
+ }
+ }
+
+ var resp = processMessage(data);
+ if (resp!==null){
+ var ret = {};
+ data.result = resp;
+ ret.callback = data;
+ log(ret);
+ socket.send(JSON.stringify(ret));
+ }
+ }
+ });
+ }
+ connect();
+}
+
+function setupCommands(){
+ var commands = {}
+
+ commands.raisehand = function(value=null,value2=null){
+ return raisehand();
+ };
+ commands.togglehand = function(value=null,value2=null){
+ return raisehand();
+ };
+ commands.togglescreenshare = function(value=null,value2=null){
+ toggleScreenShare();
+ return session.screenShareState;
+ };
+ commands.chat = function(value=null,value2=null){
+ toggleChat(value);
+ return session.chat;
+ };
+ commands.speaker = function(value=null,value2=null){
+ if (value === true) { // unmute
+ session.speakerMuted = false; // set
+ toggleSpeakerMute(true); // apply
+ } else if (value === false) { // mute
+ session.speakerMuted = true; // set
+ toggleSpeakerMute(true); // apply
+ } else if (value === "toggle") { // toggle
+ toggleSpeakerMute();
+ }
+ return session.speakerMuted;
+ }; // mute speaker
+ commands.mic = function(value=null,value2=null){
+ if (value === true) { // unmute
+ session.muted = false; // set
+ log(session.muted);
+ toggleMute(true); // apply
+ } else if (value === false) { // mute
+ session.muted = true; // set
+ log(session.muted);
+ toggleMute(true); // apply
+ } else if (value === "toggle") { // toggle
+ toggleMute();
+ }
+ return session.muted;
+ };
+ commands.camera = function(value=null,value2=null){
+ if (value === true) { // unmute
+ session.videoMuted = false; // set
+ log(session.videoMuted);
+ toggleVideoMute(true); // apply
+ } else if (value === false) { // mute
+ session.videoMuted = true; // set
+ log(session.videoMuted);
+ toggleVideoMute(true); // apply
+ } else if (value === "toggle") { // toggle
+ toggleVideoMute();
+ }
+ return session.videoMuted;
+ }
+ commands.hangup = function(value=null,value2=null){
+ hangup();
+ return true;
+ };
+ commands.bitrate = function(value=null,value2=null){
+ if (value===false){
+ value = 0;
+ } else if (value===true){
+ value = -1;
+ } else {
+ value = parseInt(value) || 0;
+ }
+ for (var i in session.rpcs) {
+ try {
+ session.requestRateLimit(value, i);
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ return value;
+ };
+
+ commands.getDetails = function(value=null,value2=null){
+ return getDetailedState();
+ }
+
+ commands.reload = function(value=null,value2=null){
+ reloadRequested();
+ return false;
+ };
+ commands.volume = function(value=null,value2=null){
+ if (value===false){
+ value = 0;
+ } else if (value===true){
+ value = 100
+ } else {
+ value = parseInt(value) || 0;
+ }
+ value = parseFloat(value/100);
+ for (var i in session.rpcs) {
+ try {
+ session.rpcs[i].videoElement.volume = parseFloat(value);
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ return value;
+ };
+
+ commands.forceKeyframe = function(value=null,value2=null){
+ return session.forcePLI();
+ };
+
+ commands.panning = function(value=null,value2=null){
+ if (value===false){
+ value = 90;
+ } else if (value===true){
+ value = 90
+ } else {
+ value = parseInt(value);
+ }
+ for (var uuid in session.rpcs) {
+ try {
+ adjustPan(uuid, value); // &panning needs to be added to enable. playback only; not mic out.
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ return value;
+ };
+
+ commands.record = function(value=null,value2=null){
+
+ if (!session.videoElement){return;}
+
+ if (value === false) { // mute
+ if ("recording" in session.videoElement) {
+ recordLocalVideo("stop");
+ }
+ } else if (value === true){
+ if ("recording" in session.videoElement) {
+ // already recording
+ } else {
+ recordLocalVideo("start");
+ }
+ }
+ return value;
+ };
+
+ commands.group = function(value=null,value2=null){
+ if (value && (value !== "null")){ // mute
+ return changeGroupDirectorAPI(value);
+ }
+ return false;
+ };
+
+ commands.sendChat = function(value=null,value2=null){
+ sendChat(value);
+ // sendChatMessage // this would add it to the chat message
+ return true;
+ };
+
+ commands.layout = function(value=null,value2=null){
+ try {
+ if (parseInt(value)==value){
+ value = parseInt(value);
+ if (value ==0){
+ value = false;
+ } else {
+ value -= 1;
+ }
+ } else if (typeof value === "object"){
+ session.layout = value;
+ pokeIframeAPI("layout-updated", session.layout);
+ if (session.director){
+ var combined = {};
+ for (var i=0;i
");
- modalTemplate =
- `Update OBS Studio to v26.1.2 or newer; older versions and StreamLabs OBS are not supported on macOS.\
-
download here: https://github.com/obsproject/obs-studio/releases/tag/26.1.2\
-
\
- Please use the Electron Capture app if there are further problems or if you wish to use StreamLabs on macOS still.
\
-
You can find more details within our wiki guide - https://github.com/steveseguin/obsninja/wiki/FAQ#mac-os\
-
You can bypass this error message by refreshing, Clicking Here, or by adding &streamlabs to the URL, but it may still not actually work.\
- \
-
Please report this problem to steve@seguin.email if you feel it is an error.\
- Update OBS Studio to v26.1.2 or newer; older versions and StreamLabs OBS are not supported on macOS.\
+
download here: https://github.com/obsproject/obs-studio/releases\
+
\
+ Please use the Electron Capture app if there are further problems or if you wish to use StreamLabs on macOS still.
\
+
You can bypass this error message by refreshing, Clicking Here, or by adding &streamlabs to the URL, but it may still not actually work.\
+ \
+
Please report this problem to steve@seguin.email if you feel it is an error.\
+
- Please select which you wish to share';
+ }
+ }
+ }
+
+ if (session.roomid || urlParams.has('roomid') || urlParams.has('r') || urlParams.has('room') || filename || (session.permaid !== false)) {
+ var roomid = "";
+ if (urlParams.has('room')) { // needs to be first; takes priority
+ roomid = urlParams.get('room');
+ } else if (urlParams.has('roomid')) {
+ roomid = urlParams.get('roomid');
+ } else if (urlParams.has('r')) {
+ roomid = urlParams.get('r');
+ } else if (session.roomid) {
+ roomid = session.roomid;
+ } else if (filename) {
+ roomid = filename;
+ }
+ session.roomid = sanitizeRoomName(roomid);
+ }
+
+ if ((session.permaid===false) && (session.roomid===false) && (session.view===false) && (session.effect===false) && (session.director===false)){
+ session.effect = null;
+ }
+
+ if (urlParams.has('effects') || urlParams.has('effect')) {
+ session.effect = urlParams.get('effects') || urlParams.get('effect') || null;
+ }
+
+ if (window.FaceDetector !== undefined){
+ document.querySelectorAll(".facetracker").forEach(ele=>{
+ ele.disabled = null;
+ ele.removeAttribute("disabled");
+ ele.title = "Will slowly pan, tilt, and zoom in on the first face detected";
+ });
+ }
+
+
+ if (urlParams.has('imagelist')){ // "&imagelist="+encodeURIComponent(JSON.stringify(["./media/bg_sample.webp", "./media/bg_sample2.webp"]))
+ var imageList = urlParams.get('imagelist'); //
+ if (imageList){
+ try {
+ imageList = JSON.parse(decodeURIComponent(imageList));
+ } catch(e){
+ console.error(e);
+ }
+ if (imageList.length){
+ session.defaultBackgroundImages = imageList; // ["./media/bg_sample.webp", "./media/bg_sample2.webp"]
+ } else {
+ warnlog("empty image array; skipping");
+ }
+ }
+ }
+
+ if (session.effect!==false){
+ if (session.effect === null){
+ getById("effectsDiv").style.display = "block";
+ session.effect = "0";
+ } else if (session.effect === "0" || session.effect === "false" || session.effect === "off" || session.effect === 0){
+ session.effect = false;
+ getById("effectSelector3").style.display = "none";
+ getById("effectsDiv3").style.display = "none";
+ getById("effectSelector").style.display = "none";
+ getById("effectsDiv").style.display = "none";
+ }
+
+ if (session.effect === "5"){
+
+ loadTFLITEImages();
+
+ getById("effectSelector").style.display = "none";
+ getById("effectsDiv").style.display = "block";
+
+ }
+ if (session.effect === "3a"){ // heavier blur
+ session.effectValue = 5;
+ session.effect = "3";
+ } else if (session.effect === "3"){
+ session.effectValue = 2;
+ } else if (session.effect === "7"){
+ session.effectValue = 1;
+ }
+ // mirror == 2
+ // face == 1
+ // blur = 3
+ // green = 4
+ // image = 5
+ }
+
+
+ if (urlParams.has('effectvalue') || urlParams.has('ev')) {
+ session.effectValue = parseInt(urlParams.get('effectvalue')) || parseInt(urlParams.get('ev')) || 0;
+ session.effectValue_default = session.effectValue;
+ }
+
+ if (session.webcamonly == true) {
+ if (session.introButton){
+ getById("container-2").className = 'column columnfade hidden'; // Hide screen share
+ getById("head3").classList.add('hidden');
+ getById("head3a").classList.add('hidden');
+ } else {
+ getById("container-2").className = 'column columnfade hidden'; // Hide screen share
+ getById("container-3").classList.add("skip-animation");
+ getById("container-3").classList.remove('pointer');
+ delayedStartupFuncs.push([previewWebcam]);
+ }
+ }
+ if (session.introOnClean && (session.permaid===false) && (session.roomid===false)){
+ //getById("container-2").className = 'column columnfade hidden'; // Hide screen share
+ getById("head3").classList.add('hidden');
+ getById("head3a").classList.add('hidden');
+ } else if (session.introOnClean && (session.scene===false) && ((session.permaid!==false || session.roomid!==false))){
+ getById("container-2").className = 'column columnfade hidden'; // Hide screen share
+ getById("container-3").classList.add("skip-animation");
+ getById("container-3").classList.remove('pointer');
+ delayedStartupFuncs.push([previewWebcam]);
+ }
+
+ //if (!session.director && ((ChromeVersion == 86) || (ChromeVersion == 77) || (ChromeVersion == 62) || (ChromeVersion == 51)) && (((session.permaid===false) && session.view) || (session.scene!==false))){
+ // session.studioSoftware = true; // vmix
+ if (window.obsstudio){
+ session.studioSoftware = true;
+ }
+ if (session.cleanViewer){
+ if (session.view && !session.director && session.permaid===false){
+ session.cleanOutput = true;
+ }
+ }
+ if (urlParams.has('clock')){
+ if (urlParams.get('clock') === "false"){
+ session.showTime = false;
+ } else if (urlParams.get('clock') === "0"){
+ session.showTime = false;
+ } else {
+ session.showTime = true;
+ }
+ } else if (session.cleanOutput){
+ session.showTime = false;
+ }
+
+ if (urlParams.has('hidescreenshare') || urlParams.has('hidess') || urlParams.has('sshide') || urlParams.has('screensharehide')) { // this way I don't need to remember what it's called. I can just guess. :D
+ session.screenShareElementHidden = true;
+ }
+
+ if (urlParams.has('sspaused') || urlParams.has('sspause') || urlParams.has('ssp')) { // this way I don't need to remember what it's called. I can just guess. :D
+ session.screenShareStartPaused = true;
+ }
+
+ if (urlParams.has('zoomedbitrate') || urlParams.has('zb')) { // this way I don't need to remember what it's called. I can just guess. :D
+ session.zoomedBitrate = urlParams.get('zoomedbitrate') || urlParams.get('zb') || 2500;
+ session.zoomedBitrate = parseInt(session.zoomedBitrate) ;
+ }
+
+ if (urlParams.has('screenshareid') || urlParams.has('ssid')) {
+ if (urlParams.get('screenshareid') || urlParams.get('ssid')) {
+ session.screenshareid = urlParams.get('screenshareid') || urlParams.get('ssid');
+ session.screenshareid = sanitizeStreamID(session.screenshareid);
+ } else {
+ session.screenshareid = session.streamID + "_ss";
+ }
+ }
+
+ if (urlParams.has('screensharevideoonly') || urlParams.has('ssvideoonly') || urlParams.has('ssvo')) {
+ session.screenshareVideoOnly = true;
+ }
+
+ if (urlParams.has('screensharefps') || urlParams.has('ssfps')) {
+ if (urlParams.get('screensharefps') || urlParams.get('ssfps')) {
+ session.screensharefps = urlParams.get('screensharefps') || urlParams.get('ssfps');
+ session.screensharefps = parseInt(session.screensharefps) || 2;
+ }
+ }
+
+ if (urlParams.has('screensharequality') || urlParams.has('ssq')) {
+ if (urlParams.get('screensharequality') || urlParams.get('ssq')) {
+ session.screensharequality = urlParams.get('screensharequality') || urlParams.get('ssq');
+ session.screensharequality = parseInt(session.screensharequality) || 0;
+ try {
+ getById("gear_screen").parentNode.removeChild(getById("gear_screen"));
+ } catch(e){}
+ }
+ }
+
+ if (urlParams.has('screensharebitrate') || urlParams.has('ssbitrate')) {
+ session.screenShareBitrate = urlParams.get('screensharebitrate') || urlParams.get('ssbitrate');
+ session.screenShareBitrate = parseInt(session.screenShareBitrate) || 2500;
+ }
+
+ if (urlParams.has('screensharelabel') || urlParams.has('sslabel')) {
+ session.screenShareLabel = urlParams.get('screensharelabel') || urlParams.get('sslabel');
+ session.screenShareLabel = decodeURIComponent(session.screenShareLabel);
+ session.screenShareLabel = session.screenShareLabel.replace(/_/g, " ")
+ }
+
+ if (session.roomid!==false){
+ if (!(session.cleanOutput)) {
+ if (session.roomid === "test") {
+ if (session.password === session.defaultPassword) {
+ window.focus();
+ var testRoomResponse = confirm(miscTranslations["room-test-not-good"]);
+ if (testRoomResponse == false) {
+ hangup();
+ throw new Error("User requested to not enter room 'room'.");
+ }
+ }
+ }
+ }
+
+ if (session.audioDevice === false && session.outputDevice === false) {
+ getById("headphonesDiv2").style.display = "inline-block";
+ getById("headphonesDiv").style.display = "inline-block";
+ }
+ getById("addPasswordBasic").style.display = "none";
+
+ getById("info").innerHTML = "";
+ getById("info").style.color = "#CCC";
+ getById("videoname1").value = session.roomid;
+ getById("dirroomid").innerText = session.roomid;
+ getById("roomid").innerText = session.roomid;
+ getById("container-1").className = 'column columnfade hidden';
+ getById("container-4").className = 'column columnfade hidden';
+ // container 5 is share media file; 6 is share website
+ getById("container-7").style.display = 'none';
+ getById("container-8").style.display = 'none';
+ getById("container-9").style.display = 'none';
+ getById("container-10").style.display = 'none';
+ getById("container-11").style.display = 'none';
+ getById("container-12").style.display = 'none';
+ getById("container-13").style.display = 'none';
+ getById("container-14").style.display = 'none';
+ getById("container-15").style.display = 'none';
+ getById("mainmenu").style.alignSelf = "center";
+ getById("mainmenu").classList.add("mainmenuclass");
+ getById("header").style.alignSelf = "center";
+
+ if (session.webcamonly == true) { // mobile or manual flag 'webcam' pflag set
+ getById("head1").innerHTML = '';
+ } else {
+ getById("head1").innerHTML = 'Please select an option to join.';
+ }
+
+ if (session.roomid.length > 0) {
+ if (session.videoDevice === 0) {
+ if (session.audioDevice === 0) {
+ miniTranslate(getById("add_camera"), "join-room", "Join room");
+ } else {
+ miniTranslate(getById("add_camera"), "join-room-with-mic", "Join room with Microphone");
+ }
+ } else {
+ miniTranslate(getById("add_camera"), "join-room-with-camera", "Join Room with Camera");
+ }
+ miniTranslate(getById("add_screen"), "share-screen-with-room", "Screenshare with Room");
+ } else {
+ if (session.videoDevice === 0) {
+ miniTranslate(getById("add_camera"), "share-your-mic", "Share your Microphone");
+ } else {
+ miniTranslate(getById("add_camera"), "share-your-camera", "Share your Camera");
+ }
+ miniTranslate(getById("add_screen"), "share-your-screen", "Share your Screen");
+ }
+ getById("head3").classList.add('hidden');
+ getById("head3a").classList.add('hidden');
+ getById("container-2").title = getById("add_screen").innerText;
+ getById("container-3").title = getById("add_camera").innerText;
+
+ if (session.scene !== false) {
+ getById("container-4").className = 'column columnfade';
+ getById("container-3").className = 'column columnfade';
+ getById("container-2").className = 'column columnfade';
+ getById("container-1").className = 'column columnfade';
+ getById("header").className = 'hidden';
+ getById("info").className = 'hidden';
+ getById("head1").className = 'hidden';
+ getById("head2").className = 'hidden';
+ getById("mainmenu").style.display = "none";
+ getById("translateButton").style.display = "none";
+ log("Update Mixer Event on REsize SET");
+ window.onresize = updateMixer;
+ window.onorientationchange = function(){
+ setTimeout(updateMixer, 200);
+
+ };
+ joinRoom(session.roomid); // this is a scene, so we want high resolutions
+ getById("main").style.overflow = "hidden";
+
+ if (session.chatbutton === true) {
+ getById("chatbutton").classList.remove("hidden");
+ getById("controlButtons").style.display = "inherit";
+ } else if (session.chatbutton === false) {
+ getById("chatbutton").classList.add("hidden");
+ }
+ } else if ((session.permaid === null) && (session.roomid == "")) {
+ if (!(session.cleanOutput)) {
+ getById("head3").classList.remove('hidden');
+ getById("head3a").classList.remove('hidden');
+ }
+ } else if ((window.obsstudio) && (session.permaid === false) && (session.director === false) && (session.view) &&(session.roomid.length>0)) { // we already know roomid !== false
+ updateURL("scene", true, false); // we also know it's not a scene, but we will assume it is in this specific case.
+ }
+
+
+ } else if (urlParams.has('director') || urlParams.has('dir')) { // if I do a short form of this, it will cause duplications in the code elsewhere.
+ if (directorLanding == false) {
+ var director_room_input = urlParams.get('director') || urlParams.get('dir');
+ director_room_input = sanitizeRoomName(director_room_input);
+ log("director_room_input:" + director_room_input);
+
+ if (urlParams.has('codirector') || urlParams.has('directorpassword') || urlParams.has('dirpass') || urlParams.has('dp')) {
+ session.directorPassword = urlParams.get('codirector') || urlParams.get('directorpassword') || urlParams.get('dirpass') || urlParams.get('dp');
+ if (!session.directorPassword) {
+ window.focus();
+ session.directorPassword = await promptAlt(miscTranslations["enter-director-password"], true);
+ } else {
+ session.directorPassword = decodeURIComponent(session.directorPassword);
+ }
+ if (session.directorPassword){
+ session.directorPassword = sanitizePassword(session.directorPassword)
+ await generateHash(session.directorPassword + session.salt + "abc123", 12).then(function(hash) { // million to one error.
+ log("dir room hash is " + hash);
+ session.directorHash = hash;
+ return;
+ }).catch(errorlog);
+ } else {
+ session.directorPassword = false;
+ }
+ }
+
+ setTimeout(function(director_room_input){createRoom(director_room_input);},20, director_room_input);
+ }
+ if (session.chatbutton === true) {
+ getById("chatbutton").classList.remove("hidden");
+ getById("controlButtons").style.display = "inherit";
+ } else if (session.chatbutton === false) {
+ getById("chatbutton").classList.add("hidden");
+ }
+ } else if ((session.view) && (session.permaid === false)) {
+ //if (!session.activeSpeaker){
+ session.audioMeterGuest = false;
+ //}
+ if ((session.style===false) && window.obsstudio){
+ session.style = 1;
+ }
+ if (session.audioEffects === null) {
+ session.audioEffects = false;
+ }
+ log("Update Mixer Event on REsize SET");
+ getById("translateButton").style.display = "none";
+ window.onresize = updateMixer;
+ window.onorientationchange = function(){setTimeout(function(){
+ updateMixer();
+ }, 200);};
+ getById("main").style.overflow = "hidden";
+
+ if (session.chatbutton === true) {
+ getById("chatbutton").classList.remove("hidden");
+ getById("controlButtons").style.display = "inherit";
+ } else if (session.chatbutton === false) {
+ getById("chatbutton").classList.add("hidden");
+ }
+ }
+
+ if (urlParams.has('nofileshare') || urlParams.has('nodownloads') || urlParams.has('nofiles')){
+ session.hostedFiles = false;
+ session.nodownloads = true;
+ getById('sharefilebutton').style.display = "none";
+ getById('sharefilebutton').classList.add("hidden");
+ } else if (session.mobile){
+ getById('sharefilebutton').style.display = "none";
+ getById('sharefilebutton').classList.add("hidden");
+ } else if (session.roomid==false){
+ getById('sharefilebutton').style.display = "none";
+ getById('sharefilebutton').classList.add("hidden");
+ }
+
+ if (session.audioEffects === null) {
+ session.audioEffects = true;
+ }
+
+ if (session.audioEffects) {
+ getById("channelGroup1").style.display = "block";
+ getById("channelGroup2").style.display = "block";
+ }
+
+ if (urlParams.has('hidemenu') || urlParams.has('hm')) { // needs to happen the room and permaid applications
+ getById("mainmenu").style.display = "none";
+ getById("header").style.display = "none";
+ getById("mainmenu").style.opacity = 0;
+ getById("header").style.opacity = 0;
+ }
+
+ if (session.view) {
+ getById("main").className = "";
+ getById("credits").style.display = 'none';
+ try {
+ if (session.label === false) {
+ if (document.title == "") {
+ document.title = "View=" + session.view.toString();
+ } else {
+ document.title += ", View=" + session.view.toString();
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ };
+ }
+
+ if (urlParams.has('waitimage')){
+ session.waitImage = urlParams.get('waitimage') || false;
+ }
+
+ if (((session.view) && (session.roomid === false)) || (session.waitImage && (session.scene!==false))) {
+
+ getById("container-4").className = 'column columnfade';
+ getById("container-3").className = 'column columnfade';
+ getById("container-2").className = 'column columnfade';
+ getById("container-1").className = 'column columnfade';
+ //getById("header").className = 'hidden';
+ getById("info").className = 'hidden';
+ getById("header").className = 'hidden';
+ getById("head1").className = 'hidden';
+ getById("head2").className = 'hidden';
+ getById("head3").classList.add('hidden');
+ getById("head3a").classList.add('hidden');
+
+
+ getById("mainmenu").style.backgroundRepeat = "no-repeat";
+ getById("mainmenu").style.backgroundPosition = "bottom center";
+ getById("mainmenu").style.minHeight = "100%";
+ getById("mainmenu").style.minWidth = "100%";
+ getById("mainmenu").style.backgroundSize = "100px 100px";
+ getById("mainmenu").innerHTML = '';
+ getById("mainmenu").classList.remove("row");
+ getById("mainmenu").style.display = "block";
+
+ if (urlParams.has('waittimeout')){
+ session.waitImageTimeout = parseInt(urlParams.get('waittimeout')) || 0;
+ }
+ if (!session.fakeFeeds){
+ session.waitImageTimeoutObject = setTimeout(function() {
+ session.waitImageTimeoutObject = true;
+ try {
+ if ((session.view)) {
+ if (document.getElementById("mainmenu")) {
+ if (session.waitImage){
+ getById("mainmenu").innerHTML += '';
+ getById("retryimage").src = decodeURIComponent(session.waitImage);
+ getById("retryimage").onerror = function(){this.style.display='none';};
+
+ if (session.cover) {
+ getById("retryimage").style.objectFit = "cover";
+ }
+
+ } else if (!(session.cleanOutput)){
+ getById("mainmenu").innerHTML += '';
+ getById("retrySpinner").onclick = function(){
+ updateURL("cleanoutput");
+ location.reload();
+ }
+ getById("retrySpinner").title = miscTranslations["waiting-for-the-stream"]
+ }
+ if (urlParams.has('waitmessage')){
+ getById("mainmenu").innerHTML += '';
+ getById("retrymessage").innerText = urlParams.get('waitmessage');
+ getById("retrySpinner").title = urlParams.get('waitmessage');
+ }
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }, session.waitImageTimeout);
+ }
+
+ log("auto playing");
+ if ((iPad || iOS) && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1 && SafariVersion > 13) { // Modern iOS doesn't need pop up
+ play();
+ } else if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { // Safari on Desktop does require pop up
+ if (!(session.cleanOutput)) {
+ warnUser("Safari requires us to ask for an audio permission to use peer-to-peer technology. You will need to accept it in a moment if asked to view this live video", 20000);
+ }
+ navigator.mediaDevices.getUserMedia({
+ audio: true
+ }).then(function() {
+ closeModal();
+ play();
+ }).catch(function() {
+ play();
+ });
+ } else { // everything else is OK.
+ play();
+ }
+ } else if (session.roomid) {
+ try {
+ if (session.label === false) {
+ if (document.title == "") {
+ document.title = "Room=" + session.roomid.toString();
+ } else {
+ document.title += ": " + session.roomid.toString();
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ };
+
+ }
+
+ hideHomeCheck();
+
+ setTimeout(function(){
+ for (var i in delayedStartupFuncs) {
+ var cb = delayedStartupFuncs[i];
+ log(cb.slice(1));
+ cb[0](...cb.slice(1)); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#A_better_apply
+ }
+ delayedStartupFuncs = [];
+ },50);
+
+ if ((session.effect=="3") || (session.effect=="4") || (session.effect=="5")){
+ attemptTFLiteJsFileLoad();
+ } else if (session.effect=="6"){
+ loadTensorflowJS();
+ } else if (session.effect=="9"){
+ session.effect="sample";
+ //loadEffect(session.effect);
+ warnUser("Loading custom effects model...",1000);
+ } else if (session.effect=="10"){
+ session.effect="dog";
+ //loadEffect(session.effect);
+ warnUser("Loading custom effects model...",1000);
+ } else if (session.effect=="11"){
+ session.effect="anon";
+ //loadEffect(session.effect);
+ warnUser("Loading custom effects model...",1000);
+ } else if (session.effect=="12"){
+ session.effect="sample";
+ //loadEffect(session.effect);
+ warnUser("Loading custom effects model...",1000);
+ } else if (session.effect=="facetracking"){
+ session.effect="1";
+ } else if (session.effect=="zoom"){
+ session.effect="7";
+ }
+
+ if (session.effect === "3"){
+ getById("selectEffectAmount").style.display = "block";
+ getById("selectEffectAmount3").style.display = "block";
+ getById("selectEffectAmountInput").value = session.effectValue;
+ getById("selectEffectAmountInput3").value = session.effectValue;
+ } else if (session.effect === "7"){
+ getById("selectEffectAmount").style.display = "block";
+ getById("selectEffectAmount3").style.display = "block";
+ session.effectValue = 1.0;
+ getById("selectEffectAmountInput").min = 1;
+ getById("selectEffectAmountInput").max = 1.99;
+ getById("selectEffectAmountInput").step = 0.01
+ getById("selectEffectAmountInput3").min = 1;
+ getById("selectEffectAmountInput3").max = 1.99;
+ getById("selectEffectAmountInput3").step = 0.01
+
+ getById("selectEffectAmountInput").value = session.effectValue;
+ getById("selectEffectAmountInput3").value = session.effectValue;
+ }
+
+ if (location.protocol !== 'https:') {
+ if (!(session.cleanOutput)) {
+ warnUser("SSL (https) is not enabled. This site will not work without it!
Try accessing the site from here instead.", false, false);
+ }
+ }
+
+ if (session.sensorData) {
+ setupSensorData(parseInt(session.sensorData));
+ }
+
+ try {
+ navigator.mediaDevices.ondevicechange = reconnectDevices;
+ } catch (e) {
errorlog(e);
}
+
+ if (urlParams.has('autohide')) {
+ session.autohide=true;
+ }
+ if (session.autohide && (session.scene===false)){// && (session.roomid!==false)){
+ getById("main").onmouseover = showControl; // this is correct. (it's not session.showControls)
+ document.ontouchstart = showControl; // this is correct. (it's not session.showControls)
+ getById("controlButtons").classList.add("zeroHeight");
+ getById("gridlayout").classList.add("nocontrolbar");
+ }
- if (session.speedtest){
- if (session.maxvideobitrate !== false) {
- if (session.maxvideobitrate > 6000) {
- session.maxvideobitrate = 6000; // Please feel free to get rid of this if using your own TURN servers...
- }
- } else {
- session.maxvideobitrate = 6000; // don't let people pull more than 2500 from you
- }
- if (session.bitrate !== false) {
- if (session.bitrate > 6000) {
- session.bitrate = 6000; // Please feel free to get rid of this if using your own TURN servers...
- }
- }
- } else {
- if (session.maxvideobitrate !== false) {
- if (session.maxvideobitrate > 2500) {
- session.maxvideobitrate = 2500; // Please feel free to get rid of this if using your own TURN servers...
- }
- } else {
- session.maxvideobitrate = 2500; // don't let people pull more than 2500 from you
- }
- if (session.bitrate !== false) {
- if (session.bitrate > 2500) {
- session.bitrate = 2500; // Please feel free to get rid of this if using your own TURN servers...
- }
- }
+ if (urlParams.has('flagship')) {
+ session.flagship = true;
}
-}
-
-if (urlParams.has('wss')) {
- if (urlParams.get('wss')) {
- session.wss = "wss://" + urlParams.get('wss');
+ //if (!session.flagship && session.mobile && (session.limitTotalBitrate===false)){
+ // session.limitTotalBitrate = session.totalRoomBitrate_default; // 500, with the max per guest stream out at maxMobileBitrate (350kbps) or 35-kbps if more than X in the room.
+ //}
+
+ if (urlParams.has('maxmobilebitrate')) {
+ session.maxMobileBitrate = parseInt(urlParams.has('maxmobilebitrate')) || 0;
+ }
+ if (urlParams.has('lowmobilebitrate')) {
+ session.lowMobileBitrate = parseInt(urlParams.has('lowmobilebitrate')) || 0;
}
-}
-if (urlParams.has('queue')) {
- session.queue = true;
-}
-
-if (isIFrame) { // reduce CPU load if not needed.
- window.onmessage = function(e) { // iFRAME support
- log(e);
+ // Please contact steve on discord.vdo.ninja if you'd like this iFRAME tweaked, expanded, etc -- it's updated based on user request
+
+ session.remoteInterfaceAPI = function(e) { // iFRAME api support
+ warnlog(e);
try {
if ("function" in e.data) { // these are calling in-app functions, with perhaps a callback -- TODO: add callbacks
var ret = null;
@@ -2611,6 +4139,10 @@ if (isIFrame) { // reduce CPU load if not needed.
ret.innerHTML = e.data.value;
} else if (e.data.function === "publishScreen") {
ret = publishScreen();
+ } else if (e.data.function === "routeMessage"){
+ try {
+ session.ws.onmessage({data: e.data.value});
+ } catch(e){warnlog("handshake not yet setup");}
} else if (e.data.function === "eval") {
eval(e.data.value); // eval == evil ; feedback welcomed
}
@@ -2618,13 +4150,52 @@ if (isIFrame) { // reduce CPU load if not needed.
} catch (err) {
errorlog(err);
}
+
+ if ("sendData" in e.data) { // send generic data via p2p. Send whatever you want I guess; there is a max chunk size of course. Use filetransfer for large files?
+ var UUID = false;
+ var streamID = false;
+ var type = false;
+ if (e.data.UUID){
+ UUID = e.data.UUID;
+ } else if (e.data.streamID){
+ streamID = e.data.streamID;
+ }
+ if (e.data.type){
+ type = e.data.type;
+ }
+ var ret = session.sendGenericData(e.data.sendData, UUID, streamID, type); // comes out the other side as: ("dataReceived", data, UUID);
+ if (!ret){warnlog("Not connected yet or no peers available");}
+ return;
+ }
+
+ if ("PPT" in e.data){
+ log("PTT activated-webmain");
+ if (e.data.PPT === true) { // unmute
+ session.muted = false; // set
+ getById("mutebutton").classList.add("PPTActive");
+ log(session.muted);
+ toggleMute(true); // apply
+
+ } else if (e.data.PPT === false) { // mute
+ session.muted = true; // set
+ getById("mutebutton").classList.remove("PPTActive");
+ log(session.muted);
+ toggleMute(true); // apply
+ } else if (e.data.PPT === "toggle") { // toggle
+ toggleMute();
+ }
+ return; // this is a high-load call, so lets skip the rest of the checks to save cpu.
+ }
if ("sendChat" in e.data) {
sendChat(e.data.sendChat); // sends to all peers; more options down the road
+ return;
}
// Chat out gets called via getChatMessage function
// Related code: parent.postMessage({"chat": {"msg":-----,"type":----,"time":---} }, "*");
+ // session.requestResolution(vid.dataset.UUID, wrw*window.devicePixelRatio, hrh*window.devicePixelRatio);
+
if ("mic" in e.data) { // this should work for the director's mic mute button as well. Needs to be manually enabled the first time still tho.
if (e.data.mic === true) { // unmute
session.muted = false; // set
@@ -2652,7 +4223,63 @@ if (isIFrame) { // reduce CPU load if not needed.
toggleVideoMute();
}
}
-
+
+ if ("keyframe" in e.data) {
+ session.sendKeyFrameScenes();
+ }
+
+
+ if ("groups" in e.data) {
+ if (typeof e.data.groups == "object"){
+ session.group = e.data.groups || [];
+ } else if (!e.data.group){
+ session.group = [];
+ } else {
+ session.group = e.data.groups.split(",");
+ }
+ var eleGroup = getById("groups");
+ eleGroup.querySelectorAll('[data-action-type="toggle-group"][data-group]').forEach(group=>{
+ if (!(session.group && session.group.includes(group))){
+ group.remove("green");
+ }
+ });
+
+ if (session.group){
+ session.group.forEach(group =>{
+ var ele = eleGroup.querySelector('[data-action-type="toggle-group"][data-group="'+group+'"');
+ if (!ele){
+ ele = document.createElement("div");
+ ele.dataset.actionType = "toggle-group";
+ ele.dataset.group = group;
+ ele.classList.add('float');
+ ele.style.display = "inline-block";
+ ele.role = "button";
+ ele.innerHTML = '
'+group;
+ eleGroup.appendChild(ele);
+ ele.onclick = function(){
+ changeGroupDirectorAPI(this.dataset.group);
+ }
+ }
+ ele.classList.add("green");
+ });
+ }
+
+ updateMixer();
+
+ if (session.group.length || session.allowNoGroup){
+ session.sendMessage({"group":session.group.join(",")});
+ if (session.screenShareState && (session.screenshareType ===3)){
+ session.sendMessage({"group":session.group.join(","), altUUID:true});
+ }
+ } else {
+ session.sendMessage({"group":false});
+ if (session.screenShareState && (session.screenshareType ===3)){
+ session.sendMessage({"group":false, altUUID:true});
+ }
+ }
+
+ }
+
if ("mute" in e.data) {
if (e.data.mute === true) { // unmute
session.speakerMuted = true; // set
@@ -2680,72 +4307,227 @@ if (isIFrame) { // reduce CPU load if not needed.
if ("recording" in session.videoElement) {
recordLocalVideo("stop");
}
- } else if (e.data.record == true){
+ } else if (e.data.record === true){
if ("recording" in session.videoElement) {
// already recording
} else {
recordLocalVideo("start");
}
- }
- }
-
-
- if ("volume" in e.data) {
- for (var i in session.rpcs) {
- try {
- session.rpcs[i].videoElement.volume = parseFloat(e.data.volume);
- } catch (e) {
- errorlog(e);
+ } else if (e.data.record){
+ var video = document.getElementById(e.data.record);
+ if (video){
+ recordLocalVideo(null, 4000, video);
}
}
}
- if ("bitrate" in e.data) {
+
+ if ("volume" in e.data) { // might not work with iframes or meshcast currently.
+ session.volume = parseFloat(e.data.volume) || 0;
+ if (session.volume > 1.0){ // this is a bit quasi improper. But the API is official 0 to 1.0; not 0 to 100, so this is mainly a catch for those not using the API right.
+ session.volume = session.volume/100.0;
+ }
+ if (!("target" in e.data) || (e.data.target == "*")){
+ if (session.videoElement){
+ session.videoElement.volume = session.volume;
+ }
+ }
for (var i in session.rpcs) {
try {
- session.requestRateLimit(parseInt(e.data.bitrate), i);
+ if (!session.rpcs[i].videoElement){continue;}
+ if ("streamID" in session.rpcs[i]) {
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.rpcs[i].videoElement.volume = session.volume;
+ }
+ } else {
+ session.rpcs[i].videoElement.volume = session.volume;
+ }
+ }
} catch (e) {
errorlog(e);
}
}
}
- if ("audiobitrate" in e.data) {
- for (var i in session.rpcs) {
+
+
+ if ("panning" in e.data){ // panning adjusts the stereo pan , although current its UUID based. can add stream ID based if requested.
+ if ("UUID" in e.data){
try {
- session.requestAudioRateLimit(parseInt(e.data.audiobitrate), i);
+ adjustPan(UUID, e.data.panning);
} catch (e) {
errorlog(e);
}
- }
- }
-
-
- if ("sceneState" in e.data) { // TRUE OR FALSE - tells the connected peers if they are live or not via a tally light change.
-
- var visibility = e.data.sceneState;
- var bundle = {};
- bundle.sceneUpdate = [];
-
- for (var UUID in session.rpcs) {
- if (session.rpcs[UUID].visibility !== visibility) { // only move forward if there is a change; the event likes to double fire you see.
-
- session.rpcs[UUID].visibility = visibility;
- var msg = {};
- msg.visibility = visibility;
-
- if (session.rpcs[UUID].videoElement.style.display == "none") { // Flag will be left alone, but message will say its disabled.
- msg.visibility = false;
+ } else {
+ for (var i in session.rpcs) {
+ try {
+ adjustPan(i, e.data.panning);
+ } catch (e) {
+ errorlog(e);
}
-
- msg.UUID = UUID;
- session.sendRequest(msg, UUID);
- bundle.sceneUpdate.push(msg);
}
}
- session.sendRequest(bundle); // we want all publishing peers to know the state
+ }
+
+ if (("targetBitrate" in e.data) || ("targetAudioBitrate" in e.data)) { // this sets the fundemental bitrate target, but does not necessarily "lock" .
+
+ var msg = {};
+ if ("targetBitrate" in e.data){
+ msg.targetBitrate = e.data.targetBitrate;
+ }
+ if ("targetAudioBitrate" in e.data){
+ msg.targetAudioBitrate = e.data.targetAudioBitrate;
+ }
+ if (e.data.requestAs){
+ msg.requestAs = e.data.requestAs;
+ }
+ if (e.data.remote){
+ msg.remote = e.data.remote;
+ }
+ for (var i in session.rpcs) {
+ try {
+ if ("streamID" in session.rpcs[i]) {
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.sendRequest(msg, i);
+ }
+ } else if (e.data.UUID && (e.data.UUID===i)) {
+ session.sendRequest(msg, i);
+ } else if (e.data.streamID) {
+ if (session.rpcs[i].streamID == e.data.streamID) { // specify a stream ID or let it apply to all videos
+ session.sendRequest(msg, i);
+ }
+ } else {
+ session.sendRequest(msg, i); // bitrate = 0 pauses the video
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ }
+
+ if ("manualBitrate" in e.data){
+ for (var i in session.rpcs) {
+ try {
+ if ("streamID" in session.rpcs[i]) {
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.rpcs[i].manualBandwidth = e.data.manualBitrate;
+ session.requestRateLimit(false, i);
+ }
+ } else if (e.data.UUID && (e.data.UUID===i)) {
+ session.rpcs[i].manualBandwidth = e.data.manualBitrate;
+ session.requestRateLimit(false, i);
+ } else if (e.data.streamID) {
+ if (session.rpcs[i].streamID == e.data.streamID) { // specify a stream ID or let it apply to all videos
+ session.rpcs[i].manualBandwidth = e.data.manualBitrate
+ session.requestRateLimit(false, i);
+ }
+ } else {
+ session.rpcs[i].manualBandwidth = e.data.manualBitrate;
+ session.requestRateLimit(false, i);
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }
}
+ if ("bitrate" in e.data) { /// set a video bitrate for a video; scene or view link; kbps
+ var lock = true;
+ if ("lock" in e.data){ // since this is the iframe API, we're going to assume the default is manual over-ride. VDO.Ninja's automixer logic won't override a locked bitrate.
+ lock = e.data.lock;
+ }
+ for (var i in session.rpcs) {
+ try {
+ if ("streamID" in session.rpcs[i]) { // we only target publishers with this call
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.requestRateLimit(e.data.bitrate, i, false, lock);
+ }
+ } else if (e.data.UUID && (e.data.UUID===i)) {
+ session.requestRateLimit(e.data.bitrate, i, false, lock);
+ } else if (e.data.streamID) {
+ if (session.rpcs[i].streamID == e.data.streamID) { // specify a stream ID or let it apply to all videos
+ session.requestRateLimit(e.data.bitrate, i, false, lock);
+ }
+ } else {
+ session.requestRateLimit(e.data.bitrate, i, false, lock); // bitrate = 0 pauses the video
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ }
+
+ if ("audiobitrate" in e.data) { // changes the audio bitrate of a specific or all inbound media tracks. kbps
+ var lock = true;
+ if ("lock" in e.data){ // since this is the iframe API, we're going to assume the default is manual over-ride. VDO.Ninja's automixer logic won't override a locked bitrate.
+ lock = e.data.lock;
+ }
+ for (var i in session.rpcs) {
+ try {
+ if ("streamID" in session.rpcs[i]) { // we only target publishers with this call
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.requestAudioRateLimit(parseInt(e.data.bitrate), i, lock);
+ }
+ } else if (e.data.UUID && (e.data.UUID===i)) {
+ session.requestAudioRateLimit(parseInt(e.data.bitrate), i, lock);
+ } else if (e.data.streamID) {
+ if (session.rpcs[i].streamID == e.data.streamID) { // specify a stream ID or let it apply to all videos
+ session.requestAudioRateLimit(parseInt(e.data.bitrate), i, lock);
+ }
+ } else {
+ session.requestAudioRateLimit(parseInt(e.data.bitrate), i, lock); // bitrate = 0 pauses the video
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ }
+
+ if ("changeVideoDevice" in e.data) {
+ warnlog(e.data.changeVideoDevice);
+ changeVideoDevice(e.data.changeVideoDevice);
+ }
+
+ if ("changeAudioDevice" in e.data) {
+ warnlog(e.data.changeAudioDevice);
+ changeAudioDevice(e.data.changeAudioDevice);
+ }
+
+ if ("changeAudioOutputDevice" in e.data) {
+ warnlog(e.data.changeAudioOutputDevice);
+ changeAudioOutputDeviceById(e.data.changeAudioOutputDevice);
+ }
+
+ if ("getDeviceList" in e.data) { // get a list of local camera / audio devices
+ warnlog(e.data.getDeviceList);
+ enumerateDevices().then(function(deviceInfos) {
+ parent.postMessage({
+ "deviceList": JSON.parse(JSON.stringify(deviceInfos))
+ }, session.iframetarget);
+ });
+ }
+
+ if ("sceneState" in e.data) { // TRUE OR FALSE - tells the connected peers if they are live or not via a tally light change.
+ var msg = {};
+ msg.obsState = {};
+ msg.obsState.visibility = e.data.sceneState || false;
+ msg.obsState.recording = e.data.sceneState || false;
+ session.sendRequest(msg);
+ }
+
+ if ("layouts" in e.data) {
+ session.layouts = e.data.layouts;
+ }
+
if ("sendMessage" in e.data) { // webrtc send to viewers
session.sendMessage(e.data);
}
@@ -2753,136 +4535,239 @@ if (isIFrame) { // reduce CPU load if not needed.
if ("sendRequest" in e.data) { // webrtc send to publishers
session.sendRequest(e.data);
}
+
+ if ("sendRawMIDI" in e.data) { // webrtc send to publishers
+ //var msg = {};
+ //msg.midi = {};
+ //msg.midi.d = e.data.sendRawMIDI.data; aka [d1,d2,d3];
+ //msg.midi.c = e.data.sendRawMIDI.channel;
+ //msg.midi.s = e.data.sendRawMIDI.timestamp;
+ // e.data.UUID or e.data.streamID or leave empty to send to all
+ if ("UUID" in e.data){
+ sendRawMIDI(e.data.sendRawMIDI, e.data.UUID); // send to connection
+ } else if (e.data.streamID){
+ sendRawMIDI(e.data.sendRawMIDI, false, e.data.streamID); // send to connection
+ } else {
+ sendRawMIDI(e.data.sendRawMIDI); // send to all
+ }
+ return; // make it send faster.
+ }
if ("sendPeers" in e.data) { // webrtc send message to every connected peer; like send and request; a hammer vs a knife.
session.sendPeers(e.data);
}
- if ("reload" in e.data) {
- location.reload();
+ if ("reload" in e.data) { // reload the page
+ reloadRequested(); // location.reload();, but with no user prompt (force reload)
+ }
+
+ if ("getFaces" in e.data){
+ if (e.data.faceTrack){
+ session.grabFaceData = true;
+ getFaces();
+ } else {
+ session.grabFaceData = false;
+ }
}
- if ("getStats" in e.data) {
-
+ if (("getStats" in e.data)){
var stats = {};
- stats.total_outbound_connections = Object.keys(session.pcs).length;
- stats.total_inbound_connections = Object.keys(session.rpcs).length;
- stats.inbound_stats = {};
- for (var i in session.rpcs) {
- stats.inbound_stats[session.rpcs[i].streamID] = session.rpcs[i].stats;
- }
-
-
- for (var uuid in session.pcs) {
- setTimeout(function(UUID) {
- session.pcs[UUID].getStats().then(function(stats) {
- stats.forEach(stat => {
- if (stat.type == "outbound-rtp") {
- if (stat.kind == "video") {
-
- if ("qualityLimitationReason" in stat) {
- session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
- }
- if ("framesPerSecond" in stat) {
- session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
- }
- if ("encoderImplementation" in stat) {
- session.pcs[UUID].stats.encoder = stat.encoderImplementation;
- }
- }
- } else if (stat.type == "remote-candidate") {
- if ("relayProtocol" in stat) {
- if ("ip" in stat) {
- session.pcs[UUID].stats.remote_relay_IP = stat.ip;
- }
- session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
- }
- if ("candidateType" in stat) {
- session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
- }
- } else if (stat.type == "local-candidate") {
- if ("relayProtocol" in stat) {
- if ("ip" in stat) {
- session.pcs[UUID].stats.local_relayIP = stat.ip;
- }
- session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
- }
- if ("candidateType" in stat) {
- session.pcs[UUID].stats.local_candidateType = stat.candidateType;
- }
- } else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
+ try {
+ stats.inbound_stats = {};
+ stats.total_outbound_connections = Object.keys(session.pcs).length;
+ stats.total_inbound_connections = Object.keys(session.rpcs).length;
+ for (var i in session.rpcs) {
+ stats.inbound_stats[session.rpcs[i].streamID] = session.rpcs[i].stats;
+ }
+ for (var uuid in session.pcs) {
+ setTimeout(function(UUID) {
+ session.pcs[UUID].getStats().then(function(stats) {
+ stats.forEach(stat => {
- if ("availableOutgoingBitrate" in stat){
- session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
+ if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
+
+ if (stat.type == "outbound-rtp") {
+ if (stat.kind == "video") {
+
+ if ("qualityLimitationReason" in stat) {
+
+ session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
+ }
+ if ("framesPerSecond" in stat) {
+ session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
+ }
+ if ("encoderImplementation" in stat) {
+ session.pcs[UUID].stats.encoder = stat.encoderImplementation;
+ }
+ }
+ } else if (stat.type == "remote-candidate") {
+ if ("relayProtocol" in stat) {
+ if ("ip" in stat) {
+ session.pcs[UUID].stats.remote_relay_IP = stat.ip;
+ }
+ session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
+ }
+ if ("candidateType" in stat) {
+ session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
+ }
+ } else if (stat.type == "local-candidate") {
+ if ("relayProtocol" in stat) {
+ if ("ip" in stat) {
+ session.pcs[UUID].stats.local_relayIP = stat.ip;
+ }
+ session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
+ }
+ if ("candidateType" in stat) {
+ session.pcs[UUID].stats.local_candidateType = stat.candidateType;
+ }
+ } else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
+
+ if ("availableOutgoingBitrate" in stat){
+ session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
+ }
+ if ("totalRoundTripTime" in stat){
+ if ("responsesReceived" in stat){
+ session.pcs[UUID].stats.average_roundTripTime_ms = parseInt((stat.totalRoundTripTime/stat.responsesReceived)*1000);
+ }
+
+ }
}
- if ("totalRoundTripTime" in stat){
- session.pcs[UUID].stats.total_roundTripTime_ms = stat.totalRoundTripTime*1000;
- }
- }
+ return;
+ });
return;
});
- return;
- });
- }, 0, uuid);
+ }, 0, uuid);
+ }
+ } catch(e){
+ // disconnected probably
}
setTimeout(function() {
stats.outbound_stats = {};
- for (var i in session.pcs) {
- stats.outbound_stats[i] = session.pcs[i].stats;
- }
+ try {
+ for (var i in session.pcs) {
+ stats.outbound_stats[i] = session.pcs[i].stats;
+ }
+ } catch(e){}
parent.postMessage({
"stats": stats
- }, "*");
+ }, session.iframetarget);
}, 1000);
}
if ("getRemoteStats" in e.data) {
- session.sendRequest({"requestStats":true, "remote":session.remote});
+ if (session.remote){
+ session.sendRequest({"requestStats":true, "remote":session.remote});
+ } else {
+ session.sendRequest({"requestStats":true});
+ }
+ }
+
+ if ("requestStatsContinuous" in e.data) {
+ if (session.remote){
+ session.sendRequest({"requestStatsContinuous":e.data.requestStatsContinuous, "remote":session.remote});
+ } else {
+ session.sendRequest({"requestStatsContinuous":e.data.requestStatsContinuous});
+ }
}
if ("getLoudness" in e.data) {
log("GOT LOUDNESS REQUEST");
if (e.data.getLoudness == true) {
- session.pushLoudness = true;
- var loudness = {};
+ if (!session.pushLoudness && (session.audioEffects!==true)){
+ session.pushLoudness = true;
+ for (var i in session.rpcs) {
+ updateIncomingAudioElement(i); // this can be called when turning on/off inbound audio processing.
+ }
+ } else {
+ session.pushLoudness = true;
+ }
+
+ var loudness = {};
for (var i in session.rpcs) {
loudness[session.rpcs[i].streamID] = session.rpcs[i].stats.Audio_Loudness;
}
parent.postMessage({
"loudness": loudness
- }, "*");
+ }, session.iframetarget);
} else {
- session.pushLoudness = false;
+ if (session.pushLoudness && !session.audioEffects){ // turn off audio processing
+ session.pushLoudness = false;
+ for (var i in session.rpcs) {
+ updateIncomingAudioElement(i)
+ }
+ } else {
+ session.pushLoudness = false; // can't turn off audio processing
+ }
+ }
+ }
+
+ if ("getEffectsData" in e.data) {
+ log("GOT getEffects Data REQUESTed"); // face tracking info, etc.
+ if (e.data.getEffectsData !== false) {
+ session.pushEffectsData = e.data.getEffectsData; // which effect do you want the data from? it won't enable the effect necessarily; just the ML pipeline
+
+ //parent.postMessage({
+ // "effectsData": effectsData,
+ // "effectsID": session.pushEffectsData
+ //}, session.iframetarget);
+
+ } else {
+ session.pushEffectsData = false;
}
}
- if ("getStreamIDs" in e.data) {
- if (e.data.getStreamIDs == true) {
+ if ("getStreamIDs" in e.data) { // get a list of stream Ids, with a label if it is present. label = false if not there
+ if (e.data.getStreamIDs) {
var streamIDs = {};
for (var i in session.rpcs) {
streamIDs[session.rpcs[i].streamID] = session.rpcs[i].label;
}
parent.postMessage({
"streamIDs": streamIDs
- }, "*");
-
+ }, session.iframetarget);
}
}
-
- if ("close" in e.data) {
- for (var i in session.rpcs) {
- try {
- session.rpcs[i].close();
- } catch (e) {
- errorlog(e);
+
+ if ("getStreamInfo" in e.data) { // get a list of stream Ids, with a label if it is present. label = false if not there
+ try {
+ var UUIDS = {};
+ for (var i in session.rpcs){
+ UUIDS[i] = {};
+ UUIDS[i].label = session.rpcs[i].label || false;
+ UUIDS[i].streamID = session.rpcs[i].streamID || false;
+ if (session.rpcs[i].stats && session.rpcs[i].stats.info){
+ UUIDS[i].info = session.rpcs[i].stats.info;
+ } else {
+ UUIDS[i].info = {};
+ }
}
+ parent.postMessage({
+ "streamInfo": UUIDS
+ }, session.iframetarget);
+ } catch(e){
+ errorlog(e);
}
}
- if ("style" in e.data) {
+ if (("close" in e.data) || ("hangup" in e.data)) { // disconnect and hangup all inbound streams.
+ var tmp = e.data.close || e.data.hangup;
+ if (tmp == "estop"){ // try to stop the video recording even if not complete; if you can't wait even ms before a reload/exit.
+ console.log("ESTOP");
+ session.hangup(false,true);
+ } else if (tmp == "reload"){ // stop and reload the page safely.
+ session.hangup(true);
+ } else { // just hangup, but can take up to 1-second to do so fully.
+ session.hangup();
+ }
+ }
+ if ("hangup" in e.data) { // disconnect and hangup all inbound streams.
+ session.hangup();
+ }
+
+ if ("style" in e.data) { // insert a custom style sheet
try {
const style = document.createElement('style');
style.textContent = e.data.style;
@@ -2892,9 +4777,15 @@ if (isIFrame) { // reduce CPU load if not needed.
errorlog(e);
}
}
+
+ if ("getDetailedState" in e.data) {
+ var detailedState = getDetailedState();
+ parent.postMessage({
+ "detailedState": detailedState
+ }, session.iframetarget);
+ }
-
- if ("automixer" in e.data) {
+ if ("automixer" in e.data) { // stop the auto mixer if you want to control the layout and bitrate yourself
if (e.data.automixer == true) {
session.manual = false;
try {
@@ -2904,21 +4795,144 @@ if (isIFrame) { // reduce CPU load if not needed.
session.manual = true;
}
}
+
+ if ("advancedMode" in e.data){
+ if (e.data.advancedMode){
+ document.documentElement.style.setProperty('--advanced-mode', "inline-block"); // show advanced items
+ } else {
+ document.documentElement.style.setProperty('--advanced-mode', "none"); // hide advanced items
+ }
+ }
+
+ if ("requestStream" in e.data){
+ if (e.data.requestStream){ // load a specific stream ID
+ log("requestStream iframe api");
+ session.requestStream(e.data.requestStream);
+ } // don't use if the stream is in your room (as not needed)
+ } // you can load a stream ID from inside a room that exists outside any room
+
+
+ if ("previewMode" in e.data){
+ if ("layout" in e.data){
+ session.layout = e.data.layout;
+ pokeIframeAPI("layout-updated", session.layout);
+ }
+ switchModes(e.data.previewMode);
+ } else if ("layout" in e.data){
+ warnlog("changing layout request via IFRAME API");
+ session.layout = e.data.layout;
+ pokeIframeAPI("layout-updated", session.layout);
+
+ if (e.data.obsCommand){
+ issueLayoutOBS(e.data);
+ } else {
+ if (session.director){
+ if ("scene" in e.data){
+ if ("UUID" in e.data){
+ issueLayout(e.data.layout, e.data.scene, e.data.UUID);
+ } else {
+ issueLayout(e.data.layout, e.data.scene);
+ }
+ } else if ("UUID" in e.data){
+ issueLayout(e.data.layout, false, e.data.UUID);
+ }
+ }
+ }
+ updateMixer();
+ } else if (e.data.obsCommand){
+ errorlog("obsCommand via iframe API currently needs a layout..");
+ }
+
+
+ if ("slotmode" in e.data){
+ if (session.slotmode){
+ session.slotmode = parseInt(e.data.slotmode);
+ } else {
+ session.slotmode = false;
+ }
+ }
+
+ //////////// manual scale. Request a specific down-scaled resolution from a remote connection
+ var targetWidth = false;
+ var targetHeight = false;
+ if ("targetWidth" in e.data){
+ targetWidth = e.data.targetWidth || 0;
+ }
+ if ("targetHeight" in e.data){
+ targetHeight = e.data.targetHeight || 0;
+ }
+ // session.viewheight or session.viewwidth
+ if ((targetWidth || targetHeight) && e.data.UUID){
+ var requestAs = false;
+ if (e.data.requestAs){
+ requestAs = e.data.requestAs;
+ }
+ session.requestResolution(e.data.UUID, targetWidth || 4096 , targetHeight || 2160 , false, requestAs); // this is fine.
+ }
+ ////////////////
+
+ if ("scale" in e.data) {
+ if (e.data.scale === false){
+ session.dynamicScale = true; // disable manual scaling
+ updateMixer();
+ var scale = false;
+ } else {
+ session.dynamicScale = false;
+ var scale = parseInt(e.data.scale) || 100;
+ }
- if ("target" in e.data) {
+ if (e.data.UUID){
+ session.sendRequest({scale:scale}, UUID);
+ } else if (e.data.target){
+ for (var i in session.rpcs) {
+ try {
+ if ("streamID" in session.rpcs[i]) {
+ if ("target" in e.data) {
+ if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
+ session.sendRequest({scale:scale}, i);
+ }
+ } else {
+ session.sendRequest({scale:scale}, i);
+ }
+ }
+ } catch (e) {
+ errorlog(e);
+ }
+ }
+ } else {
+ session.sendRequest({scale:scale});
+ }
+
+ }
+
+
+ if (("action" in e.data) && (e.data.action!="null")) { /////////////// reuse the Companion API
+ var resp = processMessage(e.data); // reuse the companion API
+ if (resp!==null){
+ log(resp);
+ parent.postMessage(resp, session.iframetarget);
+ }
+ } else if ("target" in e.data) {
log(e.data);
for (var i in session.rpcs) {
try {
if ("streamID" in session.rpcs[i]) {
if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) {
try {
+
if ("settings" in e.data) {
- for (const property in e.data.settings) {
- session.rpcs[i].videoElement[property] = e.data.settings[property];
- }
+ try{
+ for (const property in e.data.settings) {
+ try {
+ session.rpcs[i].videoElement[property] = e.data.settings[property];
+ } catch(e){}
+ }
+ } catch(e){}
}
if ("add" in e.data) {
- getById("gridlayout").appendChild(session.rpcs[i].videoElement);
+ try{
+ getById("gridlayout").appendChild(session.rpcs[i].videoElement);
+ } catch(e){warnlog(e);}
} else if ("remove" in e.data) {
try {
@@ -2928,7 +4942,16 @@ if (isIFrame) { // reduce CPU load if not needed.
session.rpcs[i].videoElement.parentNode.parentNode.removeChild(session.rpcs[i].videoElement.parentNode);
} catch (e) {}
}
- }
+ } else if ("replace" in e.data) { // should allow for a cleaner cut between two video streams.
+ try {
+ getById("gridlayout").appendChild(session.rpcs[i].videoElement);
+ getById("gridlayout").childNodes.forEach(ele=>{
+ if ((!ele.id) || (ele.id !== session.rpcs[i].videoElement.id)){
+ getById("gridlayout").removeChild(ele);
+ }
+ });
+ } catch(e){}
+ }
} catch (e) {
errorlog(e);
}
@@ -2940,13762 +4963,554 @@ if (isIFrame) { // reduce CPU load if not needed.
}
}
};
-}
+
+ if (isIFrame) { // reduce CPU load if not needed. //iframe API
+ window.onmessage = session.remoteInterfaceAPI;
+ }
-function setupIncomingVideoTracking(v, UUID){ // video element.
-
- if (session.directorUUID===UUID){
- v.muted=false;
- } else {
- v.muted = session.speakerMuted;
- }
-
- v.onpause = (event) => { // prevent things from pausing; human or other
- if (!((event.ctrlKey) || (event.metaKey) )){
- warnlog("Video paused; force it to play again");
- //return;
- //session.audioCtx.resume();
- //log("ctx resume");
-
- event.currentTarget.play().then(_ => {
- log("playing");
- }).catch(error => {
- warnlog("didnt play 1");
- });
-
- }
- }
-
- v.onplay = function(){
- try {
- var bigPlayButton = document.getElementById("bigPlayButton");
- if (bigPlayButton){
- bigPlayButton.parentNode.removeChild(bigPlayButton);
- }
- } catch(e){}
- if (session.pip){
- if (v.readyState >= 3){
- if (!(v.pip)){
- v.pip=true;
- toggleSystemPip(v, true);
- }
- }
- }
+ if (session.midiHotkeys || session.midiOut!==false) {
- }
-
- if (session.pip){
- v.onloadedmetadata = function(){
- if (!v.paused){
- if (!(v.pip)){
- v.pip=true;
- toggleSystemPip(v, true);
- }
- }
- }
- }
+ var script = document.createElement('script');
+ script.onload = function() {
+ WebMidi.enable().then(() =>{
-
- v.addEventListener('resize', (e) => {
- log("resize event");
- var aspectRatio = parseFloat(e.target.videoWidth/e.target.videoHeight);
- if (v.dataset.aspectRatio){
- if (aspectRatio != v.dataset.aspectRatio){
- setTimeout(function(){updateMixer();},1); // We don't want to run this on the first resize? just subsequent ones.
- }
- } else {
- log("ASPECT RATIO CHANGED");
- setTimeout(function(){updateMixer();},1);
- }
- v.dataset.aspectRatio = parseFloat(e.target.videoWidth/e.target.videoHeight);
- });
-
- v.volume = 1.0; // play audio automatically
- v.autoplay = true;
- v.controls = false;
- v.className += "tile";
- v.setAttribute("playsinline","");
- v.controlTimer = null;
-
- changeAudioOutputDevice(v); // if enabled, changes to desired output audio device.
-
- if (document.getElementById("mainmenu")){
- var m = getById("mainmenu");
- m.remove();
- }
-
- if (session.director){
- v.controls = true;
- var container = getById("videoContainer_"+UUID);
- v.disablePictureInPicture = false
- v.setAttribute("controls","controls")
- container.appendChild(v);
- session.requestRateLimit(session.directorViewBitrate,UUID); /// limit resolution for director
-
- if (session.beepToNotify) {
- playtone();
- }
-
- } else if (session.scene!==false){
- v.controls = false;
-
- if (session.view){ // specific video to be played
- v.style.display="block";
- } else if (session.scene===0){
- v.style.display="block";
- } else { // group scene I guess; needs to be added manually
- v.style.display="none";
- v.muted= true;
- }
-
- setTimeout(function(){updateMixer();},1);
- } else if (session.roomid!==false){
- if (session.cleanOutput){
- v.controls = false;
- } else if (window.obsstudio) {
- v.controls = false;
- } else {
- v.controls = true;
- }
- if ((session.roomid==="") && (session.bitrate)){
- // let's keep the default bitrates, since this isn't a real room and bitrates are specified.
- } else if (session.novideo !== false){
- if (session.novideo.includes(session.rpcs[UUID].streamID)){
- session.requestRateLimit(0,UUID);// limit resolution for guests see ln: 1804 in main.js also
- }
- } else {
- session.requestRateLimit(0,UUID);// limit resolution for guests see ln: 1804 in main.js also
- }
- setTimeout(function(){updateMixer();},1);
- } else {
- v.style.display="block";
- if (window.obsstudio) {
- v.controls = false;
- }
- setTimeout(function(){updateMixer();},1);
- }
-
-
- v.addEventListener('click', function(e) { // show stats of video if double clicked
- log("clicked");
- try {
- if ((e.ctrlKey)||(e.metaKey)){
- e.preventDefault();
- var uid = e.currentTarget.dataset.UUID;
- if ("stats" in session.rpcs[uid]){
-
- var [menu, innerMenu] = statsMenuCreator();
-
- printViewStats(innerMenu, session.rpcs[uid].stats, session.rpcs[uid].streamID );
-
- menu.interval = setInterval(printViewStats,3000, innerMenu, session.rpcs[uid].stats, session.rpcs[uid].streamID);
-
-
- }
- e.stopPropagation();
- return false;
- }
- } catch(e){errorlog(e);}
- });
-
- if (session.statsMenu){
- if ("stats" in session.rpcs[UUID]){
-
- if (getById("menuStatsBox")){
- clearInterval(getById("menuStatsBox").interval);
- getById("menuStatsBox").remove();
- }
-
- var [menu, innerMenu] = statsMenuCreator();
-
- printViewStats(innerMenu, session.rpcs[UUID].stats, session.rpcs[UUID].streamID );
-
- menu.interval = setInterval(printViewStats,3000, innerMenu, session.rpcs[UUID].stats, session.rpcs[UUID].streamID);
-
- }
- }
-
-
- v.touchTimeOut = null;
- v.touchLastTap = 0;
- v.touchCount = 0;
- v.addEventListener('touchend', function(event) {
- log("touched");
-
- document.ontouchup = null;
- document.onmouseup = null;
- document.onmousemove = null;
- document.ontouchmove = null;
-
- var currentTime = new Date().getTime();
- var tapLength = currentTime - v.touchLastTap;
- clearTimeout(v.touchTimeOut);
- if (tapLength < 500 && tapLength > 0) {
- ///
- log("double touched");
- v.touchCount+=1;
- event.preventDefault();
- if (v.touchCount<5){
- v.touchLastTap = currentTime;
- return false;
- }
- v.touchLastTap = 0;
- v.touchCount=0;
-
- log("double touched");
- var uid = event.currentTarget.dataset.UUID;
- if ("stats" in session.rpcs[uid]){
-
- var [menu, innerMenu] = statsMenuCreator();
-
- printViewStats(innerMenu, session.rpcs[uid].stats, session.rpcs[uid].streamID );
-
- menu.interval = setInterval(printViewStats,3000, innerMenu, session.rpcs[uid].stats, session.rpcs[uid].streamID);
-
-
- }
- event.stopPropagation();
- return false;
- //////
- } else {
- v.touchCount=1;
- v.touchTimeOut = setTimeout(function(vv) {
- clearTimeout(vv.touchTimeOut);
- vv.touchLastTap = 0;
- vv.touchCount=0;
- }, 5000, v);
- v.touchLastTap = currentTime;
- }
-
- });
-
-
- if (session.remote){
- v.addEventListener("wheel", session.remoteControl);
- }
-
- if (v.controls == false){
- v.addEventListener("click", function () {
- if (v.paused){
- log("PLAYING MANUALLY?");
- v.play().then(_ => {
- log("playing");
- }).catch(warnlog);
- }
- });
- if (session.nocursor==false){ // we do not want to show the controls. This is because MacOS + OBS does not work; so electron app needs this.
- if (!(session.cleanOutput)){
- if (!(window.obsstudio)){
- if (v.controlTimer){
- clearInterval(v.controlTimer);
- }
- v.controlTimer = setTimeout(showControlBar.bind(null,v),3000);
- //v.controlTimer = setTimeout(function (){v.controls=true;},3000); // 3 seconds before I enable the controls automatically. This way it doesn't auto appear during loading. 3s enough, right?
- }
- }
- }
- }
-
- setTimeout(session.processStats, 1000, UUID);
-
-}
-
-function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a giant function that runs when there are changes to screensize, video track statuses, etc.
- if (getById("subControlButtons").dragElement){
- if (parseInt(getById("subControlButtons").style.top) > 0){
- getById("subControlButtons").style.top = "0px";
- } else if (parseInt(getById("subControlButtons").style.top) < parseInt(50 - window.innerHeight) ){
- getById("subControlButtons").style.top = parseInt( 50 - window.innerHeight)+"px";
- }
- if (parseInt(getById("subControlButtons").style.left) < 0){
- getById("subControlButtons").style.left = "0px";
- } else if (parseInt(getById("subControlButtons").style.left) > parseInt( window.innerWidth - getById("subControlButtons").offsetWidth) ){
- getById("subControlButtons").style.left = parseInt( window.innerWidth -getById("subControlButtons").offsetWidth )+"px";
- }
- }
- if (session.director){return;}
- if (session.manual === true){return;}
- var playarea = getById("gridlayout");
- var header = getById("header");
-
- var hi = header.offsetHeight ;
- var w = window.innerWidth;
- var h = window.innerHeight - hi;
-
- if ( window.innerHeight<=700 ){
- if (document.getElementById("controlButtons")){
- var h = window.innerHeight - hi - document.getElementById("controlButtons").offsetHeight;
- } else {
- var h = window.innerHeight - hi;
- }
- }
-
- if (session.aspectratio){
- if (session.aspectratio==1){
- var arW = 9.0;
- var arH = 16.0;
- } else if (session.aspectratio==2){
- var arW = 1.0;
- var arH = 1.0;
- }
- } else {
- var arW = 16.0;
- var arH = 9.0;
- }
-
- var ww = w/arW;
- var hh = h/arH;
-
- var mediaPool = [];
- var mediaPool_invisible = [];
-
- if (session.videoElement){ // I, myself, exist
- if (session.videoElement.style.display!="none"){ // local feed
- if (session.minipreview && (session.infocus!==true)){
-
- session.videoElement.onclick = function(){
- if (session.infocus === true){
- session.infocus = false;
- } else {
- session.infocus = true;
- log("session: myself");
- }
- setTimeout(()=>updateMixer(),10);
- };
-
- } else {
- if (session.order!==false){
- session.videoElement.order=session.order;
- } else {
- session.videoElement.order=0;
- }
- if (session.activeSpeaker && (!session.activelySpeaking)){
- mediaPool_invisible.push(session.videoElement);
- } else {
- mediaPool.push(session.videoElement);
- }
- }
- }
- }
-
- if (session.screenShareElement){ // I, myself, exist
- if (session.screenShareElement.style.display!="none"){ // local feed
- if (session.order!==false){
- session.screenShareElement.order=session.order;
- } else {
- session.screenShareElement.order=0;
- }
-
- if (session.infocus!==false){
- mediaPool_invisible.push(session.screenShareElement);
- } else if (session.activeSpeaker && (!session.activelySpeaking)){
- mediaPool_invisible.push(session.screenShareElement);
- } else {
- mediaPool.push(session.screenShareElement);
- }
- }
- }
-
- if (session.iframeEle){ // I, myself, exist
- if (session.iframeEle.style.display!="none"){ // local feed
- if (session.order!==false){
- session.iframeEle.order=session.order;
- } else {
- session.iframeEle.order=0;
- }
- if (session.activeSpeaker && (!session.activelySpeaking)){
- mediaPool_invisible.push(session.iframeEle);
- } else {
- mediaPool.push(session.iframeEle);
- }
- }
- }
-
-
- if ((session.infocus) && (session.infocus in session.rpcs)){ // remote guest being full screened; infocus == UUID
- log(session.infocus+" set fullscreen");
- mediaPool = []; // remove myself from fullscreen
- for (var j in session.rpcs){
- if (j != session.infocus){
- session.requestRateLimit(0, j); // disable the video of non-fullscreen videos
- try {
- if (session.rpcs[j].videoElement.style.display!="none"){ // Add it if not hidden
- mediaPool_invisible.push(session.rpcs[j].videoElement);
- }
- } catch(e){}
- } else { // in focus video
- ////////
- try {
- if (session.rpcs[j].order!==false){
- session.rpcs[j].videoElement.order=session.rpcs[j].order;
- } else {
- session.rpcs[j].videoElement.order=0;
- }
- ///////////
- if (session.activeSpeaker && (!session.rpcs[j].activelySpeaking)){
- mediaPool_invisible.push(session.rpcs[j].videoElement);
- } else {
- mediaPool.push(session.rpcs[j].videoElement);
- }
-
- session.rpcs[j].videoElement.style.visibility = "visible";
- if ((session.rpcs[j].targetBandwidth!==-1) && (session.rpcs[j].targetBandwidth";
- button.title = "Show all active videos togethers";
- } else if (mediaPool.length>1){
- button.innerHTML = "
";
- button.title = "Enlarge video and increase its clarity";
- } else {
- button.style.visibility = "hidden";
- }
- button.style.transition = "opacity 0.3s"
- button.style.width ="4vh";
- button.style.height = "4vh";
- button.style.maxWidth ="30px";
- button.style.maxHeight = "30px";
- button.style.minWidth ="15px";
- button.style.minHeight = "15px";
- button.style.position = "absolute";
- //button.style.display="none";
- //button.style.opacity="10%";
- button.style.zIndex="3";
- button.style.right = "4vh";//(Math.ceil(w/rw) -30 - 30 + offsetx+Math.floor(((i%rw)+0)*w/rw))+"px";
- button.style.top = "4vh";//( offsety + 30 + Math.floor((Math.floor(i/rw)+0)*h/rh + hi))+"px";
- button.style.color = "white";
- button.style.cursor = "pointer";
-
-
- container.appendChild(button);
- if (vid.id == "videosource"){
- button.onclick = function(){
- var target = event.currentTarget;
- log(target);
- if (session.infocus === true){
- session.infocus = false;
- //target.childNodes[0].className = 'las la-arrows-alt';
- } else {
- session.infocus = true;
- log("session: myself");
- //target.childNodes[0].className = 'las la-compress';
- }
- setTimeout(()=>updateMixer(),10);
- };
-
- } else {
- button.dataset.UUID = vid.dataset.UUID;
- button.onclick = function(event){
- var target = event.currentTarget;
- log("fullscreen");
- log(target);
- if (session.infocus === target.dataset.UUID){
- //target.childNodes[0].className = 'las la-arrows-alt';
- session.infocus = false;
- } else {
- //target.childNodes[0].className = 'las la-compress';
- session.infocus = target.dataset.UUID;
- //log("session:"+target.dataset.UUID);
- }
- setTimeout(()=>updateMixer(),10);
- };
-
- }
- vid.onclick = function(){
- button.style.display="block";
- container.style.backgroundColor= "#4444";
- button.style.opacity="100%";
- };
- button.onmouseenter = function(){
- button.style.display="block";
- container.style.backgroundColor= "#4444";
- setTimeout(function(button){button.style.opacity="100%";},0,button);
-
- };
- container.onmouseenter = function(){
- button.style.display="block";
- container.style.backgroundColor= "#4444";
- setTimeout(function(button){button.style.opacity="100%";},0,button);
- };
- container.onmouseleave = function(){
- button.style.display="none";
- container.style.backgroundColor= null;
- button.style.opacity="10%";
- };
- }
- }
- i+=1;
- });
-}
-
-
-var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
-var eventer = window[eventMethod];
-var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
-eventer(messageEvent, function(e) { // this listens for child IFRAMES.
- if ("action" in e.data) {
- if (e.data.action == "screen-share-ended") {
- if (session.screenShareElement) {
- if (e.source == session.screenShareElement.contentWindow) { // reject messages send from other iframes
- warnlog(e);
- session.screenShareElement.contentWindow.postMessage({
- "close": true
- }, '*');
- session.screenShareElement.parentNode.removeChild(session.screenShareElement);
- session.screenShareElement = false;
- updateMixer();
- getById("screenshare2button").classList.add("float");
- getById("screenshare2button").classList.remove("float2");
- }
- }
- }
- }
-});
-
-
-function requestKeyframeScene(ele) {
- var UUID = ele.dataset.UUID;
- if (ele.dataset.value == 1) {
- } else {
- ele.dataset.value = 1;
- ele.classList.add("pressed");
- session.requestKeyframe(UUID, true);
- setTimeout(function(el){
- el.dataset.value = 0;
- el.classList.remove("pressed");
- }, 1000, ele)
- }
-}
-
-function pokeIframeAPI(action, value = null, UUID = null) {
- if (!isIFrame){return;}
- try {
- var data = {};
-
- data.action = action;
-
- if (value !== null) {
- data.value = value;
- }
- if (UUID !== null) {
- data.UUID = UUID;
- }
-
- if (isIFrame) {
- parent.postMessage(data, "*");
- }
- } catch (e) {
- errorlog(e);
- }
-}
-
-function jumptoroom(event = null) {
-
- if (event) {
- if (event.which !== 13) {
- return;
- }
- }
-
- var arr = window.location.href.split('?');
- var roomname = getById("joinroomID").value;
- roomname = sanitizeRoomName(roomname);
- if (roomname.length) {
-
- var passStr = "";
- var pass = prompt("Enter a password if provided, otherwise just click cancel"); //sanitizePassword(session.password);
- if (pass && pass.length) {
- session.password = sanitizePassword(pass);
- passStr = "&password=" + session.password;
- } else {
- session.password = false;
- }
-
- if (arr.length > 1 && arr[1] !== '') {
- window.location += "&room=" + roomname + passStr;
- } else {
- window.location += "?room=" + roomname + passStr;
- }
- }
-}
-
-function sleep(ms = 0) {
- return new Promise(r => setTimeout(r, ms)); // LOLz!
-}
-
-////////// Canvas Effects ///////////////
-
-function drawFrameMirrored() {
- session.canvasCtx.save();
- session.canvasCtx.scale(-1, 1);
- session.canvasCtx.drawImage(session.canvasSource, 0, 0, session.canvas.width * -1, session.canvas.height);
- session.canvasCtx.restore();
-}
-
-function setupCanvas() {
- if (session.canvas === null) {
- warnlog("SETUP CANVAS");
- session.canvas = document.createElement("canvas");
- session.canvas.width = 512;
- session.canvas.height = 288;
- session.canvasCtx = session.canvas.getContext('2d', {alpha: false, desynchronized: true});
- //session.canvasCtx.width=288;
- //session.canvasCtx.height=720;
- session.canvasCtx.fillStyle = "blue";
- session.canvasCtx.fillRect(0, 0, 512, 288);
- session.canvasSource = document.createElement("video");
- session.canvasSource.width=512;
- session.canvasSource.height=288;
- session.canvasSource.autoplay = true;
- session.canvasSource.srcObject = new MediaStream();
- }
-}
-
-function applyEffects(track, stream) {
-
- setupCanvas();
-
- session.canvasSource.srcObject.addTrack(track, stream);
- session.canvasSource.width = track.getSettings().width || 1280;
- session.canvasSource.height = track.getSettings().height || 720;
- session.canvas.width = track.getSettings().width || 1280;
- session.canvas.height = track.getSettings().height || 720;
-
- var audioTracks = session.streamSrc.getAudioTracks();
- session.streamSrc = session.canvas.captureStream();
- audioTracks.forEach(function(trk) {
- session.streamSrc.addTrack(trk);
- });
- session.videoElement.srcObject = session.streamSrc;
-
- if (session.effects == 1) { // auto align face
- setTimeout(function() {
- drawFace();
- }, 100);
- } else if (session.effects == 2) { // mirror video at a canvas level
- var drawRate = parseInt(1000 / track.getSettings().frameRate) + 1;
- if (session.canvasInterval !== null) {
- clearInterval(session.canvasInterval);
- }
- session.canvasInterval = setInterval(function() {
- drawFrameMirrored();
- }, drawRate);
- } else if ((session.effects == 3) || (session.effects == 4) || (session.effects == 5)){ // blur & greenscreen (low and high)
- TFLiteWorker();
- } else if (session.effects == 6){
- session.canvasSource.onloadeddata = mainMeshMask;
- }
- return session.streamSrc.getVideoTracks()[0];
-}
-
-function dataURItoArraybuffer(dataURI) {
- var byteString = atob(dataURI.split(',')[1]);
- var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
- var ab = new ArrayBuffer(byteString.length);
- var ia = new Uint8Array(ab);
- for (var i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
- return ab;
-}
-
-
-var makeImagesActive = null;
-var makeImagesTimeout = null;
-async function makeImages(startup=false){
- if (makeImagesActive===true){return;}
- if (!session.videoElement){return;}
- if (session.videoMuted){return;}
- if (!session.videoElement.srcObject.getVideoTracks().length){return;}
-
- if (makeImagesActive===null){
- makeImagesActive=true;
- session.webPcanvas = document.createElement("canvas");
-
- var width = 480;
- var height = 270;
- var timeout = 60;
-
- if (session.webPquality===0){
- width = 1920;
- height = 1080;
- timeout = 15;
- } else if (session.webPquality===1){
- width = 1280;
- height = 720;
- timeout = 15;
- } else if (session.webPquality===2){
- width = 640;
- height = 360;
- timeout = 15;
- } else if (session.webPquality===3){
- width = 480;
- height = 270;
- timeout = 15;
- } else if (session.webPquality===4){
- width = 480;
- height = 270;
- timeout = 30;
- } else {
- width = 480;
- height = 270;
- timeout = 60;
- }
- session.webPcanvas.width = width;
- session.webPcanvas.height = height;
- session.webPcanvas.timeout = timeout;
- session.webPcanvasCtx = session.webPcanvas.getContext('2d', {alpha: false, desynchronized: true});
- session.webPcanvasCtx.fillStyle = "black";
- session.webPcanvasCtx.fillRect(0, 0, width, height);
- } else {
- makeImagesActive=true;
- }
-
- if (startup){
- var exit = true;
- for (var i in session.pcs){
- if (session.pcs[i].allowBroadcast){ // just for safety, to avoid a race condition, double check that it's still not active.
- exit = false;
- }
- }
- if (exit){
- makeImagesActive=false;
- return;
- }
- }
-
- try{
- session.webPcanvasCtx.drawImage(session.videoElement, 0, 0, session.webPcanvas.width, session.webPcanvas.height);
- const webp = session.webPcanvas.toDataURL("image/webp",0.6); // seems like a good balance here
- const arrayBuffer = dataURItoArraybuffer(webp);
- var broadcasting = false;
- for (var i in session.pcs){
- try{
- if (session.pcs[i].allowBroadcast){ // only publish to those seeking this stream
- broadcasting = true;
- if (!session.pcs[i].sendChannel.bufferedAmount){ // only send when the buffer is free. don't clog it up or wait.
- session.pcs[i].sendChannel.send(arrayBuffer);
- }
- }
- } catch(e){}
- }
- } catch(e){
- warnlog(e);
- makeImagesActive=false;
- return;
- }
- makeImagesActive=false;
- if (broadcasting){ // cancel broadcasting since now one is active.
- clearTimeout(makeImagesTimeout);
- makeImagesTimeout = setTimeout(function(){
- window.requestAnimationFrame(makeImages);
- },session.webPcanvas.timeout);
- } else {
- for (var i in session.pcs){
- if (session.pcs[i].allowBroadcast){ // just for safety, to avoid a race condition, double check that it's still not active.
- clearTimeout(makeImagesTimeout);
- makeImagesTimeout = setTimeout(function(){
- window.requestAnimationFrame(makeImages);
- },session.webPcanvas.timeout);
- return;
- }
- }
- log("Stopping webP broadcast.");
- }
-}
-
-var updateUserListTimeout=null
-var updateUserListActive = false;
-function updateUserList(){
- if ((session.showList!==true) && (session.cleanOutput || (session.scene!==false) || !session.roomid || session.director || (session.showList===false))){return;}
- clearInterval(updateUserListTimeout);
- updateUserListTimeout = setTimeout(function(){
- if (updateUserListActive){return;}
- updateUserListActive=true;
- try {
- var added = false;
- getById("userList").innerHTML = "";
-
- for (var UUID in session.rpcs){
- var track = false;
- if (session.rpcs[UUID].streamSrc && session.rpcs[UUID].streamSrc.getVideoTracks().length){
- var tracks = session.rpcs[UUID].streamSrc.getVideoTracks().forEach(function(trk){
- //if (!trk.muted){
- track=true;
- //}
- });
- }
- if (((session.rpcs[UUID].videoMuted || (track==false && !session.rpcs[UUID].imageElement)) && !session.rpcs[UUID].canvas) || ( session.infocus && session.infocus!==UUID )){
-
- if (UUID === session.directorUUID){
- if (!session.rpcs[UUID].streamSrc){ // director not active yet, so we won't bother showing it.
- continue;
- }
- }
-
- var insert = document.createElement("div");
- if (session.rpcs[UUID].label){
- insert.innerText = session.rpcs[UUID].label + "";
- } else if (session.directorUUID === UUID){
- insert.innerText = "Director";
- } else {
- insert.innerText = "Unknown User";
- }
- getById("userList").appendChild(insert);
-
- if (session.rpcs[UUID].remoteMuteState || !(session.rpcs[UUID].streamSrc)){
- var muteInsert = document.createElement("div");
- muteInsert.className = "video-mute-state-userlist";
- muteInsert.innerHTML = '';
- insert.appendChild(muteInsert);
- } else if (session.rpcs[UUID].voiceMeter){
- insert.appendChild(session.rpcs[UUID].voiceMeter);
- }
- //getById("userList").innerHTML += "
";
- added=true;
- }
- }
-
- if (!added){
- getById("connectUsers").style.display = "none";
- } else {
- getById("connectUsers").style.display = "block";
- }
- } catch(e){}
- updateUserListActive=false;
- },200);
-}
-
-var LaunchTFWorkerCallback = false;
-function TFLiteWorker(){
- if (session.tfliteModule==false){
- LaunchTFWorkerCallback=true
- return;
- }
- if (TFLITELOADING){LaunchTFWorkerCallback=true;return;}
- LaunchTFWorkerCallback=false;
-
- const segmentationWidth = 256;
- const segmentationHeight = 144;
- const segmentationPixelCount = segmentationWidth * segmentationHeight;
- const inputMemoryOffset = session.tfliteModule._getInputMemoryOffset() / 4;
- const outputMemoryOffset = session.tfliteModule._getOutputMemoryOffset() / 4;
- const segmentationMask = new ImageData(segmentationWidth, segmentationHeight);
- const segmentationMaskCanvas = document.createElement('canvas');
- segmentationMaskCanvas.width = segmentationWidth;
- segmentationMaskCanvas.height = segmentationHeight;
- const segmentationMaskCtx = segmentationMaskCanvas.getContext('2d');
-
- session.tfliteModule.img = document.createElement("img");
- session.tfliteModule.img.onload = function(){
- URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
- session.tfliteModule.img.ready = true;
- }
- session.tfliteModule.img.src = "./media/bg_sample.jpg";
- session.tfliteModule.img.ready = false;
-
- console.log('Starting Loop');
-
-
- function process(){
- if (session.tfliteModule.activelyProcessing){return;}
- session.tfliteModule.activelyProcessing=true;
- try{
- segmentationMaskCtx.drawImage(
- session.canvasSource,
- 0,
- 0,
- session.canvasSource.width,
- session.canvasSource.height,
- 0,
- 0,
- segmentationWidth,
- segmentationHeight
- )
-
- const imageData = segmentationMaskCtx.getImageData(
- 0,
- 0,
- segmentationWidth,
- segmentationHeight
- );
-
- for (let i = 0; i < segmentationPixelCount; i++) {
- session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
- session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
- session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
- }
-
- session.tfliteModule._runInference();
-
- for (let i = 0; i < segmentationPixelCount; i++) {
- const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
- const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
- const shift = Math.max(background, person);
- const backgroundExp = Math.exp(background - shift);
- const personExp = Math.exp(person - shift);
- segmentationMask.data[i * 4 + 3] = (255 * personExp) / (backgroundExp + personExp); // softmax
- }
- segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
-
- session.canvasCtx.globalCompositeOperation = 'copy';
- session.canvasCtx.filter = 'blur(4px)';
- session.canvasCtx.drawImage(
- segmentationMaskCanvas,
- 0,
- 0,
- segmentationWidth,
- segmentationHeight,
- 0,
- 0,
- session.canvasSource.width,
- session.canvasSource.height
- )
-
- session.canvasCtx.globalCompositeOperation = 'source-in';
- session.canvasCtx.filter = 'none';
- session.canvasCtx.drawImage(session.canvasSource, 0, 0);
-
- session.canvasCtx.globalCompositeOperation = 'destination-over';
- if (session.effects=="4"){ // greenscreen
- session.canvasCtx.filter = 'none';
- session.canvasCtx.fillStyle = "#0F0";
- session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
- } else if (session.effects=="5"){
- session.canvasCtx.filter = 'none';
- if (session.tfliteModule.img.ready){
- session.canvasCtx.drawImage(session.tfliteModule.img, 0, 0, session.canvas.width, session.canvas.height);
- }
- } else if (session.effects=="3"){ // BLUR
- session.canvasCtx.filter = 'blur(4px)'; // Does not work on Safari
- session.canvasCtx.drawImage(session.canvasSource, 0, 0);
- } else {
- session.tfliteModule.activelyProcessing=false;
- return;
- }
- } catch (e){
- errorlog(e);
- session.tfliteModule.activelyProcessing=false;
- return;
- }
- session.tfliteModule.activelyProcessing=false;
- window.requestAnimationFrame(process);
- }
- process();
-}
-
-
-function mainMeshMask() {
- if (model == false){
- setTimeout(function(){mainMeshMask();},1000);
- return;
- }
- function heatMapColorforValue(value){
- var h = parseInt((1.0 - value) * 240);
- if (h<0){h=0;}
- if (h>240){h=240;}
- return "hsl(" + h + ", 100%, 50%)";
- }
- async function innerLoop(){
- const predictions = await model.estimateFaces({
- input: session.canvasSource
- });
- if (predictions.length > 0) {
- for (let j = 0; j < predictions.length; j++) {
- const fp = predictions[j].annotations;
- session.canvasCtx.fillStyle = "#000000";
- session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
- const keypoints = predictions[j].scaledMesh
- for (let i = 0; i < keypoints.length; i++) {
- const [x, y, z] = keypoints[i];
- session.canvasCtx.fillStyle = heatMapColorforValue((z+40)/60);
- session.canvasCtx.fillRect(parseInt(x), parseInt(y), 5, 5);
- }
- }
- }
- window.requestAnimationFrame(innerLoop);
- }
- innerLoop();
-}
-
-function drawFace() {
- var faceAlignment = (function() {
- var vid = session.canvasSource;
-
- var canvas = session.canvas;
- var ctx = session.canvasCtx;
-
- var canvas_tmp = document.createElement("canvas");
- var ctx_tmp = canvas_tmp.getContext('2d');
-
- //var stream = canvas.captureStream(30);
-
- var image = new Image();
- var zoom = 10;
- var scale = 1;
- var lastFace = {};
-
- var w = vid.videoWidth;
- var h = vid.videoHeight;
- var x = vid.videoWidth / 2;
- var y = vid.videoHeight / 2;
-
- lastFace.x = vid.videoWidth / 2;
- lastFace.y = vid.videoHeight / 2;
- lastFace.w = vid.videoWidth;
- lastFace.h = vid.videoHeight;
- var yoffset = 0;
-
- if (window.FaceDetector == undefined) {
- //console.error('Face Detection not supported');
- var faceDetector = false;
- } else {
- var faceDetector = new FaceDetector();
- //console.log('FaceD Loaded');
- setTimeout(function() {
- detect();
- }, 300);
- setTimeout(function() {
- draw();
- }, 33);
- }
-
- canvas.height = vid.videoHeight;
- canvas.width = vid.videoWidth;
- canvas_tmp.height = vid.videoHeight;
- canvas_tmp.width = vid.videoWidth;
- image.src = canvas_tmp.toDataURL();
- scale = canvas.width / image.width;
- lastFace.x = 0;
- lastFace.y = 0;
- lastFace.w = 1280 / 3 / 16 * zoom;
- lastFace.h = 720 / 3 / 9 * zoom;
-
- w = 1280 / 5;
- h = 720 / 5;
- x = 1280 / 2;
- y = 720 / 2 - w * 9 / zoom / 2;
-
- async function detect() {
-
- if (session.effects !== 1){return;}
-
- ctx_tmp.drawImage(vid, 0, 0, vid.videoWidth, vid.videoHeight);
- image.src = canvas_tmp.toDataURL();
- await faceDetector.detect(image).then(faces => {
-
- if (faces.length === 0) {
- log("NO FACES");
- setTimeout(function() {
- detect();
- }, 10);
- return;
- }
- for (let face of faces) {
- lastFace.x = (face.boundingBox.x + lastFace.x) / 2 || face.boundingBox.x;
- lastFace.y = (face.boundingBox.y + lastFace.y) / 2 || face.boundingBox.y;
- lastFace.w = (face.boundingBox.width + lastFace.w) / 2 || face.boundingBox.width;
- lastFace.h = (face.boundingBox.height + lastFace.h) / 2 || face.boundingBox.height;
- }
-
- setTimeout(function() {
- detect();
- }, 300);
- }).catch((e) => {
- console.error("Boo, Face Detection failed: " + e);
- });
-
- }
-
- function draw() {
-
- if (session.effects !== 1){return;}
-
- canvas.height = vid.videoHeight;
- canvas.width = vid.videoWidth;
-
- if (lastFace.w - w < 0.15 * lastFace.w) {
- w = w * 0.999 + 0.001 * lastFace.w;
- }
- if (lastFace.h - h < 0.15 * lastFace.h) {
- h = h * 0.999 + 0.001 * lastFace.h;
- }
- if (Math.abs(x - (lastFace.x + lastFace.w / 2)) > 0.15 * (lastFace.x + lastFace.w / 2.0)) {
- x = x * 0.999 + 0.001 * (lastFace.x + lastFace.w / 2.0);
- }
- if (Math.abs(y - (lastFace.y + lastFace.h / 2)) > 0.15 * (lastFace.y + lastFace.h / 2.0)) {
- y = y * 0.999 + 0.001 * (lastFace.y + lastFace.h / 2.0);
- }
-
- yoffset = w * 9 / zoom / 2;
-
- var yyy = y - w * 9 / zoom - yoffset;
- var hhh = w * 3 * 9 / zoom;
- var www = w * 3 * 16 / zoom;
- var xxx = x - w * 16 / zoom * 1.5;
-
- if (www + xxx < 1280) {
- xxx = 1280 - www;
- }
- if (hhh + yyy < 720) {
- yyy = 720 - hhh;
- }
-
- if (www + xxx > 1280) {
- xxx = 1280 - www;
- }
- if (hhh + yyy > 720) {
- yyy = 720 - hhh;
- }
-
- if (yyy < 0) {
- yyy = 0;
- }
- if (xxx < 0) {
- xxx = 0;
- }
-
- ctx.drawImage(vid, xxx, yyy, www, hhh, 0, 0, vid.videoWidth, vid.videoHeight);
-
- setTimeout(function() {
- draw();
- }, 30);
- }
- })();
-}
-
-
-//////// END CANVAS EFFECTS ///////////////////
-
-
-if (urlParams.has('permaid') || urlParams.has('push')) {
- session.permaid = urlParams.get('push') || urlParams.get('permaid');
-
- if (session.permaid) {
- session.streamID = sanitizeStreamID(session.permaid);
- } else {
- session.permaid = null;
- }
-
- if (urlParams.has('permaid')) {
- updateURL("permaid=" + session.streamID, true, false); // I'm not deleting the permaID first tho...
- } else {
- updateURL("push=" + session.streamID, true, false); // I'm not deleting the permaID first tho...
- }
-
- if (urlParams.has('director') || urlParams.has('dir')) { // if I do a short form of this, it will cause duplications in the code elsewhere.
- //var director_room_input = urlParams.get('director');
- //director_room_input = sanitizeRoomName(director_room_input);
- //createRoom(director_room_input);
- session.permaid = false; // used to avoid a trigger later on.
- } else {
- getById("container-1").className = 'column columnfade advanced';
- getById("container-4").className = 'column columnfade advanced';
- getById("dropButton").className = 'column columnfade advanced';
-
- getById("info").innerHTML = "";
- if (session.videoDevice === 0) {
- getById("add_camera").innerHTML = "Share your Microphone";
- miniTranslate(getById("add_camera"), "share-your-mic");
- } else {
- getById("add_camera").innerHTML = "Share your Camera";
- miniTranslate(getById("add_camera"), "share-your-camera");
- }
- getById("add_screen").innerHTML = "Share your Screen";
- miniTranslate(getById("add_screen"), "share-your-screen");
-
- getById("passwordRoom").value = "";
- getById("videoname1").value = "";
- getById("dirroomid").innerHTML = "";
- getById("roomid").innerHTML = "";
-
- getById("mainmenu").style.alignSelf = "center";
- getById("mainmenu").classList.add("mainmenuclass");
- getById("header").style.alignSelf = "center";
-
- if ((iOS) || (iPad)) {
- getById("header").style.display = "none"; // just trying to free up space.
- }
-
- if (session.webcamonly == true) { // mobile or manual flag 'webcam' pflag set
- getById("head1").innerHTML = '- Please accept any camera permissions';
- } else {
- getById("head1").innerHTML = '
- Please select which you wish to share';
- }
- }
-}
-
-if ((session.roomid) || (urlParams.has('roomid')) || (urlParams.has('r')) || (urlParams.has('room')) || (filename) || (session.permaid !== false)) {
-
- var roomid = "";
- if (filename) {
- roomid = filename;
- } else if (urlParams.has('room')) {
- roomid = urlParams.get('room');
- } else if (urlParams.has('roomid')) {
- roomid = urlParams.get('roomid');
- } else if (urlParams.has('r')) {
- roomid = urlParams.get('r');
- } else if (session.roomid) {
- roomid = session.roomid;
- }
-
- session.roomid = sanitizeRoomName(roomid);
-
- if (!(session.cleanOutput)) {
- if (session.roomid === "test") {
- if (session.password === session.defaultPassword) {
- var testRoomResponse = confirm("The room name 'test' is very commonly used and may not be secure.\n\nAre you sure you wish to proceed?");
- if (testRoomResponse == false) {
- hangup();
- throw new Error("User requested to not enter room 'room'.");
- }
- }
- }
- }
-
- if (session.audioDevice === false && session.outputDevice === false) {
- getById("headphonesDiv2").style.display = "inline-block";
- getById("headphonesDiv").style.display = "inline-block";
- }
- getById("addPasswordBasic").style.display = "none";
-
- getById("info").innerHTML = "";
- getById("info").style.color = "#CCC";
- getById("videoname1").value = session.roomid;
- getById("dirroomid").innerText = session.roomid;
- getById("roomid").innerText = session.roomid;
- getById("container-1").className = 'column columnfade advanced';
- getById("container-4").className = 'column columnfade advanced';
- getById("container-7").style.display = 'none';
- getById("container-8").style.display = 'none';
- getById("mainmenu").style.alignSelf = "center";
- getById("mainmenu").classList.add("mainmenuclass");
- getById("header").style.alignSelf = "center";
-
- if (session.webcamonly == true) { // mobile or manual flag 'webcam' pflag set
- getById("head1").innerHTML = '';
- } else {
- getById("head1").innerHTML = 'Please select an option to join.';
- }
-
- if (session.roomid.length > 0) {
- if (session.videoDevice === 0) {
- if (session.audioDevice === 0) {
- getById("add_camera").innerHTML = "Join room";
- miniTranslate(getById("add_camera"), "join-room");
- } else {
- getById("add_camera").innerHTML = "Join room with Microphone";
- miniTranslate(getById("add_camera"), "join-room-with-mic");
- }
- } else {
- getById("add_camera").innerHTML = "Join Room with Camera";
- miniTranslate(getById("add_camera"), "join-room-with-camera");
- }
- getById("add_screen").innerHTML = "Screenshare with Room";
- miniTranslate(getById("add_screen"), "share-screen-with-room");
- } else {
- if (session.videoDevice === 0) {
- getById("add_camera").innerHTML = "Share your Microphone";
- miniTranslate(getById("add_camera"), "share-your-mic");
- } else {
- getById("add_camera").innerHTML = "Share your Camera";
- miniTranslate(getById("add_camera"), "share-your-camera");
- }
- getById("add_screen").innerHTML = "Share your Screen";
- miniTranslate(getById("add_screen"), "share-your-screen");
- }
- getById("head3").className = 'advanced';
-
- if (session.scene !== false) {
- getById("container-4").className = 'column columnfade';
- getById("container-3").className = 'column columnfade';
- getById("container-2").className = 'column columnfade';
- getById("container-1").className = 'column columnfade';
- getById("header").className = 'advanced';
- getById("info").className = 'advanced';
- getById("head1").className = 'advanced';
- getById("head2").className = 'advanced';
- getById("mainmenu").style.display = "none";
- getById("translateButton").style.display = "none";
- log("Update Mixer Event on REsize SET");
- window.addEventListener("resize", updateMixer);
- window.addEventListener("orientationchange", function() {
- setTimeout(updateMixer, 200);
- });
- joinRoom(session.roomid); // this is a scene, so we want high resolutions
- getById("main").style.overflow = "hidden";
- } else {
- if ((session.permaid === null) && (session.roomid == "")) {
- if (!(session.cleanOutput)) {
- getById("head3").className = '';
- }
- }
- }
-
-} else if (urlParams.has('director') || urlParams.has('dir')) { // if I do a short form of this, it will cause duplications in the code elsewhere.
- if (directorLanding == false) {
- var director_room_input = urlParams.get('director') || urlParams.get('dir');
- director_room_input = sanitizeRoomName(director_room_input);
- log("director_room_input:" + director_room_input);
- createRoom(director_room_input);
- }
-} else if ((session.view) && (session.permaid === false)) {
- //if (!session.activeSpeaker){
- session.audioMeterGuest = false;
- //}
- if (session.audioEffects === null) {
- session.audioEffects = false;
- }
- log("Update Mixer Event on REsize SET");
- getById("translateButton").style.display = "none";
- window.addEventListener("resize", updateMixer);
- window.addEventListener("orientationchange", function() {
- setTimeout(updateMixer, 200);
- });
- getById("main").style.overflow = "hidden";
-}
-
-if (session.audioEffects === null) {
- session.audioEffects = true;
-}
-
-if (session.audioEffects) {
- getById("channelGroup1").style.display = "block";
- getById("channelGroup2").style.display = "block";
-}
-
-if (urlParams.has('hidemenu') || urlParams.has('hm')) { // needs to happen the room and permaid applications
- getById("mainmenu").style.display = "none";
- getById("header").style.display = "none";
- getById("mainmenu").style.opacity = 0;
- getById("header").style.opacity = 0;
-}
-
-if (urlParams.has('hideheader') || urlParams.has('noheader') || urlParams.has('hh')) { // needs to happen the room and permaid applications
- getById("header").style.display = "none";
- getById("header").style.opacity = 0;
-}
-
-function checkConnection() {
- if (session.ws === null) {
- return;
- }
- if (document.getElementById("qos")) { // true or false; null might cause problems?
- if ((session.ws) && (session.ws.readyState === WebSocket.OPEN)) {
- getById("qos").style.color = "white";
- } else {
- getById("qos").style.color = "red";
- }
- }
-}
-setInterval(function() {
- checkConnection();
-}, 5000);
-
-
-function remoteStats(msg){
- if (session.director){
- var output = "";
- var size = 0;
- for (var key in msg.remoteStats) {
- if (msg.remoteStats.hasOwnProperty(key)) size++;
- }
- output += "Total Viewers: "+size;
- for (var uuid in msg.remoteStats){
- if ("scene" in msg.remoteStats[uuid] && msg.remoteStats[uuid].scene !== false){
- output+="
scene: "+msg.remoteStats[uuid].scene;
- if ("video_bitrate_kbps" in msg.remoteStats[uuid]){
- output+="
video_bitrate_kbps: "+msg.remoteStats[uuid].video_bitrate_kbps;
- }
- if ("audio_bitrate_kbps" in msg.remoteStats[uuid]){
- output+="
audio_bitrate_kbps: "+msg.remoteStats[uuid].audio_bitrate_kbps;
- }
- if (msg.remoteStats[uuid].resolution){
- output+="
resolution: "+msg.remoteStats[uuid].resolution;
- }
- if (msg.remoteStats[uuid].video_encoder){
- output+="
video_encoder: "+msg.remoteStats[uuid].video_encoder;
- }
- if ("scaleFactor" in msg.remoteStats[uuid]){
- output+="
scaleFactor: "+msg.remoteStats[uuid].scaleFactor;
- }
- if ("nacks_per_second" in msg.remoteStats[uuid]){
- output+="
nacks_per_second: "+msg.remoteStats[uuid].nacks_per_second;
- }
- if (msg.remoteStats[uuid].retransmitted_kbps){
- output+="
quality_limitation_reason: "+msg.remoteStats[uuid].retransmitted_kbps;
- }
- if (msg.remoteStats[uuid].quality_limitation_reason){
- output+="
quality_limitation_reason: "+msg.remoteStats[uuid].quality_limitation_reason;
- }
- }
- }
- warnUser(output);
- };
- if (isIFrame){
- parent.postMessage({"remoteStats": msg.remoteStats }, "*");
- }
-}
-
-
-function printViewStats(menu, statsObj, streamID) { // Stats for viewing a remote video
- var scrollLeft = menu.scrollLeft;
- var scrollTop = menu.scrollTop;
- //menu.innerHTML="rae:"+session.audioEffects+ ", lae:"+ !session.disableWebAudio;
- menu.innerHTML = "StreamID: " + streamID + "
";
- menu.innerHTML += printValues(statsObj);
- menu.scrollTop = scrollTop;
- menu.scrollLeft = scrollLeft;
-
-}
-
-function printValues(obj) { // see: printViewStats
- var out = "";
- for (var key in obj) {
- if (typeof obj[key] === "object") {
- if (obj[key] != null) {
- var tmp = key;
- tmp = sanitizeChat((tmp));
- out += "" + tmp + "
(Stereo-mode)";
- if (value == 3) {
- value = "3 (outbound hi-fi)
Use Headphones";
- } else if (value == 1) {
- value = "1 (in & out hi-fi)
Use Headphones";
- } else if (value == 2) {
- value = "3 (inbound hi-fi)";
- } else if (value == 4) {
- value = "3 (multichannel)
Use Headphones";
- } else if (value == 5) {
- value = "5 (auto-mode)
Use Headphones";
- }
- }
- else if (value === false) {
- continue
- }
- else if (value === "false") {
- continue
- }
- out += "
";
- }
- try {
- getById("menuStatsBox").scrollLeft = scrollLeft;
- getById("menuStatsBox").scrollTop = scrollTop;
- } catch (e) {}
-}
-
-
-function updateLocalStats(){
-
- var totalBitrate = 0;
- var totalBitrate2 = 0;
- var cpuLimited = false;
- for (var uuid in session.pcs) {
- if ("video_bitrate_kbps" in session.pcs[uuid].stats){
- totalBitrate+=session.pcs[uuid].stats.video_bitrate_kbps || 0;
- }
- if ("audio_bitrate_kbps" in session.pcs[uuid].stats){
- totalBitrate+=session.pcs[uuid].stats.audio_bitrate_kbps || 0;
- }
- if ("total_sending_bitrate_kbps" in session.pcs[uuid].stats){
- totalBitrate2+=session.pcs[uuid].stats.total_sending_bitrate_kbps || 0;
- }
-
- if ("quality_limitation_reason" in session.pcs[uuid].stats){
- if (session.pcs[uuid].stats.quality_limitation_reason == "cpu"){
- cpuLimited=true;
- }
- }
-
- if (uuid in session.rpcs){
- if (session.pcs[uuid].stats.label){
- session.pcs[uuid].stats.label = session.rpcs[uuid].label;
- }
- if (session.pcs[uuid].stats.streamID){
- session.pcs[uuid].stats.streamID = session.rpcs[uuid].streamID;
- }
- }
-
- setTimeout(function(UUID) {
- if (!( session.pcs[UUID])){return;}
- session.pcs[UUID].getStats().then(function(stats) {
- if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
- session.pcs[UUID].stats.audio_bitrate_kbps=0;
- }
- stats.forEach(stat => {
- if (stat.type == "transport"){
- if ("bytesSent" in stat) {
- if ("_bytesSent" in session.pcs[UUID].stats){
- if (session.pcs[UUID].stats._timestamp){
- if (stat.timestamp){
- session.pcs[UUID].stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp));
- }
- }
- }
- session.pcs[UUID].stats._bytesSent = stat.bytesSent;
- }
- if ("timestamp" in stat) {
- session.pcs[UUID].stats._timestamp = stat.timestamp;
- }
- } else if (stat.type == "outbound-rtp") {
- if (stat.kind == "video") {
-
- if ("qualityLimitationReason" in stat) {
- session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
- }
- if ("framesPerSecond" in stat) {
- session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
- }
- if ("encoderImplementation" in stat) {
- session.pcs[UUID].stats.video_encoder = stat.encoderImplementation;
- }
- if ("bytesSent" in stat) {
- if ("_bytesSentVideo" in session.pcs[UUID].stats){
- if (session.pcs[UUID].stats._timestamp1){
- session.pcs[UUID].stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentVideo)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
- if (stat.timestamp){
- }
- }
- }
- session.pcs[UUID].stats._bytesSentVideo = stat.bytesSent;
- }
-
- if ("nackCount" in stat) {
- if ("_nackCount" in session.pcs[UUID].stats){
- if (session.pcs[UUID].stats._timestamp1){
- if (stat.timestamp){
- session.pcs[UUID].stats.nacks_per_second = parseInt(10000*(stat.nackCount - session.pcs[UUID].stats._nackCount)/(stat.timestamp - session.pcs[UUID].stats._timestamp1))/10;
- }
- }
- }
- }
- if ("retransmittedBytesSent" in stat) {
- if ("_retransmittedBytesSent" in session.pcs[UUID].stats){
- if (session.pcs[UUID].stats._timestamp1){
- if (stat.timestamp){
- session.pcs[UUID].stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - session.pcs[UUID].stats._retransmittedBytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
- }
- }
- }
- }
-
- if ("nackCount" in stat) {
- session.pcs[UUID].stats._nackCount = stat.nackCount;
-
- }
-
- if ("retransmittedBytesSent" in stat) {
- session.pcs[UUID].stats._retransmittedBytesSent = stat.retransmittedBytesSent;
-
- }
-
- if ("timestamp" in stat) {
- session.pcs[UUID].stats._timestamp1 = stat.timestamp;
- }
-
- if ("pliCount" in stat) {
- session.pcs[UUID].stats.total_pli_count = stat.pliCount;
- }
- if ("keyFramesEncoded" in stat) {
- session.pcs[UUID].stats.total_key_frames_encoded = stat.keyFramesEncoded;
- }
-
-
- } else if (stat.kind == "audio") {
- if ("bytesSent" in stat) {
- if (session.pcs[UUID].stats._bytesSentAudio){
- if (session.pcs[UUID].stats._timestamp2){
- if (stat.timestamp){
- if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
- session.pcs[UUID].stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentAudio)/(stat.timestamp - session.pcs[UUID].stats._timestamp2));
- } else {
- session.pcs[UUID].stats.audio_bitrate_kbps=0;
- }
- }
- }
- }
- }
- if ("timestamp" in stat) {
- session.pcs[UUID].stats._timestamp2 = stat.timestamp;
- }
-
- if ("bytesSent" in stat) {
- session.pcs[UUID].stats._bytesSentAudio = stat.bytesSent;
-
- }
- }
- } else if (stat.type == "remote-candidate") {
- if ("relayProtocol" in stat) {
- if ("ip" in stat) {
- session.pcs[UUID].stats.remote_relay_IP = stat.ip;
- }
- session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
- }
- if ("candidateType" in stat) {
- session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
- }
- } else if (stat.type == "local-candidate") {
- if ("relayProtocol" in stat) {
- if ("ip" in stat) {
- session.pcs[UUID].stats.local_relayIP = stat.ip;
- }
- session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
- }
- if ("candidateType" in stat) {
- session.pcs[UUID].stats.local_candidateType = stat.candidateType;
- }
- } else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
-
- if ("availableOutgoingBitrate" in stat){
- session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
- }
- if ("totalRoundTripTime" in stat){
- session.pcs[UUID].stats.total_roundTripTime_ms = stat.totalRoundTripTime*1000;
- }
- }
- return;
+ WebMidi.addListener("connected", function(e) {
+ log(e);
});
- return;
- });
- }, 0, uuid);
- }
- var headerStats = "Viewers: ";
- headerStats += Object.keys(session.pcs).length || 0;
- headerStats += ", Upload (kbps): "+totalBitrate2; // + " / "+totalBitrate;
- if (cpuLimited){
- headerStats += ", CPU Overloaded";
- }
- if (Object.keys(session.pcs).length){
- getById("head5").classList.remove("advanced");
- }
- getById("head5").innerHTML = headerStats;
-}
-
-function updateStats(obsvc = false) {
- log('updateStats - resolution found');
- if (document.getElementById('previewWebcam')) {
- var ele = document.getElementById('previewWebcam');
- var wcs = "webcamstats";
- } else if (document.getElementById('videosource')) {
- var ele = document.getElementById('videosource');
- var wcs = "webcamstats3";
- } else {
- return;
- }
-
- try {
- getById(wcs).innerHTML = "";
- ele.srcObject.getVideoTracks().forEach(
- function(track) {
- if ((obsvc) && (parseInt(track.getSettings().frameRate) == 30)) {
- getById(wcs).innerHTML = "Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + " @ up to 60fps";
- } else {
- getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + "@" + (parseInt(track.getSettings().frameRate * 10) / 10) + "fps";
- }
- }
- );
-
- } catch (e) {
- errorlog(e);
- }
-}
-
-function toggleMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
-
- log("muting");
-
- if (session.director) {
- if (!session.directorEnabledPPT) {
- log("Director doesn't have PPT enabled yet");
- // director has not enabled PTT yet.
- return;
- }
- }
-
- if (apply) {
- session.muted = !session.muted; // we flip here as we are going to flip again in a second.
- }
- //try{var ptt = getById("press2talk");} catch(e){var ptt=false;}
-
-
- if (session.muted == false) {
- session.muted = true;
- getById("mutetoggle").className = "las la-microphone-slash my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutebutton").className = "float2 red puslate";
- getById("header").classList.add('red');
-
- if (session.localMuteElement){
- session.localMuteElement.style.display = "block";
- }
-
- }
- if (session.streamSrc) {
- session.streamSrc.getAudioTracks().forEach((track) => {
- track.enabled = false;
- });
- }
- //if (ptt){
- // ptt.innerHTML = "🔇 Push to Talk";
- //}
-
- } else {
- session.muted = false;
- getById("mutetoggle").className = "las la-microphone my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutebutton").className = "float";
- getById("header").classList.remove('red');
-
- if (session.localMuteElement){
- session.localMuteElement.style.display = "none";
- }
-
- }
- if (session.streamSrc) {
- session.streamSrc.getAudioTracks().forEach((track) => {
- track.enabled = true;
- });
- }
- //if (ptt){
- // ptt.innerHTML = "🔴 Push to Mute";
- //}
- }
-
- if (document.getElementById("screensharesource")){
- document.getElementById("screensharesource").contentWindow.postMessage({"mic":!session.muted}, '*');
- }
-
- if (!apply) { // only if they are changing states do we bother to spam.
- data = {};
- data.muteState = session.muted;
- session.sendMessage(data);
- log("SEND DATA");
- pokeIframeAPI('mic-mute-state', session.muted);
- }
-}
-
-
-function toggleSpeakerMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
-
- if (CtrlPressed) {
- resetupAudioOut();
- }
-
- if (apply) {
- session.speakerMuted = !session.speakerMuted;
- }
- if (session.speakerMuted == false) {
- session.speakerMuted = true;
- getById("mutespeakertoggle").className = "las la-volume-mute my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutespeakerbutton").className = "float2 red";
- }
- var sounds = document.getElementsByTagName("video");
- for (var i = 0; i < sounds.length; ++i) {
- sounds[i].muted = session.speakerMuted;
- }
-
- } else {
- session.speakerMuted = false;
-
- getById("mutespeakertoggle").className = "las la-volume-up my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutespeakerbutton").className = "float";
- }
-
- var sounds = document.getElementsByTagName("video");
- for (var i = 0; i < sounds.length; ++i) {
-
- if (sounds[i].id === "videosource") { // don't unmute ourselves. feedback galore if so.
- continue;
- } else if (sounds[i].id === "previewWebcam") {
- continue;
- } else if (sounds[i].id === "screenshare") {
- continue;
- } else {
- sounds[i].muted = session.speakerMuted;
- }
- }
- }
-
- for (var UUID in session.rpcs) {
- if (session.rpcs[UUID].videoElement) {
- //if (UUID === session.directorUUID) {
- // session.rpcs[UUID].videoElement.muted = false; // unmute director
- // log("MAKE SURE DIRECTOR ISN'T MUTED");
- //} else {
- session.rpcs[UUID].videoElement.muted = session.speakerMuted;
- // }
- }
- }
-
- if ((iOS) || (iPad)) {
- resetupAudioOut();
- }
-}
-
-
-function toggleChat(event = null) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
- if (session.chat == false) {
- setTimeout(function() {
- document.addEventListener("click", toggleChat);
- }, 10);
-
- getById("chatModule").addEventListener("click", function(e) {
- e.stopPropagation();
- return false;
- });
- session.chat = true;
- getById("chattoggle").className = "las la-comment-dots my-float toggleSize";
- getById("chatbutton").className = "float2";
- getById("chatModule").style.display = "block";
- getById("chatInput").focus(); // give it keyboard focus
- } else {
- session.chat = false;
- getById("chattoggle").className = "las la-comment-alt my-float toggleSize";
- getById("chatbutton").className = "float";
- getById("chatModule").style.display = "none";
-
- document.removeEventListener("click", toggleChat);
- getById("chatModule").removeEventListener("click", function(e) {
- e.stopPropagation();
- return false;
- });
- }
- if (getById("chatNotification").value) {
- getById("chatNotification").value = 0;
- }
- getById("chatNotification").classList.remove("notification");
-}
-
-function directorAdvanced(ele) {
- var target = document.createElement("div");
- target.style = "position:absolute;float:left;width:270px;height:222px;background-color:#7E7E7E;";
-
- var closeButton = document.createElement("button");
- closeButton.innerHTML = " close";
- closeButton.style.left = "5px";
- closeButton.style.position = "relative";
- closeButton.onclick = function() {
- target.parentNode.removeChild(target);
- };
- target.appendChild(closeButton);
-
- var someButton = document.createElement("button");
- someButton.innerHTML = " some action ";
- someButton.style.left = "5px";
- someButton.style.position = "relative";
- someButton.onclick = function() {
- var actionMsg = {};
- session.sendRequest(actionMsg, ele.dataset.UUID);
- };
- target.appendChild(someButton);
-
- ele.parentNode.appendChild(target);
-}
-
-function directorSendMessage(ele) {
- var target = document.createElement("div");
- target.style = "position:absolute;float:left;width:270px;height:222px;background-color:#7E7E7E;";
-
- var inputField = document.createElement("textarea");
- inputField.placeholder = "Enter your message here";
- inputField.style.width = "255px";
- inputField.style.height = "170px";
- inputField.style.margin = "5px 10px 5px 10px";
- inputField.style.padding = "5px";
-
- target.appendChild(inputField);
-
- var sendButton = document.createElement("button");
- sendButton.innerHTML = " send message ";
- sendButton.style.left = "5px";
- sendButton.style.position = "relative";
- sendButton.onclick = function() {
- var chatMsg = {};
- chatMsg.chat = inputField.value;
- if (sendButton.parentNode.overlay) {
- chatMsg.overlay = sendButton.parentNode.overlay;
- }
- session.sendRequest(chatMsg, ele.dataset.UUID);
- inputField.value = "";
- //target.parentNode.removeChild(target);
- };
-
-
- var closeButton = document.createElement("button");
- closeButton.innerHTML = " close";
- closeButton.style.left = "5px";
- closeButton.style.position = "relative";
- closeButton.onclick = function() {
- inputField.value = "";
- target.parentNode.removeChild(target);
- };
-
- var overlayMsg = document.createElement("span");
-
- overlayMsg.style.left = "16px";
- overlayMsg.style.top = "6px";
- overlayMsg.style.position = "relative";
- overlayMsg.innerHTML = "";
- target.overlay = true;
-
- overlayMsg.onclick = function(e) {
- log(e.target.parentNode.parentNode);
- if (e.target.parentNode.parentNode.overlay === true) {
- e.target.parentNode.parentNode.overlay = false;
- e.target.parentNode.innerHTML = "";
- } else {
- e.target.parentNode.parentNode.overlay = true;
- e.target.parentNode.innerHTML = "";
- }
- }
-
-
- inputField.addEventListener("keydown", function(e) {
- if (e.keyCode == 13) {
- e.preventDefault();
- sendButton.click();
- } else if (e.keyCode == 27) {
- e.preventDefault();
- inputField.value = "";
- target.parentNode.removeChild(target);
- }
- });
- target.appendChild(closeButton);
- target.appendChild(sendButton);
- target.appendChild(overlayMsg);
- ele.parentNode.appendChild(target);
- inputField.focus();
- inputField.select();
-}
-
-function toggleVideoMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
- if (apply) {
- session.videoMuted = !session.videoMuted;
- }
- if (session.videoMuted == false) {
- session.videoMuted = true;
- getById("mutevideotoggle").className = "las la-video-slash my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutevideobutton").className = "float2 red";
- }
- if (session.streamSrc) {
- session.streamSrc.getVideoTracks().forEach((track) => {
- track.enabled = false;
- });
- }
-
- } else {
- session.videoMuted = false;
-
- getById("mutevideotoggle").className = "las la-video my-float toggleSize";
- if (!(session.cleanOutput)){
- getById("mutevideobutton").className = "float";
- }
- if (session.streamSrc) {
- session.streamSrc.getVideoTracks().forEach((track) => {
- // try {
- // if (document.querySelector("select#videoSource3").value == "ZZZ"){
- // return;
- // }
- // } catch(e){}
- track.enabled = true;
- });
- }
- }
-
-
-
- if (!apply) {
- var msg = {};
- msg.videoMuted = session.videoMuted;
- session.sendMessage(msg);
- pokeIframeAPI('video-mute-state', session.videoMuted);
- if (!session.videoMuted){makeImages();}
- }
-}
-
-var toggleSettingsState = false;
-
-function toggleSettings(forceShow = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
-
- getById("multiselect-trigger3").dataset.state = "0";
- getById("multiselect-trigger3").classList.add('closed');
- getById("multiselect-trigger3").classList.remove('open');
- getById("chevarrow2").classList.add('bottom');
-
- if (toggleSettingsState == true) {
- if (forceShow == true) {
- enumerateDevices().then(gotDevices2);
- return;
- }
- } // don't close if already open
- if (getById("popupSelector").style.display == "none") {
-
- updateConstraintSliders();
-
- setTimeout(function() {
- document.addEventListener("click", toggleSettings);
- }, 10);
-
- getById("popupSelector").addEventListener("click", function(e) {
- e.stopPropagation();
- return false;
- });
-
- if (navigator.userAgent.indexOf('Chrome') != -1) {
- try {
- navigator.permissions.query({
- name: "camera"
- }).then(function(promise) {
- if (promise && promise.state) {
- if (promise.state == "prompt") {
- navigator.mediaDevices.getUserMedia({
- video: true
- , audio: false
- }).then(function(stream) {
- enumerateDevices().then(gotDevices2).then(function() {
- stream.getTracks().forEach(function(track) {
- //stream.removeTrack(track);
- track.stop(); // clean up?
- });
- });
-
- }).catch(function(err) {
- enumerateDevices().then(gotDevices2).then(function() {});
- });
- } else {
- enumerateDevices().then(gotDevices2).then(function() {});
- }
- // console.log(promise.state); //"granted", "prompt" or "rejected"
- } else {
- enumerateDevices().then(gotDevices2).then(function() {});
- }
+ WebMidi.addListener("disconnected", function(e) {
+ log(e);
});
- } catch (e) {
- enumerateDevices().then(gotDevices2).then(function() {});
- }
- } else {
- enumerateDevices().then(gotDevices2).then(function() {});
- }
-
- getById("popupSelector").style.display = "inline-block"
- getById("settingsbutton").classList.add("float2");
- getById("settingsbutton").classList.remove("float");
- setTimeout(function() {
- getById("popupSelector").style.right = "0px";
- }, 1);
- toggleSettingsState = true;
- } else {
- document.removeEventListener("click", toggleSettings);
- getById("popupSelector").removeEventListener("click", function(e) {
- e.stopPropagation();
- return false;
- });
-
- getById("popupSelector").style.right = "-400px";
-
- getById("settingsbutton").classList.add("float");
- getById("settingsbutton").classList.remove("float2");
- setTimeout(function() {
- getById("popupSelector").style.display = "none";
- }, 200);
- toggleSettingsState = false;
- }
-}
-
-function hangup() { // TODO: I need to have this be MUTE, toggle, with volume not touched.
- getById("main").innerHTML = "👋";
- setTimeout(function() {
- session.hangup();
- }, 0);
-}
-
-function hangup2() {
- session.hangupDirector();
- getById("miniPerformer").innerHTML = "";
- getById("press2talk").dataset.value = 0;
- getById("screensharebutton").classList.add("advanced");
- getById("settingsbutton").classList.add("advanced");
- getById("mutebutton").classList.add("advanced");
- getById("hangupbutton2").classList.add("advanced");
- //getById("chatbutton").classList.remove("advanced");
- getById("controlButtons").style.display = "inherit";
- //getById("mutespeakerbutton").classList.add("advanced");
- getById("mutevideobutton").classList.add("advanced");
- getById("screenshare2button").classList.add("advanced");
-
- getById("screensharebutton").classList.add("float");
- getById("screensharebutton").classList.remove("float2");
-
- if (session.showDirector == false) {
- getById("miniPerformer").innerHTML = '
(only guests can see this feed)
(only guests can see this feed)This is you, the director.
You are also a performer.
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://obs.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
- details = details.split(' ').join('+');
- details = details.split('&').join('%26');
- var linkToOpen = "https://calendar.google.com/calendar/r/eventedit?text="+title+"&details="+details;
- //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
-
- window.open(linkToOpen);
-
-}
-
-function addToOutlookCalendar(){
- var title = "Live Stream";
- var linkout = getById("director_block_1").innerText;
- var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://obs.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
- details = details.split(' ').join('%20');
- details = details.split('&').join('%26');
-
-
- var linkToOpen = "https://outlook.live.com/owa/?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&subject="+title+"&body="+details;
- //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
-
- window.open(linkToOpen);
-}
-
-function addToYahooCalendar(){
- var title = "Live Stream";
- var linkout = getById("director_block_1").innerText;
- var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://obs.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
- details = details.split(' ').join('%20');
- details = details.split('&').join('%26');
- var linkToOpen = "https://calendar.yahoo.com?v60&title="+title+"&desc="+details;
- //https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
-
- window.open(linkToOpen);
-}
-
-function toggle(ele, tog = false, inline = true) {
- var x = ele;
- if (x.style.display === "none") {
- if (inline) {
- x.style.display = "inline-block";
- } else {
- x.style.display = "block";
- }
- } else {
- x.style.display = "none";
- }
- if (tog) {
- if (tog.dataset.saved) {
- tog.innerHTML = tog.dataset.saved;
- delete(tog.dataset.saved);
- } else {
- tog.dataset.saved = tog.innerHTML;
- tog.innerHTML = "Hide This";
- }
- }
-}
-
-function toggleByDataset(filter) {
- var elements = document.querySelectorAll('[data-cluster="'+filter+'"]'); // ie: .cluster1
- for (var i = 0; i < elements.length; i++) {
- elements[i].classList.toggle('hidden');
- }
-}
-
-
-var SelectedAudioOutputDevices = []; // order matters.
-var SelectedAudioInputDevices = []; // ..
-var SelectedVideoInputDevices = []; // ..
-
-function enumerateDevices() {
-
- log("enumerated start");
-
- if (typeof navigator.enumerateDevices === "function") {
- log("enumerated failed 1");
- return navigator.enumerateDevices();
- } else if (typeof navigator.mediaDevices === "object" && typeof navigator.mediaDevices.enumerateDevices === "function") {
- return navigator.mediaDevices.enumerateDevices();
- } else {
- return new Promise((resolve, reject) => {
- try {
- if (window.MediaStreamTrack == null || window.MediaStreamTrack.getSources == null) {
- throw new Error();
- }
- window.MediaStreamTrack.getSources((devices) => {
- resolve(devices
- .filter(device => {
- return device.kind.toLowerCase() === "video" || device.kind.toLowerCase() === "videoinput";
- })
- .map(device => {
- return {
- deviceId: device.deviceId != null ? device.deviceId : ""
- , groupId: device.groupId
- , kind: "videoinput"
- , label: device.label
- , toJSON: /* istanbul ignore next */ function() {
- return this;
- }
- };
- }));
- });
- } catch (e) {
- errorlog(e);
- }
- });
- }
-}
-
-function requestOutputAudioStream() {
- try {
- //warnlog("GET USER MEDIA");
- return navigator.mediaDevices.getUserMedia({
- audio: true
- , video: false
- }).then(function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
- log("get media sources; request audio stream");
- return enumerateDevices().then(function(deviceInfos) {
- stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
- track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
- });
- const audioOutputSelect = document.querySelector('#outputSourceScreenshare');
- audioOutputSelect.remove(0);
- audioOutputSelect.removeAttribute("onclick");
-
- for (let i = 0; i !== deviceInfos.length; ++i) {
- const deviceInfo = deviceInfos[i];
- if (deviceInfo == null) {
- continue;
- }
- const option = document.createElement('option');
- option.value = deviceInfo.deviceId;
- if (deviceInfo.kind === 'audiooutput') {
- const option = document.createElement('option');
- if (audioOutputSelect.length === 0) {
- option.dataset.default = true;
- } else {
- option.dataset.default = false;
- }
- option.value = deviceInfo.deviceId || "default";
- if (option.value == session.sink) {
- option.selected = true;
- }
- option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
- audioOutputSelect.appendChild(option);
- } else {
- log('Some other kind of source/device: ', deviceInfo);
- }
- }
- });
- });
- } catch (e) {
- if (!(session.cleanOutput)) {
- if (window.isSecureContext) {
- warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
- } else if ((iOS) || (iPad)) {
- warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
- } else {
- warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
- }
- }
- }
-}
-
-
-function requestAudioStream() {
- try {
- //warnlog("GET USER MEDIA");
- return navigator.mediaDevices.getUserMedia({
- audio: true
- , video: false
- }).then(function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
- log("get media sources; request audio stream");
- return enumerateDevices().then(function(deviceInfos) {
- stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
- track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
- });
- log("updating audio");
- const audioInputSelect = document.querySelector('select#audioSourceScreenshare');
- audioInputSelect.remove(1);
- audioInputSelect.removeAttribute("onchange");
-
-
- for (let i = 0; i !== deviceInfos.length; ++i) {
- const deviceInfo = deviceInfos[i];
- if (deviceInfo == null) {
- continue;
- }
- const option = document.createElement('option');
- option.value = deviceInfo.deviceId;
- if (deviceInfo.kind === 'audioinput') {
- option.text = deviceInfo.label || `Microphone ${audioInputSelect.length + 1}`;
- audioInputSelect.appendChild(option);
- } else {
- log('Some other kind of source/device: ', deviceInfo);
- }
- }
- audioInputSelect.style.minHeight = ((audioInputSelect.childElementCount + 1) * 1.15 * 16) + 'px';
- audioInputSelect.style.minWidth = "342px";
- });
- });
- } catch (e) {
- if (!(session.cleanOutput)) {
- if (window.isSecureContext) {
- warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
- } else if ((iOS) || (iPad)) {
- warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
- } else {
- warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
- }
- }
- }
-}
-
-
-function gotDevices(deviceInfos) { // https://github.com/webrtc/samples/blob/gh-pages/src/content/devices/input-output/js/main.js#L19
-
- log("got devices!");
- log(deviceInfos);
- try {
- const audioInputSelect = document.querySelector('#audioSource');
-
- audioInputSelect.innerHTML = "";
-
- var option = document.createElement('input');
- option.type = "checkbox";
- option.value = "ZZZ";
- option.name = "multiselect1";
- option.id = "multiselect1";
- option.style.display = "none";
- option.checked = true;
-
-
- var label = document.createElement('label');
- label.for = option.name;
- label.innerHTML = 'No Audio';
-
- var listele = document.createElement('li');
- listele.appendChild(option);
- listele.appendChild(label);
- audioInputSelect.appendChild(listele);
-
-
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- if (!(getById("multiselect1").checked)) {
- getById("multiselect1").checked = true;
-
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
-
- log("CHECKED 1");
- } else {
-
- var list = document.querySelectorAll("#audioSource>li>input");
- for (var i = 0; i < list.length; i++) {
- if (list[i].id !== "multiselect1") {
- list[i].checked = false;
- }
- }
-
- while (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(event.currentTarget.value), 1);
- }
- }
- };
-
- getById('multiselect-trigger').dataset.state = '0';
- getById('multiselect-trigger').classList.add('closed');
- getById('multiselect-trigger').classList.remove('open');
- getById('chevarrow1').classList.add('bottom');
-
- const videoSelect = document.querySelector('select#videoSourceSelect');
- const audioOutputSelect = document.querySelector('#outputSource');
- const selectors = [videoSelect];
-
- const values = selectors.map(select => select.value);
- selectors.forEach(select => {
- while (select.firstChild) {
- select.removeChild(select.firstChild);
- }
- });
-
-
- function comp(a, b) {
- if (a.kind === 'audioinput') {
- return 0;
- } else if (a.kind === 'audiooutput') {
- return 0;
- }
- const labelA = a.label.toUpperCase();
- const labelB = b.label.toUpperCase();
- if (labelA > labelB) {
- return 1;
- } else if (labelA < labelB) {
- return -1;
- }
- return 0;
- }
- //deviceInfos.sort(comp); // I like this idea, but it messes with the defaults. I just don't know what it will do.
-
- // This is to hide NDI from default device. NDI Tools fucks up.
- var tmp = [];
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if (!((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek")))) {
- tmp.push(deviceInfo);
- }
- }
-
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek"))) {
- tmp.push(deviceInfo);
- log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
- }
- }
- deviceInfos = tmp;
- log(deviceInfos);
-
- if ((session.audioDevice) && (session.audioDevice !== 1)) { // this sorts according to users's manual selection
- var tmp = [];
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if ((deviceInfo.kind === 'audioinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.audioDevice))) {
- tmp.push(deviceInfo);
- log("A DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
- } else if (deviceInfo.deviceId === session.audioDevice){
- tmp.push(deviceInfo);
- log("EXACT A DEVICE FOUND");
- }
- }
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if (!((deviceInfo.kind === 'audioinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.audioDevice)))) {
- if (deviceInfo.deviceId !== session.audioDevice){
- tmp.push(deviceInfo);
- }
- }
- }
-
- deviceInfos = tmp;
- log(session.audioDevice);
- log(deviceInfos);
- }
-
-
- if ((session.videoDevice) && (session.videoDevice !== 1)) { // this sorts according to users's manual selection
- var tmp = [];
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.videoDevice))) {
- tmp.push(deviceInfo);
- log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
- } else if (deviceInfo.deviceId === session.videoDevice){
- tmp.push(deviceInfo);
- log("EXACT V DEVICE FOUND");
- }
- }
- for (let i = 0; i !== deviceInfos.length; ++i) {
- deviceInfo = deviceInfos[i];
- if (!((deviceInfo.kind === 'videoinput') && (deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase().includes(session.videoDevice)))) {
- if (deviceInfo.deviceId !== session.videoDevice){
- tmp.push(deviceInfo);
- }
- }
- }
- deviceInfos = tmp;
- log("VDECICE:" + session.videoDevice);
- log(deviceInfos);
- }
-
-
- var counter = 1;
- for (let i = 0; i !== deviceInfos.length; ++i) {
- const deviceInfo = deviceInfos[i];
- if (deviceInfo == null) {
- continue;
- }
-
- if (deviceInfo.kind === 'audioinput') {
- option = document.createElement('input');
- option.type = "checkbox";
- counter++;
- listele = document.createElement('li');
- if (counter == 2) {
- option.checked = true;
- listele.style.display = "block";
- option.style.display = "none";
- getById("multiselect1").checked = false;
- getById("multiselect1").parentNode.style.display = "none";
- } else {
- listele.style.display = "none";
- }
-
-
- option.value = deviceInfo.deviceId || "default";
- option.name = "multiselect" + counter;
- option.id = "multiselect" + counter;
- label = document.createElement('label');
- label.for = option.name;
-
- label.innerHTML = " " + (deviceInfo.label || ("microphone " + ((audioInputSelect.length || 0) + 1)));
-
- listele.appendChild(option);
- listele.appendChild(label);
- audioInputSelect.appendChild(listele);
-
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- getById("multiselect1").checked = false;
- log("UNCHECKED");
- if (!(CtrlPressed)) {
- document.querySelectorAll("#audioSource input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.id !== item.id) {
- item.checked = false;
-
- while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
- }
-
- } else {
- item.checked = true;
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
- }
- });
- }
- };
-
- } else if (deviceInfo.kind === 'videoinput') {
- option = document.createElement('option');
- option.value = deviceInfo.deviceId || "default";
- option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
- videoSelect.appendChild(option);
- } else if (deviceInfo.kind === 'audiooutput') {
- option = document.createElement('option');
- if (audioOutputSelect.length === 0) {
- option.dataset.default = true;
- } else {
- option.dataset.default = false;
- }
- option.value = deviceInfo.deviceId || "default";
- if (option.value == session.sink) {
- option.selected = true;
- }
- option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
- audioOutputSelect.appendChild(option);
- } else {
- log('Some other kind of source/device: ', deviceInfo);
- }
- }
-
- if (audioOutputSelect.childNodes.length == 0) {
- option = document.createElement('option');
- option.value = "default";
- option.text = "System Default";
- audioOutputSelect.appendChild(option);
- }
-
- option = document.createElement('option');
- option.text = "Disable Video";
- option.value = "ZZZ";
- videoSelect.appendChild(option); // NO AUDIO OPTION
-
- selectors.forEach((select, selectorIndex) => {
- if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
- select.value = values[selectorIndex];
- }
- });
-
- } catch (e) {
- errorlog(e);
- }
-}
-
-
-if (location.protocol !== 'https:') {
- if (!(session.cleanOutput)) {
- warnUser("SSL (https) is not enabled. This site will not work without it!
Try accessing the site from here instead.");
- }
-}
-
-
-function getUserMediaVideoParams(resolutionFallbackLevel, isSafariBrowser) {
- switch (resolutionFallbackLevel) {
- case 0:
- if (isSafariBrowser) {
- return {
- width: {
- min: 360
- , ideal: 1920
- , max: 1920
- }
- , height: {
- min: 360
- , ideal: 1080
- , max: 1080
- }
- };
- } else {
- return {
- width: {
- min: 720
- , ideal: 1920
- , max: 1920
- }
- , height: {
- min: 720
- , ideal: 1080
- , max: 1920
- }
- };
- }
- case 1:
- if (isSafariBrowser) {
- return {
- width: {
- min: 360
- , ideal: 1280
- , max: 1280
- }
- , height: {
- min: 360
- , ideal: 720
- , max: 720
- }
- };
- } else {
- return {
- width: {
- min: 720
- , ideal: 1280
- , max: 1280
- }
- , height: {
- min: 720
- , ideal: 720
- , max: 1280
- }
- };
- }
- case 2:
- if (isSafariBrowser) {
- return {
- width: {
- min: 640
- }
- , height: {
- min: 360
- }
- };
- } else {
- return {
- width: {
- min: 240
- , ideal: 640
- , max: 1280
- }
- , height: {
- min: 240
- , ideal: 360
- , max: 1280
- }
- };
- }
- case 3:
- if (isSafariBrowser) {
- return {
- width: {
- min: 360
- , ideal: 1280
- , max: 1440
- }
- };
- } else {
- return {
- width: {
- min: 360
- , ideal: 1280
- , max: 1440
- }
- };
- }
- case 4:
- if (isSafariBrowser) {
- return {
- height: {
- min: 360
- , ideal: 720
- , max: 960
- }
- };
- } else {
- return {
- height: {
- ideal: 720
- , max: 960
- }
- };
- }
- case 5:
- if (isSafariBrowser) {
- return {
- width: {
- min: 360
- , ideal: 640
- , max: 1440
- }
- , height: {
- min: 360
- , ideal: 360
- , max: 720
- }
- };
- } else {
- return {
- width: {
- ideal: 640
- , max: 1920
- }
- , height: {
- ideal: 360
- , max: 1920
- }
- }; // same as default, but I didn't want to mess with framerates until I gave it all a try first
- }
- case 6:
- if (isSafariBrowser) {
- return {}; // iphone users probably don't need to wait any longer, so let them just get to it
- } else {
- return {
- width: {
- min: 360
- , ideal: 640
- , max: 3840
- }
- , height: {
- min: 360
- , ideal: 360
- , max: 2160
- }
- };
-
- }
- case 7:
- return { // If the camera is recording in low-light, it may have a low framerate. It coudl also be recording at a very high resolution.
- width: {
- min: 360
- , ideal: 640
- }
- , height: {
- min: 360
- , ideal: 360
- }
- , };
-
- case 8:
- return {
- width: {
- min: 360
- }
- , height: {
- min: 360
- }
- , frameRate: 10
- }; // same as default, but I didn't want to mess with framerates until I gave it all a try first
- case 9:
- return {
- frameRate: 0
- }; // Some Samsung Devices report they can only support a framerate of 0.
- case 10:
- return {}
- default:
- return {};
- }
-}
-
-function addScreenDevices(device) {
- if (device.kind == "audio") {
- const audioInputSelect = document.querySelector('#audioSource3');
- const listele = document.createElement('li');
- listele.style.display = "block";
-
- const option = document.createElement('input');
- option.type = "checkbox";
- option.checked = true;
-
- if (getById('multiselect-trigger3').dataset.state == 0) {
- option.style.display = "none";
- }
-
- option.value = device.id;
- option.name = device.label;
- option.dataset.type = "screen";
- option.label = device.label;
-
- const label = document.createElement('label');
- label.for = option.name;
- label.innerHTML = " " + device.label;
- listele.appendChild(option);
- listele.appendChild(label);
-
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- log("change 4644");
- if (!(CtrlPressed)) {
- document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.value !== item.value) { // this shoulnd't happen, but if it does.
-
- item.checked = false;
-
- if (item.dataset.type == "screen") {
- item.parentElement.parentElement.removeChild(item.parentElement);
- }
-
- while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
- }
-
- activatedPreview = false;
- grabAudio("videosource", "#audioSource3"); // exclude item.id
-
- } else {
- if (SelectedAudioInputDevices.indexOf(item.value) > -1) {} else {
- SelectedAudioInputDevices.push(item.value);
- }
-
- item.checked = true;
- activatedPreview = false;
- grabAudio("videosource", "#audioSource3", item.value); // exclude item.id. we will reconnect, even if already connected, as a way to 'reset' a device if it isn't working.
- }
- });
- }
- event.stopPropagation();
- return false;
- };
- audioInputSelect.appendChild(listele);
- getById("audioSourceNoAudio2").checked = false;
-
- } else if (device.kind == "video") {
- const videoSelect = document.querySelector('select#videoSource3');
- //const selectors = [ videoSelect];
- //const values = selectors.map(select => select.value);
- const option = document.createElement('option');
- option.value = device.id;
- option.text = device.label;
- option.selected = true;
- option.label = device.label;
- videoSelect.appendChild(option);
- }
-}
-
-function gotDevices2(deviceInfos) {
- log("got devices!");
- log(deviceInfos);
-
- getById("multiselect-trigger3").dataset.state = "0";
- getById("multiselect-trigger3").classList.add('closed');
- getById("multiselect-trigger3").classList.remove('open');
- getById("chevarrow2").classList.add('bottom');
-
- var knownTrack = false;
-
- try {
- const audioInputSelect = document.querySelector('#audioSource3');
- const videoSelect = document.querySelector('select#videoSource3');
- const audioOutputSelect = document.querySelector('#outputSource3');
- const selectors = [videoSelect];
-
-
- [audioInputSelect].forEach(select => {
- while (select.firstChild) {
- select.removeChild(select.firstChild);
- }
- });
-
- const values = selectors.map(select => select.value);
- selectors.forEach(select => {
- while (select.firstChild) {
- select.removeChild(select.firstChild);
- }
- });
-
- [audioOutputSelect].forEach(select => {
- while (select.firstChild) {
- select.removeChild(select.firstChild);
- }
- });
-
- var counter = 0;
- for (let i = 0; i !== deviceInfos.length; ++i) {
- const deviceInfo = deviceInfos[i];
- if (deviceInfo == null) {
- continue;
- }
-
- if (deviceInfo.kind === 'audioinput') {
- const option = document.createElement('input');
- option.type = "checkbox";
- counter++;
- const listele = document.createElement('li');
- listele.style.display = "none";
-
- try {
- session.streamSrc.getAudioTracks().forEach(function(track) {
- if (deviceInfo.label == track.label) {
- option.checked = true;
- listele.style.display = "inherit";
- }
- });
- } catch (e) {
- errorlog(e);
- }
-
- option.style.display = "none"
- option.value = deviceInfo.deviceId || "default";
- option.name = "multiselecta" + counter;
- option.id = "multiselecta" + counter;
- option.dataset.label = deviceInfo.label || ("microphone " + ((audioInputSelect.length || 0) + 1));
-
- const label = document.createElement('label');
- label.for = option.name;
-
- label.innerHTML = " " + (deviceInfo.label || ("microphone " + ((audioInputSelect.length || 0) + 1)));
-
- listele.appendChild(option);
- listele.appendChild(label);
- audioInputSelect.appendChild(listele);
-
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- log("change 4768");
- if (!(CtrlPressed)) {
- document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.value !== item.value) {
- item.checked = false;
- if (item.dataset.type == "screen") {
- item.parentElement.parentElement.removeChild(item.parentElement);
- }
- while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
- }
- } else {
- item.checked = true;
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
- }
- });
- } else {
-
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
-
- getById("audioSourceNoAudio2").checked = false;
- }
- };
-
- } else if (deviceInfo.kind === 'videoinput') {
- const option = document.createElement('option');
- option.value = deviceInfo.deviceId || "default";
- option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
- try {
- if (!knownTrack){
- if (session.canvasSource){
- session.canvasSource.srcObject.getVideoTracks().forEach(function(track) {
- if (option.text == track.label) {
- option.selected = true;
- knownTrack = true;
- }
- });
- }
- }
- if (!knownTrack){
- session.streamSrc.getVideoTracks().forEach(function(track) {
- if (option.text == track.label) {
- option.selected = true;
- knownTrack = true;
- }
- });
- }
- } catch (e) {
- errorlog(e);
- }
- videoSelect.appendChild(option);
-
- } else if (deviceInfo.kind === 'audiooutput') {
- const option = document.createElement('option');
- if (audioOutputSelect.length === 0) {
- option.dataset.default = true;
- } else {
- option.dataset.default = false;
- }
- option.value = deviceInfo.deviceId || "default";
- if (option.value == session.sink) {
- option.selected = true;
- }
- option.text = deviceInfo.label || `Speaker ${outputSelect.length + 1}`;
- audioOutputSelect.appendChild(option);
-
- } else {
- log('Some other kind of source/device: ', deviceInfo);
- }
- }
-
- if (audioOutputSelect.childNodes.length == 0) {
- const option = document.createElement('option');
- option.value = "default";
- option.text = "System Default";
- audioOutputSelect.appendChild(option);
- }
-
- ////////////
-
- session.streamSrc.getAudioTracks().forEach(function(track) { // add active ScreenShare audio tracks to the list
- log("Checking for screenshare audio");
- var matched = false;
- for (var i = 0; i !== deviceInfos.length; ++i) {
- var deviceInfo = deviceInfos[i];
- if (deviceInfo == null) {
- continue;
- }
- log("---");
- if (track.label == deviceInfo.label) {
- matched = true;
- continue;
- }
- }
- if (matched == false) { // Not a gUM device
-
- var listele = document.createElement('li');
- listele.style.display = "block";
- var option = document.createElement('input');
- option.type = "checkbox";
- option.value = track.id;
- option.checked = true;
- option.style.display = "none";
- option.name = track.label;
- option.label = track.label;
- option.dataset.type = "screen";
- const label = document.createElement('label');
- label.for = option.name;
- label.innerHTML = " " + track.label;
- listele.appendChild(option);
- listele.appendChild(label);
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- log("change 4873");
- var trackid = null;
- if (!(CtrlPressed)) {
-
- document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.value !== item.value) { // this shoulnd't happen, but if it does.
- item.checked = false;
- if (item.dataset.type == "screen") {
- item.parentElement.parentElement.removeChild(item.parentElement);
- }
- } else {
- event.currentTarget.checked = true;
- trackid = item.value;
- }
- });
- } else {
- //getById("audioSourceNoAudio2").checked=false;
- if (event.currentTarget.dataset.type == "screen") {
- event.currentTarget.parentElement.parentElement.removeChild(event.currentTarget.parentElement);
- }
- }
- activatedPreview = false;
- grabAudio("videosource", "#audioSource3", trackid); // exclude item.id.
- event.stopPropagation();
- return false;
- };
- audioInputSelect.appendChild(listele);
- }
- });
- /////////// no video option
- var optionss = false;
- if (screensharesupport) {
- optionss = document.createElement('option');
- optionss.text = "New Screen Share";
- optionss.value = "XXX";
- optionss.previous =
- videoSelect.appendChild(optionss); // NO AUDIO OPTION
- }
-
- option = document.createElement('option'); // no video
- option.text = "Disable Video";
- option.value = "ZZZ";
- videoSelect.appendChild(option);
- if (session.streamSrc.getVideoTracks().length == 0) {
- option.selected = true;
- } else if (knownTrack == false) {
- option = document.createElement('option'); // no video
- option.text = session.streamSrc.getVideoTracks()[0].label;
- option.value = "YYY";
- videoSelect.appendChild(option);
- option.selected = true;
-
- }
-
- if (optionss) {
- optionss.lastSelected = videoSelect.selectedIndex;
- }
-
- ///////////// /// NO AUDIO appended option
-
- var option = document.createElement('input');
- option.type = "checkbox";
- option.value = "ZZZ";
- option.style.display = "none"
- option.id = "audioSourceNoAudio2";
-
- var label = document.createElement('label');
- label.for = option.name;
- label.innerHTML = " No Audio";
- var listele = document.createElement('li');
-
- if (session.streamSrc.getAudioTracks().length == 0) {
- option.checked = true;
- } else {
- listele.style.display = "none";
- option.checked = false;
- }
- option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
- log("change 4938");
- if (!(CtrlPressed)) {
- document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.value !== item.value) {
- item.checked = false;
- if (item.dataset.type == "screen") {
- item.parentElement.parentElement.removeChild(item.parentElement);
- }
-
- while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
- }
- } else {
- item.checked = true;
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {
- //
- } else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
- }
- });
- } else {
- document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
- if (event.currentTarget.value === item.value) {
- event.currentTarget.checked = true;
- if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) > -1) {} else {
- SelectedAudioInputDevices.push(event.currentTarget.value);
- }
- } else {
- item.checked = false;
- if (item.dataset.type == "screen") {
- item.parentElement.parentElement.removeChild(item.parentElement);
- }
- while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
- SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
- }
- }
-
- });
- }
- };
- listele.appendChild(option);
- listele.appendChild(label);
- audioInputSelect.appendChild(listele);
-
- ////////////
-
-
- selectors.forEach((select, selectorIndex) => {
- if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
- select.value = values[selectorIndex];
- }
- });
-
- audioInputSelect.onchange = function() {
- log("Audio OPTION HAS CHANGED? 2");
- activatedPreview = false;
- setTimeout(function(){
- grabAudio("videosource", "#audioSource3");
- },10)
- };
- videoSelect.onchange = function(event) {
- try {
- if (event.target.options[event.target.options.selectedIndex].value === "XXX") {
- videoSelect.selectedIndex = event.target.options[event.target.options.selectedIndex].lastSelected;
- if (session.screenShareState == false) {
- toggleScreenShare();
- } else {
- toggleScreenShare(true);
- }
- return;
- }
- } catch (e) {}
- log("video source changed");
- activatedPreview = false;
- grabVideo(session.quality, "videosource", "select#videoSource3");
- enumerateDevices().then(gotDevices2).then(function() {
- session.screenShareState = false;
- pokeIframeAPI("screen-share-ended");
- getById("screensharebutton").classList.add("float");
- getById("screensharebutton").classList.remove("float2");
- });
- };
- getById("refreshVideoButton").onclick = function() {
- if (session.screenShareState) {
- log("can't refresh a screenshare");
- return;
- }
- log("video source changed");
- activatedPreview = false;
- grabVideo(session.quality, "videosource", "select#videoSource3");
- };
-
- audioOutputSelect.onchange = function() {
-
- if ((iOS) || (iPad)) {
- return;
- }
-
- var outputSelect = document.querySelector('select#outputSource3');
- session.sink = outputSelect.options[outputSelect.selectedIndex].value;
- //if (session.sink=="default"){session.sink=false;} else {
- try {
- getById("videosource").setSinkId(session.sink).then(() => {
- log("New Output Device:" + session.sink);
- }).catch(error => {
- errorlog(error);
- });
- } catch (e) {
- errorlog(e);
- }
- for (UUID in session.rpcs) {
- session.rpcs[UUID].videoElement.setSinkId(session.sink).then(() => {
- log("New Output Device for: " + UUID);
- }).catch(error => {
- errorlog(error);
- });
- }
- }
-
- } catch (e) {
- errorlog(e);
- }
-}
-
-function playtone(screen = false) {
-
- if ((iOS) || (iPad)) {
- // try{
- // session.audioContext.resume();
- // } catch(e){errorlog(e);}
- var testtone = document.getElementById("testtone");
- if (testtone) {
- testtone.mute
- testtone.play();
- }
- return;
- }
-
- if (screen) {
- var outputSelect = document.querySelector('select#outputSourceScreenshare');
- session.sink = outputSelect.options[outputSelect.selectedIndex].value;
- }
-
- var testtone = document.getElementById("testtone");
- if (testtone) {
- if (session.sink) {
- try {
- testtone.setSinkId(session.sink).then(() => { // TODO: iOS doens't support sink. Needs to bypass if IOS
- log("changing audio sink:" + session.sink);
- testtone.play();
- }).catch(error => {
- errolog("couldn't set sink");
- errorlog(error);
- });
- } catch (e) {
- warnlog(e); // firefox?
- testtone.play();
- }
- } else {
- testtone.play();
- }
- }
-}
-
-async function getAudioOnly(selector, trackid = null, override = false) {
- var audioSelect = document.querySelector(selector).querySelectorAll("input");
- var audioList = [];
- var streams = [];
- log("getAudioOnly()");
- for (var i = 0; i < audioSelect.length; i++) {
- if (audioSelect[i].value == "ZZZ") {
- continue;
- } else if (trackid == audioSelect[i].value) { // skip already excluded
- continue;
- } else if ("screen" == audioSelect[i].dataset.type) { // skip already excluded ---------- !!!!!! DOES THIS MAKE SENSE? TODO: CHECK
- continue;
- } else if (audioSelect[i].checked) {
- log(audioSelect[i]);
- audioList.push(audioSelect[i]);
- }
- }
- for (var i = 0; i < audioList.length; i++) {
-
- if ((audioList[i].value == "default") && (session.echoCancellation !== false) && (session.autoGainControl !== false) && (session.noiseSuppression !== false)) {
- var constraint = {
- audio: true
- };
- } else { // Just trying to avoid problems with some systems that don't support these features
- var constraint = {
- audio: {
- deviceId: {
- exact: audioList[i].value
- }
- }
- };
- if (session.echoCancellation === false) {
- constraint.audio.echoCancellation = false;
- } else {
- constraint.audio.echoCancellation = true;
- }
- if (session.autoGainControl === false) {
- constraint.audio.autoGainControl = false;
- } else {
- constraint.audio.autoGainControl = true;
- }
- if (session.noiseSuppression === false) {
- constraint.audio.noiseSuppression = false;
- } else {
- constraint.audio.noiseSuppression = true;
- }
- }
- constraint.video = false;
- if (override !== false) {
- try {
- if (override.audio.deviceId == audioList[i].value) {
- constraint = override;
- }
- } catch (e) {}
- }
-
- if (session.audioInputChannels) {
- if (constraint.audio === true) {
- constraint.audio = {};
- constraint.audio.channelCount = session.audioInputChannels;
- } else if (constraint.audio) {
- constraint.audio.channelCount = session.audioInputChannels;
- }
- }
- log("CONSTRAINT");
- log(constraint);
- var stream = await navigator.mediaDevices.getUserMedia(constraint).then(function(stream2) {
- return stream2;
- }).catch(function(err) {
- errorlog(err);
- if (!(session.cleanOutput)) {
- if (override !== false) {
- if (err.name) {
- if (err.constraint) {
- warnUser(err['name'] + ": " + err['constraint']);
- }
- }
- }
- }
- }); // More error reporting maybe?
- if (stream) {
- streams.push(stream);
- }
- }
-
- return streams;
-}
-
-function applyMirror(mirror, eleName = 'previewWebcam') { // true unmirrors as its already mirrored
-
- var transFlip = "";
- var transNorm = "";
- if ((eleName == 'videosource') && (session.windowed)) {
- transFlip = " translate(0, 50%)";
- transNorm = " translate(0, -50%)";
- }
-
- if (session.mirrored == 2) {
- mirror = true;
- } else if (session.mirrored === 0) {
- mirror = true;
- }
-
-
- if (mirror) {
- if (session.mirrored && session.flipped) {
- getById(eleName).style.transform = " scaleX(-1) scaleY(-1)" + transFlip;
- getById(eleName).classList.add("mirrorControl");
- } else if (session.mirrored) {
- getById(eleName).style.transform = "scaleX(-1)" + transNorm;
- getById(eleName).classList.add("mirrorControl");
- } else if (session.flipped) {
- getById(eleName).style.transform = "scaleY(-1) scaleX(1)" + transFlip;
- getById(eleName).classList.remove("mirrorControl");
- } else {
- getById(eleName).style.transform = "scaleX(1)" + transNorm;
- getById(eleName).classList.remove("mirrorControl");
- }
- } else {
- if (session.mirrored && session.flipped) {
- getById(eleName).style.transform = " scaleX(1) scaleY(-1)" + transFlip;
- getById(eleName).classList.remove("mirrorControl");
- } else if (session.mirrored) {
- getById(eleName).style.transform = "scaleX(1)" + transNorm;
- getById(eleName).classList.remove("mirrorControl");
- } else if (session.flipped) {
- getById(eleName).style.transform = "scaleY(-1) scaleX(-1)" + transFlip;
- getById(eleName).classList.add("mirrorControl");
- } else {
- getById(eleName).style.transform = "scaleX(-1)" + transNorm;
- getById(eleName).classList.add("mirrorControl");
- }
- }
-}
-
-function cleanupMediaTracks() {
- try {
- if (session.streamSrc) {
- session.streamSrc.getTracks().forEach(function(track) {
- session.streamSrc.removeTrack(track);
- track.stop();
- log("stopping old track");
- });
- }
- if (session.videoElement) {
- session.videoElement.srcObject.getTracks().forEach(function(track) {
- session.videoElement.srcObject.removeTrack(track);
- track.stop();
- log("stopping old track");
- });
- }
- activatedPreview = false;
- } catch (e) {
- errorlog(e);
- }
-}
-
-/// Detect system changes; handle change or use for debugging
-var lastAudioDevice = null;
-var lastVideoDevice = null;
-var lastPlaybackDevice = null;
-
-var audioReconnectTimeout = null;
-var videoReconnectTimeout = null;
-var grabDevicesTimeout = null;
-var playbackReconnectTimeout = null;
-
-function reconnectDevices(event) { /// TODO: Perhaps change this to only if there is a DISCONNECT; rather than ON NEW DEVICE?
- if ((iOS) || (iPad)) {
- // try{
- // session.audioContext.resume();
- // } catch(e){errorlog(e);}
- resetupAudioOut();
- return;
- }
- warnlog("A media device has changed");
-
- if (document.getElementById("previewWebcam")) {
- var outputSelect = document.getElementById("outputSource");
- if (!outputSelect) {
- errorlog("resetup audio failed");
- return;
- }
- try {
- session.sink = outputSelect.options[outputSelect.selectedIndex].value;
- getById("previewWebcam").setSinkId(session.sink).then(() => {}).catch(error => {
- warnlog(error);
- });
- } catch (e) {
- errorlog(e);
- }
- return;
- }
-
-
- if (session.streamSrc === null) {
- return;
- }
- if (document.getElementById("videosource") === null) {
- return;
- }
-
- try {
- session.streamSrc.getTracks().forEach(function(track) {
-
- if (track.readyState == "ended") {
- if (track.kind == "audio") {
- lastAudioDevice = track.label;
- } else if (track.kind == "video") {
- lastVideoDevice = track.label;
- }
- session.streamSrc.removeTrack(track);
- log("remove ended old track");
- }
- });
-
- session.videoElement.srcObject.getTracks().forEach(function(track) {
- if (track.readyState == "ended") {
- session.videoElement.srcObject.removeTrack(track);
- log("remove ended old track");
- }
- });
-
- } catch (e) {
- errorlog(e);
- }
-
- clearTimeout(audioReconnectTimeout);
- audioReconnectTimeout = null;
- if (lastAudioDevice) {
- audioReconnectTimeout = setTimeout(function() { // only reconnect same audio device. If reconnected, clear the disconnected flag.
- enumerateDevices().then(gotDevices2).then(function() {
- // TODO: check to see if any audio is connected?
- var streamConnected = false;
- var audioSelect = document.querySelector("#audioSource3").querySelectorAll("input");
- for (var i = 0; i < audioSelect.length; i++) {
- if (audioSelect[i].value == "ZZZ") {
- continue;
- } else if (audioSelect[i].checked) {
- log("checked");
- streamConnected = true;
- break;
- }
- }
-
- if (!streamConnected) {
- for (var i = 0; i < audioSelect.length; i++) {
- if (audioSelect[i].value == "ZZZ") {
- continue;
- }
- //errorlog(lastAudioDevice + " : " + audioSelect[i].dataset.label);
- if (lastAudioDevice == audioSelect[i].dataset.label) { // if the last disconnected device matches.
- audioSelect[i].checked = true;
- streamConnected = true;
- lastAudioDevice = null;
- warnlog("DISCONNECTED AUDIO DEVICE RECONNECTED");
- //for (var j=0; j
- ${sources.map(({id, name, thumbnail, display_id, appIcon}) => `
-
-
- ${name}
-
SCREEN SHARE
SELECTIONStatistics
";
- var menuCloseBtn = document.createElement("button");
- menuCloseBtn.className="close";
- menuCloseBtn.innerHTML="×";
- menu.appendChild(menuCloseBtn);
-
- var innerMenu = document.createElement("div");
- menu.appendChild(innerMenu);
-
- menuCloseBtn.addEventListener('click', function(eve) {
- clearInterval(menu.interval);
- eve.currentTarget.parentNode.remove();
- });
- return [menu, innerMenu];
-}
-
-
-// WEBCAM
-session.publishStream = function(v, title="Stream Sharing Session"){ // stream is used to generated an SDP
- log("STREAM SETUP");
-
- try {
- //session.streamSrc = v.srcObject; // this shoulnd't be needed
- v.parentNode.removeChild(v); // remove the preview video element after we switch streams to avoid bug on iOS beta 14
- v.className = "";
- } catch (e){
- errorlog(e);
- return;
- }
-
- if (session.transcript){
- setTimeout(function(){setupClosedCaptions();},0);
- }
-
- toggleMute(true); // apply mute state
-
- if (!session.streamSrc){
- log("no stream selected");
- session.streamSrc = new MediaStream(); // this is just for backup.
- }
-
- session.streamSrc.oninactive = function streamoninactive() {
- errorlog('Stream inactive');
- if (session.videoElement.recording){
- session.videoElement.recorder.stop();
- }
- };
-
- if (session.streamSrc.getVideoTracks().length==0){
- warnlog("NO VIDEO TRACK INCLUDED");
- }
-
- if (session.streamSrc.getAudioTracks().length==0){
- warnlog("NO AUDIO TRACK INCLUDED");
- }
-
-
- var container = document.createElement("div");
- container.id = "container";
-
- if (session.cleanOutput){
- container.style.height = "100%";
- v.style.maxWidth = "100%";
- v.style.boxShadow = "none";
- }
-
- container.className = "vidcon";
- getById("gridlayout").appendChild(container);
-
- v.className = "tile";
- v.muted = true;
- v.autoplay = true;
- v.controls = false;
- v.setAttribute("playsinline","");
- v.id = "videosource"; // could be set to UUID in the future
- v.oncanplay = null;
- container.appendChild(v);
-
- if (session.nopreview){
- v.style.display="none";
- container.style.display="none";
- }
-
- changeAudioOutputDevice(v);
-
- if (session.mirrored && session.flipped){
- v.style.transform = "scaleX(1) scaleY(-1) ";
- } else if (session.mirrored){
- v.style.transform = "scaleX(1) ";
- } else if (session.flipped){
- v.style.transform = "scaleY(-1) scaleX(-1)";
- } else {
- v.style.transform = "scaleX(-1) ";
- }
-
- var bigPlayButton = document.getElementById("bigPlayButton");
- if (bigPlayButton){
- bigPlayButton.parentNode.removeChild(bigPlayButton);
- }
-
- session.videoElement = v;
-
- if (session.streamID){
- session.videoElement.dataset.sid = session.streamID;
- }
-
- if (session.director){
- // audio is not mucked with
- } else if (session.scene!==false){
- setTimeout(function(){updateMixer();},1);
- } else if (session.roomid!==false){
- if (session.roomid===""){
- if (!(session.view) || (session.view==="")){
-
-
- if (session.fullscreen){
- session.windowed = false;
- } else {
- v.className = "myVideo";
- session.windowed = true;
- }
- getById("mutespeakerbutton").classList.add("advanced");
-
- applyMirror(session.mirrorExclude, 'videosource');
-
- container.style.width="100%";
- //container.style.height="100%";
-
- container.style.alignItems = "center";
- container.backgroundColor = "#666";
-
- setTimeout(function (){dragElement(v);},1000);
- play();
- } else {
- session.windowed = false;
- applyMirror(session.mirrorExclude, 'videosource');
- play();
- setTimeout(function(){updateMixer();},1);
- }
- } else {
- //session.cbr=0; // we're just going to override it
- if (session.stereo==5){ // not a scene or director, so we will assume its a guest. changing to stereo=3
- session.stereo=3;
- }
- session.windowed = false;
- applyMirror(session.mirrorExclude, 'videosource');
-
- setTimeout(function(){updateMixer();},1);
- }
- } else {
-
-
- if (session.fullscreen){
- session.windowed = false;
- } else {
- v.className = "myVideo";
- session.windowed = true;
- }
- getById("mutespeakerbutton").classList.add("advanced");
-
- applyMirror(session.mirrorExclude, 'videosource');
-
- container.style.width="100%";
- //container.style.height="100%";
- //container.style.display = "flex";
-
- container.style.alignItems = "center";
- container.backgroundColor = "#666";
-
- setTimeout(function (){dragElement(v);},1000);
-
- }
-
- v.onpause = (event) => { // prevent things from pausing; human or other
- if (!((event.ctrlKey) || (event.metaKey) )){
- log("Video paused; auto playing");
- event.currentTarget.play().then(_ => {
- log("playing");
- }).catch(warnlog);
- }
- };
-
- v.addEventListener('click', function(e) {
- log("click");
- try {
- if ((e.ctrlKey)||(e.metaKey)){
- e.preventDefault();
-
- var [menu, innerMenu] = statsMenuCreator();
-
- menu.interval = setInterval(printMyStats,3000, innerMenu);
-
- printMyStats(innerMenu);
- e.stopPropagation();
- return false;
- }
- } catch(e){errorlog(e);}
- });
-
- v.touchTimeOut = null;
- v.touchLastTap = 0;
- v.touchCount = 0;
- v.addEventListener('touchend', function(event) {
- log("touched");
-
- document.ontouchup = null;
- document.onmouseup = null;
- document.onmousemove = null;
- document.ontouchmove = null;
-
- var currentTime = new Date().getTime();
- var tapLength = currentTime - v.touchLastTap;
- clearTimeout(v.touchTimeOut);
- if (tapLength < 500 && tapLength > 0) {
- ///
- log("double touched");
- v.touchCount+=1;
- event.preventDefault();
- if (v.touchCount<5){
- v.touchLastTap = currentTime;
- return false;
- }
- v.touchLastTap = 0;
- v.touchCount=0;
-
- var [menu, innerMenu] = statsMenuCreator();
-
- menu.interval = setInterval(printMyStats,3000, innerMenu);
-
- printMyStats(innerMenu);
- event.stopPropagation();
- return false;
- //////
- } else {
- v.touchCount=1;
- v.touchLastTap = currentTime;
-
- v.touchTimeOut = setTimeout(function(vv) {
- clearTimeout(vv.touchTimeOut);
- vv.touchLastTap = 0;
- vv.touchCount=0;
- }, 5000, v);
-
- }
-
- });
-
- try{
- var m = getById("mainmenu");
- m.remove();
- } catch (e){}
-
- var added = "";
- if (session.defaultPassword===false){
- if (session.password){
- added="&pw="+session.password;
- }
- }
- getById("reshare").href = "https://"+location.host+location.pathname+"?view="+session.streamID+added;
- getById("reshare").text = "https://"+location.host+location.pathname+"?view="+session.streamID+added;
- getById("reshare").style.width = ((getById("reshare").text.length + 1)*1.15 * 8) + 'px';
- pokeIframeAPI('started-camera');
-
-
-
- if (session.videoMutedFlag){
- session.videoMuted = true;
- toggleVideoMute(true);
- }
-
- clearInterval(session.updateLocalStatsInterval);
- session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},3000);
-
- session.title=title;
- session.seeding=true;
- session.seedStream();
-
-};
-
-
-session.publishScreen = function(constraints, title="Screen Sharing Session", audioList=[], audio=true){ // webcam stream is used to generated an SDP
- log("SCREEN SHARE SETUP");
- if (!navigator.mediaDevices.getDisplayMedia){
- setTimeout(function(){warnUser("Sorry, your browser is not supported. Please use the desktop versions of Firefox or Chrome instead");},1);
- return false;
- }
- if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
- if (!ElectronDesktopCapture){
- if (!(session.cleanOutput)) {
- warnUser("The Electron Capture app does not support Screen Capture.");
- }
- warnlog("Electron doesn't support screen capture");
- return false;
- }
- }
-
- var streams = [];
- for (var i=1; i
\
-
and don\'t forget theOBS Browser Source Link:
' + viewstr + ' \
-
\
- This invite link and OBS ingestion link are reusable. Only one person may use a specific invite at a time.Attempting to load video stream.
';
- getById("mainmenu").innerHTML += 'The stream is not available yet or an error occured.
';
-
- }
- }
- } catch (e) {
- errorlog("Error handling QR Code failure");
- }
- }, 15000);
-
- log("auto playing");
- var SafariVer = safariVersion();
- if ((iPad || iOS) && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1 && SafariVer > 13) { // Modern iOS doesn't need pop up
- play();
- } else if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { // Safari on Desktop does require pop up
- if (!(session.cleanOutput)) {
- warnUser("Safari requires us to ask for an audio permission to use peer-to-peer technology. You will need to accept it in a moment if asked to view this live video", 20000);
- }
- navigator.mediaDevices.getUserMedia({
- audio: true
- }).then(function() {
- closeModal();
- play();
- }).catch(function() {
- play();
- });
- } else { // everything else is OK.
- play();
- }
-} else if (session.roomid) {
- try {
- if (session.label === false) {
- if (document.title == "") {
- document.title = "Room=" + session.roomid.toString();
- } else {
- document.title += ": " + session.roomid.toString();
- }
- }
- } catch (e) {
- errorlog(e);
- };
-
-}
-
-
-var vis = (function() {
- var stateKey, eventKey, keys = {
- hidden: "visibilitychange"
- , webkitHidden: "webkitvisibilitychange"
- , mozHidden: "mozvisibilitychange"
- , msHidden: "msvisibilitychange"
- };
- for (stateKey in keys) {
- if (stateKey in document) {
- eventKey = keys[stateKey];
- break;
- }
- }
- return function(c) {
- if (c) {
- document.addEventListener(eventKey, c);
- //document.addEventListener("blur", c);
- //document.addEventListener("focus", c);
- }
- return !document[stateKey];
- };
-})();
-
-(function rightclickmenuthing() { // right click menu
- "use strict";
-
- function clickInsideElement(e, className) {
- var el = e.srcElement || e.target;
-
- if (el.classList.contains(className)) {
- return el;
- } else {
- while (el = el.parentNode) {
- if (el.classList && el.classList.contains(className)) {
- return el;
- }
- }
- }
-
- return false;
- }
-
- function getPosition(event2) {
- var posx = 0;
- var posy = 0;
-
- if (!event2) var event = window.event;
-
- if (event2.pageX || event2.pageY) {
- posx = event2.pageX;
- posy = event2.pageY;
- } else if (event2.clientX || event2.clientY) {
- posx = event2.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
- posy = event2.clientY + document.body.scrollTop + document.documentElement.scrollTop;
- }
-
- return {
- x: posx
- , y: posy
- };
- }
- var contextMenuClassName = "context-menu";
- var contextMenuItemClassName = "context-menu__item";
- var contextMenuLinkClassName = "context-menu__link";
- var contextMenuActive = "context-menu--active";
-
- var taskItemClassName = "task";
- var taskItemInContext;
-
- var clickCoords;
- var clickCoordsX;
- var clickCoordsY;
-
- var menu = document.querySelector("#context-menu");
- var menuItems = menu.querySelectorAll(".context-menu__item");
- var menuState = 0;
- var menuWidth;
- var menuHeight;
- var menuPosition;
- var menuPositionX;
- var menuPositionY;
-
- var windowWidth;
- var windowHeight;
-
- function init() {
- contextListener();
- clickListener();
- keyupListener();
- resizeListener();
- }
-
- function contextListener() {
- document.addEventListener("contextmenu", function(e) {
- taskItemInContext = clickInsideElement(e, taskItemClassName);
-
- if (taskItemInContext) {
- e.preventDefault();
- toggleMenuOn();
- positionMenu(e);
- } else {
- taskItemInContext = null;
- toggleMenuOff();
- }
- });
- }
-
- function clickListener() {
- document.addEventListener("click", function(e) {
- var clickeElIsLink = clickInsideElement(e, contextMenuLinkClassName);
-
- if (clickeElIsLink) {
- e.preventDefault();
- menuItemListener(clickeElIsLink);
- } else {
- var button = e.which || e.button;
- if (button === 1) {
- toggleMenuOff();
- }
- }
- });
- }
-
- function keyupListener() {
- window.onkeyup = function(e) {
- // if ( e.keyCode === 27 ) {
- // toggleMenuOff();
- // }
- if (e.altKey && e.shiftKey && e.keyCode === 67 /* C */) {
- toggleControlBar();
- }
- };
- }
-
- function resizeListener() {
- //window.onresize = function(e) {
- // toggleMenuOff();
- // };
- }
-
- function toggleMenuOn() {
- if (menuState !== 1) {
- menuState = 1;
- menu.classList.add(contextMenuActive);
- }
- }
-
- function toggleMenuOff() {
- if (menuState !== 0) {
- menuState = 0;
- menu.classList.remove(contextMenuActive);
- }
- }
-
- function toggleControlBar() {
- if (getById("controlButtons").style.display != 'none') {
- // Dont hardcode style here. Copy it over to data-style before changing to none;
- getById("controlButtons").dataset.style = getById("controlButtons").style.display;
- getById("controlButtons").style.display = 'none';
- } else {
- // Copy the style over from the data-style attribute.
- getById("controlButtons").style.display = getById("controlButtons").dataset.style;
- };
- }
-
- function positionMenu(e) {
- clickCoords = getPosition(e);
- clickCoordsX = clickCoords.x;
- clickCoordsY = clickCoords.y;
-
- menuWidth = menu.offsetWidth + 4;
- menuHeight = menu.offsetHeight + 4;
-
- windowWidth = window.innerWidth;
- windowHeight = window.innerHeight;
-
- if ((windowWidth - clickCoordsX) < menuWidth) {
- menu.style.left = windowWidth - menuWidth + "px";
- } else {
- menu.style.left = clickCoordsX + "px";
- }
-
- if ((windowHeight - clickCoordsY) < menuHeight) {
- menu.style.top = windowHeight - menuHeight + "px";
- } else {
- menu.style.top = clickCoordsY + "px";
- }
- }
-
- function menuItemListener(link) {
- if (link.getAttribute("data-action") == "Open") {
- window.open(taskItemInContext.value);
- } else {
- // nothing needed
- }
- log("Task ID - " + taskItemInContext + ", Task action - " + link.getAttribute("data-action"));
- toggleMenuOff();
- }
-
- init();
-
-})();
-
-document.addEventListener("dragstart", event => {
- var url = event.target.href || event.target.value;
- if (!url || !url.startsWith('https://')) return;
- if (event.target.dataset.drag != "1") {
- return;
- }
- //event.target.ondragend = function(){event.target.blur();}
-
- var streamId = url.split('view=');
- var label = url.split('label=');
-
- if (session.label !== false) {
- url += '&layer-name=' + session.label;
- } else {
- url += '&layer-name=OBS.Ninja';
- }
- if (streamId.length > 1) url += ': ' + streamId[1].split('&')[0];
- if (label.length > 1) url += ' - ' + decodeURI(label[1].split('&')[0]);
-
- try {
- if (document.getElementById("videosource")) {
- var video = getById('videosource');
- if (typeof(video.videoWidth) == "undefined") {
url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
url += '&layer-height=1080';
- } else if ((parseInt(video.videoWidth) < 360) || (video.videoHeight < 640)) {
- url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
- url += '&layer-height=1080';
- } else {
- url += '&layer-width=' + video.videoWidth; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
- url += '&layer-height=' + video.videoHeight;
}
- } else {
+ } catch (error) {
url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
url += '&layer-height=1080';
}
- } catch (error) {
- url += '&layer-width=1920'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
- url += '&layer-height=1080';
- }
- event.dataTransfer.setDragImage(document.querySelector('#dragImage'), 24, 24);
- event.dataTransfer.setData("text/uri-list", encodeURI(url));
- //event.dataTransfer.setData("url", encodeURI(url));
+ event.dataTransfer.setDragImage( getById('dragImage'), 24, 24);
+ event.dataTransfer.setData("text/uri-list", encodeURI(url));
-});
-
-function popupMessage(e, message = "Copied to Clipboard") { // right click menu
-
- var posx = 0;
- var posy = 0;
-
- if (!e) var e = window.event;
-
- if (e.pageX || e.pageY) {
- posx = e.pageX;
- posy = e.pageY;
- } else if (e.clientX || e.clientY) {
- posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
- posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
- }
-
- posx += 10;
-
-
- var menu = document.querySelector("#messagePopup");
- menu.innerHTML = "
";
- textOverlay.appendChild(spanOverlay);
- textOverlay.style.display = "block";
- var showtime = msg.length * 200 + 3000;
- if (showtime > 8000) {
- showtime = 8000;
+ battery.addEventListener('chargingchange', function() {
+ session.batteryState = {};
+ var miniInfo = {};
+ if ("level" in battery){
+ session.batteryState.level = battery.level;
+ miniInfo.bat = battery.level;
}
- setTimeout(function(ele) {
- ele.parentNode.removeChild(ele);
- }, showtime, spanOverlay);
- }
- }
+ if ("charging" in battery){
+ session.batteryState.charging = battery.charging;
+ miniInfo.chrg = battery.charging;
+ }
+ if (session.batteryState == {}){
+ session.batteryState = null;
+ }
+ session.sendMessage({"miniInfo":miniInfo});
+ });
+
+ battery.addEventListener('levelchange', function(){
+ session.batteryState = {};
+ var miniInfo = {};
+ if ("level" in battery){
+ session.batteryState.level = battery.level;
+ miniInfo.bat = battery.level;
+ }
+ if ("charging" in battery){
+ session.batteryState.charging = battery.charging;
+ miniInfo.chrg = battery.charging;
+ }
+ if (session.batteryState == {}){
+ session.batteryState = null;
+ }
+ session.sendMessage({"miniInfo":miniInfo});
+ });
+
+ });
}
- if (isIFrame) {
- parent.postMessage({
- "gotChat": data
- }, "*");
- }
-
- if (session.chatbutton===false){return;} // messages can still appear as overlays ^
- messageList.push(data);
- messageList = messageList.slice(-100);
-
- if (session.beepToNotify) {
- playtone();
- }
- updateMessages();
-
- if (session.chat == false) {
- getById("chattoggle").className = "las la-comments my-float toggleSize puslate";
- getById("chatbutton").className = "float";
-
- if (getById("chatNotification").value) {
- getById("chatNotification").value = getById("chatNotification").value + 1;
- } else {
- getById("chatNotification").value = 1;
+ var lastTouchEnd = 0;
+ document.addEventListener('touchend', function(event) {
+ var now = (new Date()).getTime();
+ if (now - lastTouchEnd <= 300) {
+ event.preventDefault();
}
- getById("chatNotification").classList.add("notification");
+ lastTouchEnd = now;
+ }, false);
- }
+ document.addEventListener('click', function(event) {
+ if (session.firstPlayTriggered == false) {
+ playAllVideos();
+ session.firstPlayTriggered = true;
+ history.pushState({}, '');
+ }
+ });
- if (session.broadcastChannel !== false) {
- session.broadcastChannel.postMessage(data); /* send */
- }
-
-}
-
-function updateClosedCaptions(msg, label, UUID) {
- msg.counter = parseInt(msg.counter);
- var transcript = sanitizeChat(msg.transcript); // keep it clean.
- if (transcript == "") {
- return;
- }
- transcript = transcript.toUpperCase();
-
- if (label) {
- label = sanitizeLabel(label);
- label = "" + label + ": ";
- } else {
- label = "";
- }
-
- var textOverlay = getById("overlayMsgs");
- if (textOverlay) {
- if (document.getElementById(UUID + "_" + msg.counter)) {
- var spanOverlay = document.getElementById(UUID + "_" + msg.counter);
+ document.addEventListener("keydown", event => {
+ keyDownEvent(event);
+ });
+
+ function keyDownEvent(event){
+
+ if ((event.ctrlKey) || (event.metaKey)) { // detect if CTRL is pressed
+ CtrlPressed = true;
} else {
- var spanOverlay = document.createElement("span");
- spanOverlay.id = UUID + "_" + msg.counter;
- textOverlay.appendChild(spanOverlay);
- textOverlay.style.height = "auto";
- textOverlay.style.textAlign = "left";
- textOverlay.style.display = "block";
- textOverlay.style.position = "relative";
+ CtrlPressed = false;
}
- spanOverlay.innerHTML = label + transcript + "
";
-
- spanOverlay.style.fontSize = (parseInt(session.labelsize || 100) / 100.0 * 4.5) + "vh";
- spanOverlay.style.lineHeight = (parseInt(session.labelsize || 100) / 100 * 6) + "vh";
- spanOverlay.style.margin = (parseInt(session.labelsize || 100) / 100.0 * 0.75) + "vh";
-
- if (msg.isFinal) {
- var showtime = 3000;
- clearTimeout(spanOverlay.timeout);
- spanOverlay.timeout = setTimeout(function(ele) {
- ele.parentNode.removeChild(ele);
- }, showtime, spanOverlay);
+ if (event.altKey) {
+ AltPressed = true;
} else {
- clearTimeout(spanOverlay.timeout);
- spanOverlay.timeout = setTimeout(function(ele) {
- ele.parentNode.removeChild(ele);
- }, 30000, spanOverlay);
- }
-
- }
-}
-
-function updateMessages() {
- if (session.chatbutton===false){return;}
- document.getElementById("chatBody").innerHTML = "";
- for (i in messageList) {
-
- var time = timeSince(messageList[i].time) || "";
- var msg = document.createElement("div");
- //console.log(messageList[i].msg); // Display received messages for View-Only clients.
- /////////////////////////////
- if (messageList[i].type == "sent") {
- msg.innerHTML = messageList[i].msg + " - " + time + "";
- msg.classList.add("outMessage");
- } else if (messageList[i].type == "recv") {
- var label = "";
- if (messageList[i].label) {
- label = messageList[i].label;
- }
- msg.innerHTML = label + messageList[i].msg + " - " + time + "";
- msg.classList.add("inMessage");
- } else if (messageList[i].type == "alert") {
- msg.innerHTML = messageList[i].msg + " - " + time + "";
- msg.classList.add("inMessage");
- } else {
- msg.innerHTML = messageList[i].msg;
- msg.classList.add("outMessage");
- }
-
- document.getElementById("chatBody").appendChild(msg);
- }
- if (chatUpdateTimeout) {
- clearInterval(chatUpdateTimeout);
- }
- document.getElementById("chatBody").scrollTop = document.getElementById("chatBody").scrollHeight;
- chatUpdateTimeout = setTimeout(function() {
- updateMessages();
- }, 60000);
-}
-
-function EnterButtonChat(event) {
- // Number 13 is the "Enter" key on the keyboard
- var key = event.which || event.keyCode;
- if (key === 13) {
- // Cancel the default action, if needed
- event.preventDefault();
- // Trigger the button element with a click
- sendChatMessage();
- }
-}
-
-function showCustomizer(arg, ele) {
- //getById("directorLinksButton").innerHTML=' LINKS (GUEST INVITES & SCENES)'
- getById("showCustomizerButton1").style.backgroundColor = "";
- getById("showCustomizerButton2").style.backgroundColor = "";
- getById("showCustomizerButton3").style.backgroundColor = "";
- getById("showCustomizerButton4").style.backgroundColor = "";
- getById("showCustomizerButton1").style.boxShadow = "";
- getById("showCustomizerButton2").style.boxShadow = "";
- getById("showCustomizerButton3").style.boxShadow = "";
- getById("showCustomizerButton4").style.boxShadow = "";
-
-
- if (getById("customizeLinks" + arg).style.display != "none") {
- getById("customizeLinks").style.display = "none";
- getById("customizeLinks" + arg).style.display = "none";
- } else {
- //directorLinks").style.display="none";
- getById("showCustomizerButton" + arg).style.backgroundColor = "#1e0000";
- getById("showCustomizerButton" + arg).style.boxShadow = "inset 0px 0px 1px #b90000";
- getById("customizeLinks1").style.display = "none";
- getById("customizeLinks3").style.display = "none";
- getById("customizeLinks").style.display = "block";
- getById("customizeLinks" + arg).style.display = "block";
- }
-}
-
-
-var defaultRecordingBitrate = false;
-
-function recordVideo(target, event, videoKbps = false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
-
- var UUID = target.dataset.UUID;
- var video = session.rpcs[UUID].videoElement;
- var audioKbps = false;
-
- if (event === null) {
- if (defaultRecordingBitrate === null) {
- updateLocalRecordButton(UUID, -1);
- //target.style.backgroundColor = null;
- //target.innerHTML = ' record local';
- return;
- }
- } else if ((event.ctrlKey) || (event.metaKey)) {
- updateLocalRecordButton(UUID, -3);
- //target.innerHTML = ' ARMED';
- //target.style.backgroundColor = "#BF3F3F";
- Callbacks.push([recordVideo, target, null, false]);
- log("Record Video queued");
- defaultRecordingBitrate = false;
- return;
- } else {
- defaultRecordingBitrate = false;
- }
-
- log("Record Video Clicked");
- if ("recording" in video) {
- log("ALREADY RECORDING!");
- //target.style.backgroundColor = null;
- //target.innerHTML = ' record local';
- updateLocalRecordButton(UUID, -2);
- video.recorder.stop();
- session.requestRateLimit(35, UUID); // 100kbps
- if (session.audiobitrate===false){
- session.requestAudioRateLimit(-1,UUID);
+ AltPressed = false;
}
- var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.add("pressed");
+ if (event.key === "Escape") {
+ if (document.fullscreenElement) {
+ document.exitFullscreen();
+ //updateMixer();
+ }
+ return;
}
- var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.remove("pressed");
- }
- var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.remove("pressed");
- }
- return;
- } else {
- updateLocalRecordButton(UUID, 0);
- //target.style.backgroundColor = "#FCC";
- //target.innerHTML = " Download";
- video.recording = true;
- }
-
- video.recorder = {};
-
- if (videoKbps == false) {
- if (defaultRecordingBitrate == false) {
- videoKbps = 4000; // 4mbps recording bitrate
- videoKbps = prompt("Press OK to start recording. Press again to stop and download.\n\nWarning: Keep this browser tab active to continue recording.\n\nYou can change the default video bitrate if desired below (kbps)", videoKbps);
- if (videoKbps === null) {
- //target.style.backgroundColor = null;
- //target.innerHTML = ' record local';
- updateLocalRecordButton(UUID, -1);
- target.style.backgroundColor = "";
- delete(video.recorder);
- delete(video.recording);
- defaultRecordingBitrate = null;
+
+ if (session.disableHotKeys){return;}
+
+ if (PPTHotkey){
+ if (event.target && (event.target.tagName == "INPUT")){
+ // skip, since an input field is selected
+ } else if ((PPTHotkey.ctrl === event.ctrlKey) && (PPTHotkey.alt === AltPressed) && (PPTHotkey.meta === event.metaKey) && ((PPTHotkey.key===false) || ((PPTHotkey.key!==false) && (PPTHotkey.key === event.key)))){
+ if (session.muted && !PPTKeyPressed){
+ session.muted = false;
+ PPTKeyPressed = true;
+ getById("mutebutton").classList.add("PPTActive");
+ toggleMute(true);
+ } else if (!PPTKeyPressed){
+ PPTKeyPressed = true;
+ getById("mutebutton").classList.add("PPTActive");
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ } else if (PPTKeyPressed){
+ PPTKeyPressed = false;
+ getById("mutebutton").classList.remove("PPTActive");
+ if (!session.muted){
+ session.muted = true;
+ toggleMute(true);
+
+ }
+ event.preventDefault();
+ event.stopPropagation();
return;
}
- videoKbps = parseInt(videoKbps);
- defaultRecordingBitrate = videoKbps;
- } else {
- videoKbps = defaultRecordingBitrate;
}
- }
- if (videoKbps <= 0) {
- audioKbps = videoKbps * (-1);
- videoKbps = false;
- if (session.audiobitrate===false){
- if ((audioKbps>0) && (audioKbps>=128)){
- session.requestAudioRateLimit(128,UUID); // no point going higher
- } else if (audioKbps==0){
- session.requestAudioRateLimit(256,UUID); // PCM
- } else {
- session.requestAudioRateLimit(parseInt(audioKbps),UUID); // exact? sure. why not.
- }
- }
- } else if (videoKbps < 50) { // this just makes sure you can't set 0 on the record bitrate.
- videoKbps = 50;
- session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
- } else {
- session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
-
- if (videoKbps>4000){
- if (session.audiobitrate===false){
- if (session.pcm){
- session.requestAudioRateLimit(256,UUID);
- } else {
- session.requestAudioRateLimit(128,UUID);
- }
- }
- } else if (videoKbps>2500){
- if (session.audiobitrate===false){
- if (session.pcm){
- session.requestAudioRateLimit(256,UUID);
- } else {
- session.requestAudioRateLimit(80,UUID);
- }
- }
- }
-
- }
-
- var timestamp = Date.now();
- var filename = "";
- if (session.rpcs[UUID].label || session.rpcs[UUID].streamID) {
- filename = session.rpcs[UUID].label || session.rpcs[UUID].streamID;
- filename = filename.replace(/[\W]+/g, "_");
- filename = filename.substring(0, 200);
- }
-
- filename += "_" + timestamp.toString();
-
- var cancell = false;
- if (typeof video.srcObject === "undefined" || !video.srcObject) {
- return;
- }
-
- const {readable, writable} = new TransformStream({
- transform: (chunk, ctrl) => chunk.arrayBuffer().then(b => ctrl.enqueue(new Uint8Array(b)))
- });
- readable.pipeTo(streamSaver.createWriteStream(filename + '.webm'));
- var writer = writable.getWriter();
- video.recorder.writer = writer;
- video.recorder.stop = function() {
- if (!video.recording) {
- errorlog("ALREADY STOPPED");
- updateLocalRecordButton(UUID, -1);
+ if (KeyPressedTimeout || PPTKeyPressed){
+ event.preventDefault();
+ event.stopPropagation();
return;
}
- video.recording = false;
- updateLocalRecordButton(UUID, -2);
- try {
- if (video.recorder.mediaRecorder.state !== "inactive") {
- video.recorder.mediaRecorder.stop();
- }
- } catch (e) {
- errorlog(e);
- }
- session.requestRateLimit(35, UUID); // 100kbps
- if (session.audiobitrate===false){
- session.requestAudioRateLimit(-1,UUID);
- }
- var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.add("pressed");
- }
- var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.remove("pressed");
- }
- var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- elements[0].classList.remove("pressed");
- }
-
- cancell = true;
- // log('Recorded Blobs: ', recordedBlobs);
- // download();
- setTimeout(() => {
- writer.close();
- updateLocalRecordButton(UUID, -1);
- delete(video.recorder);
- delete(video.recording);
- }, 1200);
- };
-
- let options = {};
-
- if (videoKbps) {
- options.mimeType = "video/webm";
- if (session.pcm){
- options.mimeType += ";codecs=pcm";
- }
- if (videoKbps < 1000) {
- options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
- } else {
- options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
- }
- video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
- } else {
- options.mimeType = "audio/webm";
- if (audioKbps == 0) {
- if (MediaRecorder.isTypeSupported("audio/webm;codecs=pcm")) {
- options.mimeType = "audio/webm;codecs=pcm";
- }
- } else {
- options.bitsPerSecond = parseInt(audioKbps * 1024);
- }
- var stream = new MediaStream();
- video.srcObject.getAudioTracks().forEach((track) => {
- stream.addTrack(track, video.srcObject);
- });
- video.recorder.mediaRecorder = new MediaRecorder(stream, options);
- }
- log(options);
-
- function download() {
- const blob = new Blob(recordedBlobs, {
- type: "video/webm"
- });
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.style.display = 'none';
- a.href = url;
- a.download = filename + ".webm";
- document.body.appendChild(a);
- a.click();
- setTimeout(() => {
- document.body.removeChild(a);
- window.URL.revokeObjectURL(url);
- }, 100);
- }
-
- function handleDataAvailable(event) {
- if (event.data && event.data.size > 0) {
- //recordedBlobs.push(event.data);
- writer.write(event.data); ////////////
- if (video.recording) {
- updateLocalRecordButton(UUID, (parseInt((Date.now() - timestamp) / 1000) || 0));
- }
- }
- }
-
- video.recorder.mediaRecorder.ondataavailable = handleDataAvailable;
-
- video.recorder.mediaRecorder.onerror = function(event) {
- errorlog(event);
- video.recorder.stop();
- session.requestRateLimit(35, UUID);
- if (!(session.cleanOutput)) {
- setTimeout(function() {
- warnUser("an error occured with the media recorder; stopping recording");
- }, 1);
- }
- };
-
- video.srcObject.ended = function(event) {
- video.recorder.stop();
- session.requestRateLimit(35, UUID);
- if (!(session.cleanOutput)) {
- setTimeout(function() {
- warnUser("stream ended! stopping recording");
- }, 1);
- }
- };
-
-
- setTimeout(function(v) {
- v.recorder.mediaRecorder.start(1000);
- }, 500, video); // 100ms chunks
-
- return;
-}
-
-function updateRemoteRecordButton(UUID, recorder) {
- var elements = document.querySelectorAll('[data-action-type="recorder-remote"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- var time = parseInt(recorder) || 0;
- if (time == -3) {
- elements[0].classList.remove("pressed");
- elements[0].disabled = true;
- elements[0].innerHTML = ' Not Supported';
- if (!(session.cleanOutput)) {
- setTimeout(function() {
- warnUser('The remote browser does not support recording.\n\nPerhaps try local recording instead.');
- }, 0);
- }
-
- } else if (time == -2) {
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' stopping...';
- } else if (time == -1) {
- elements[0].classList.remove("pressed");
- elements[0].innerHTML = ' Record Remote';
- } else {
- var minutes = Math.floor(time / 60);
- var seconds = time - minutes * 60;
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' ' + minutes + "m : " + (seconds + "").padStart(2, '0') + "s";
- }
- }
-}
-
-function updateLocalRecordButton(UUID, recorder) {
- var elements = document.querySelectorAll('[data-action-type="recorder-local"][data--u-u-i-d="' + UUID + '"]');
- if (elements[0]) {
- var time = parseInt(recorder) || 0;
-
- //target.innerHTML = ' ARMED';
- //
- if (time == -3) {
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' ARMED';
- elements[0].style.backgroundColor = "#BF3F3F";
- } else if (time == -2) {
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' stopping...';
- elements[0].style.backgroundColor = "";
- } else if (time == -1) {
- elements[0].classList.remove("pressed");
- elements[0].innerHTML = ' Record Local';
- elements[0].style.backgroundColor = "";
- } else {
- var minutes = Math.floor(time / 60);
- var seconds = time - minutes * 60;
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' ' + minutes + "m : " + (seconds + "").padStart(2, '0') + "s";
- elements[0].style.backgroundColor = "";
- }
- }
-}
-
-function recordLocalVideoToggle() {
- var ele = getById("recordLocalbutton");
- if (ele.dataset.state == "0") {
- ele.dataset.state = "1";
- ele.style.backgroundColor = "red";
- ele.innerHTML = '';
- if ("recording" in session.videoElement) {
-
- } else {
- recordLocalVideo("start");
- }
-
- if (session.director){
- var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
- if (elements[0]) {
- elements[0].classList.add("pressed");
- elements[0].innerHTML = ' Record';
- }
- }
-
- } else {
- if ("recording" in session.videoElement) {
- recordLocalVideo("stop");
- }
- ele.dataset.state = "0";
- ele.style.backgroundColor = "";
- ele.innerHTML = '';
-
- if (session.director){
- var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
- if (elements[0]) {
- elements[0].classList.remove("pressed");
- elements[0].innerHTML = ' Record';
- }
- }
- }
-
-}
-
-function setupSensorData(pollrate = 30) {
- session.sensors = {};
- session.sensors.data = {};
- session.sensors.data.sensors = true;
-
- if (window.Accelerometer) {
- session.sensors.data.acc = {};
- session.sensors.Accelerometer = new Accelerometer({
- frequency: pollrate
- });
- session.sensors.Accelerometer.addEventListener('reading', e => {
- session.sensors.data.acc.x = session.sensors.Accelerometer.x;
- session.sensors.data.acc.y = session.sensors.Accelerometer.y;
- session.sensors.data.acc.z = session.sensors.Accelerometer.z;
- session.sensors.data.acc.t = parseInt(Math.round(session.sensors.Accelerometer.timestamp));
- });
- session.sensors.Accelerometer.start();
- }
- if (window.Gyroscope) {
- session.sensors.data.gyro = {};
- session.sensors.Gyroscope = new Gyroscope({
- frequency: pollrate
- });
- session.sensors.Gyroscope.addEventListener('reading', e => {
- session.sensors.data.gyro.x = session.sensors.Gyroscope.x;
- session.sensors.data.gyro.y = session.sensors.Gyroscope.y;
- session.sensors.data.gyro.z = session.sensors.Gyroscope.z;
- session.sensors.data.gyro.t = parseInt(Math.round(session.sensors.Gyroscope.timestamp));
- });
- session.sensors.Gyroscope.start();
- }
- if (window.Magnetometer) {
- session.sensors.data.mag = {};
- session.sensors.Magnetometer = new Magnetometer({
- frequency: pollrate
- });
- session.sensors.Magnetometer.addEventListener('reading', e => {
- session.sensors.data.mag.x = session.sensors.Magnetometer.x;
- session.sensors.data.mag.y = session.sensors.Magnetometer.y;
- session.sensors.data.mag.z = session.sensors.Magnetometer.z;
- session.sensors.data.mag.t = parseInt(Math.round(session.sensors.Magnetometer.timestamp));
-
- });
- session.sensors.Magnetometer.start();
- }
- if (window.LinearAccelerationSensor) {
- session.sensors.data.lin = {};
- session.sensors.LinearAccelerationSensor = new LinearAccelerationSensor({
- frequency: pollrate
- });
- session.sensors.LinearAccelerationSensor.addEventListener('reading', e => {
- session.sensors.data.lin.x = session.sensors.LinearAccelerationSensor.x;
- session.sensors.data.lin.y = session.sensors.LinearAccelerationSensor.y;
- session.sensors.data.lin.z = session.sensors.LinearAccelerationSensor.z;
- session.sensors.data.lin.t = parseInt(Math.round(session.sensors.LinearAccelerationSensor.timestamp));
- });
- session.sensors.LinearAccelerationSensor.start();
- }
- setInterval(function() {
- firehoseSensorData();
- }, parseInt(1000 / pollrate));
-}
-
-
-function firehoseSensorData() {
- session.sendMessage(session.sensors.data);
-}
-if (session.sensorData) {
- setupSensorData(parseInt(session.sensorData));
-}
-
-async function chunkedVideoTransfer(videoKbps = 500) {
-
- var video = session.videoElement;
- var recorder = {};
-
- var options = {};
- options.mimeType = "video/webm";
- if (videoKbps < 1000) {
- options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
- } else {
- options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
- }
-
- recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
-
- async function handleDataAvailable(event) {
- if (event.data && event.data.size > 0) {
- try{
- recorder.mediaRecorder.stop();
- setTimeout(function(){recorder.mediaRecorder.start(500);},500);
- } catch(e){
+ if (CtrlPressed && event.keyCode) {
+ if (event.keyCode == 77) { // M
+ if (event.metaKey) {
+ if (AltPressed) {
+ if (!KeyPressedTimeout){
+ toggleMute(); // macOS
+ KeyPressedTimeout = Date.now();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ }
+ } else {
+ if (!KeyPressedTimeout){
+ toggleMute(); // Windows
+ KeyPressedTimeout = Date.now();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ }
+
+ } else if (event.keyCode == 66) { // B
+ toggleVideoMute();
+ event.preventDefault();
+ event.stopPropagation();
return;
}
- const arrayBuffer = await event.data.arrayBuffer();
- for (var i in session.pcs){
- session.pcs[i].sendChannel.send(arrayBuffer);
-
+ if (AltPressed){ // CTRL + ALT
+ if (event.keyCode == 70) { // F
+ toggleFileshare()();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ } else if (event.keyCode == 67) { // C
+ cycleCameras();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ } else if (event.keyCode == 83) { // S
+ toggleScreenShare()();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ } else if (event.keyCode == 68) { // S
+ if (!drawOnScreenObject){
+ drawOnScreen();
+ } else {
+ drawOnScreenObject.stop();
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
}
}
+ }
+
+ document.addEventListener("keyup", event => {
+
+ if (PPTKeyPressed){
+ PPTKeyPressed = false;
+ getById("mutebutton").classList.remove("PPTActive");
+ if (!session.muted){
+ session.muted = true;
+ toggleMute(true);
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
- }
-
- recorder.mediaRecorder.ondataavailable = handleDataAvailable;
-
- recorder.mediaRecorder.onerror = function(event) {
- errorlog(event);
- };
-
- video.srcObject.ended = function(event) {
- errorlog(event);
- };
-
- recorder.mediaRecorder.start(500); // 100ms chunks
-
-}
-
-function recordLocalVideo(action = null, videoKbps = 6000) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
- var audioKbps = false;
- var video = session.videoElement;
- if ("recording" in video) {
- if (action == "stop") {
- log("Stopping RECORDING!");
- video.recorder.stop();
- delete(video.recorder);
- delete(video.recording);
- return;
- } else if (action == "start") {
- log("ALREADY RECORDING!");
- getById("recordLocalbutton").dataset.state = "1";
- getById("recordLocalbutton").style.backgroundColor = "red";
- getById("recordLocalbutton").innerHTML = '';
- return;
- } else {
- log("STOPPING RECORDING by default toggle!");
- video.recorder.stop();
- return;
- }
- return;
- } else if (action == "start") {
- if (safariVersion()) {
- var msg = {};
- msg.UUID = session.directorUUID;
- msg.recorder = -3;
- session.sendMessage(msg, msg.UUID);
- return;
- }
- video.recording = true;
- getById("recordLocalbutton").dataset.state = "1";
- getById("recordLocalbutton").style.backgroundColor = "red";
- getById("recordLocalbutton").innerHTML = '';
- } else if (action == "stop") {
- return;
- } else {
- getById("recordLocalbutton").dataset.state = "1";
- getById("recordLocalbutton").style.backgroundColor = "red";
- getById("recordLocalbutton").innerHTML = '';
- video.recording = true;
- }
-
- video.recorder = {};
-
- if (session.recordLocal !== false) {
- videoKbps = session.recordLocal;
- }
-
- if (videoKbps <= 0) {
- audioKbps = videoKbps * (-1);
- videoKbps = false;
- } else if (videoKbps < 50) { // this just makes sure you can't set 0 on the record bitrate.
- videoKbps = 50;
- }
-
- if (typeof video.srcObject === "undefined" || !video.srcObject) {
- return;
- }
-
- const {readable, writable} = new TransformStream({
- transform: (chunk, ctrl) => chunk.arrayBuffer().then(b => ctrl.enqueue(new Uint8Array(b)))
- });
-
- var timestamp = Date.now();
- var filename = "";
- if (session.label || session.streamID) {
- filename = session.label || session.streamID;
- filename = filename.replace(/[\W]+/g, "_");
- filename = filename.substring(0, 200);
- }
-
- filename += "_" + timestamp.toString();
- readable.pipeTo(streamSaver.createWriteStream(filename.toString() + '.webm'));
-
- var writer = writable.getWriter();
- video.recorder.writer = writer;
- pokeIframeAPI("recording-started");
-
- video.recorder.stop = function(restart = false) {
- if (restart) {
- if (getById("recordLocalbutton").dataset.state == 2) {
- getById("recordLocalbutton").dataset.state = "0";
- getById("recordLocalbutton").style.backgroundColor = "";
- getById("recordLocalbutton").innerHTML = '';
- restart = false;
- warnUser("Media Recording Stopped due to an error.");
- } else {
- getById("recordLocalbutton").innerHTML = '';
- getById("recordLocalbutton").dataset.state = "2";
- }
- } else {
- getById("recordLocalbutton").dataset.state = "0";
- getById("recordLocalbutton").style.backgroundColor = "";
- getById("recordLocalbutton").innerHTML = '';
- }
- if (!video.recording) {
- errorlog("ALREADY STOPPED");
- return;
- }
- video.recording = false;
- try {
- if (video.recorder.mediaRecorder.state !== "inactive") {
- video.recorder.mediaRecorder.stop();
- }
- } catch (e) {
- errorlog(e);
- }
-
- setTimeout(() => {
- writer.close();
- pokeIframeAPI("recording-stopped");
- try {
- if (session.directorUUID) {
- var msg = {};
- msg.UUID = session.directorUUID;
- msg.recorder = -1;
- session.sendMessage(msg, msg.UUID);
+ if (!(event.ctrlKey || event.metaKey)) {
+ if (CtrlPressed) {
+ CtrlPressed = false;
+ for (var i in Callbacks) {
+ var cb = Callbacks[i];
+ cb[0](...cb.slice(1)); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#A_better_apply
}
+ Callbacks = [];
+ }
+ }
+ if (!(event.altKey)) {
+ AltPressed = false;
+ }
+
+ if (event.altKey && event.shiftKey && event.keyCode === 67 /* C */) {
+ toggleControlBar();
+ }
+ if (KeyPressedTimeout && ((event.keyCode == 77) || (!(event.ctrlKey || event.metaKey)))) {
+ if (Date.now() - KeyPressedTimeout>300){
+ toggleMute();
+ }
+ if (event.keyCode == 77){
+ KeyPressedTimeout = 0;
+ }
+ }
+ });
+
+ setTimeout(function(){ // lets lazy load the following..
+ window.addEventListener("beforeunload", confirmUnload); // This just keeps people from killing the live stream accidentally. Also give me a headsup that the stream is ending
+ window.addEventListener("unload", function(e) {
+ try {
+ if (session.ws){
+ session.ws.close();
+ }
+ if (session.videoElement.recording) {
+ session.videoElement.recorder.writer.close();
+ session.videoElement.recording = false;
+ }
+ for (var i in session.rpcs) {
+ if (session.rpcs[i].videoElement) {
+ if (session.rpcs[i].videoElement.recording) {
+ session.rpcs[i].videoElement.recorder.writer.close();
+ session.rpcs[i].videoElement.recording = false;
+ }
+ }
+ }
+ session.hangup();
} catch (e) {
errorlog(e);
}
- delete(video.recorder);
- delete(video.recording);
-
- if (restart) {
- setTimeout(function() {
- recordLocalVideo("start", videoKbps);
- }, 0);
- }
-
- }, 500);
+ });
+
try {
- if (session.directorUUID) {
- var msg = {};
- msg.UUID = session.directorUUID;
- msg.recorder = -2;
- session.sendMessage(msg, msg.UUID);
- }
- } catch (e) {
- errorlog(e);
- }
-
- };
-
- let options = {};
-
- if (videoKbps) {
- options.mimeType = "video/webm";
- if (session.pcm){
- options.mimeType += ";codecs=pcm";
- }
- if (videoKbps < 1000) {
- options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
- } else {
- options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
- }
- video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
- } else {
- options.mimeType = "audio/webm";
- if (audioKbps == 0) {
- if (MediaRecorder.isTypeSupported("audio/webm;codecs=pcm")) {
- options.mimeType = "audio/webm;codecs=pcm";
- }
- } else {
- options.bitsPerSecond = parseInt(audioKbps * 1024);
- }
- var stream = new MediaStream();
- video.srcObject.getAudioTracks().forEach((track) => {
- stream.addTrack(track, video.srcObject);
- });
- video.recorder.mediaRecorder = new MediaRecorder(stream, options);
- }
- log(options);
-
- function handleDataAvailable(event) {
- if (event.data && event.data.size > 0) {
- writer.write(event.data);
- if (session.directorUUID) {
- if (video.recording) {
- var msg = {};
- msg.UUID = session.directorUUID;
- msg.recorder = parseInt((Date.now() - timestamp) / 1000) || 0;
- session.sendMessage(msg, msg.UUID);
- }
- }
- }
- }
-
- video.recorder.mediaRecorder.ondataavailable = handleDataAvailable;
-
- video.recorder.mediaRecorder.onerror = function(event) {
- errorlog(event);
- video.recorder.stop(true);
- };
-
- video.srcObject.ended = function(event) {
- video.recorder.stop();
- };
-
- video.recorder.mediaRecorder.start(1000); // 100ms chunks
-
- if (session.directorUUID) {
- var msg = {};
- msg.UUID = session.directorUUID;
- msg.recorder = 0;
- session.sendMessage(msg, msg.UUID);
- }
- return;
-}
-
-
-function changeAudioOutputDevice(ele) {
- if (session.sink){
- if ((iOS) || (iPad)){return;} // iOS devices do not support this.
-
- if (typeof ele.sinkId !== 'undefined'){
- navigator.mediaDevices.getUserMedia({audio:true,video:false}).then(function (stream){
- ele.setSinkId(session.sink).then(() => {
- log("New Output Device:"+session.sink);
- }).catch(errorlog);
- stream.getTracks().forEach(track => {
- track.stop();
- });
- }).catch(function canplayspecificaudio(){errorlog("Can't play out to specific audio device without mic permissions allowed");});
- } else {
- warnlog("Your browser does not support alternative audio sources.");
- }
- }
-}
-
-function addAudioPipeline(stream, UUID, track){ // INBOUND AUDIO EFFECTS
- try{
- log("Triggered webaudio effects path");
-
- if (session.audioEffects!==true){ // audio effects is not enable. Do not apply.
- errorlog("Add Audio Pipeline tried to add effects but should be disabled?");
- return stream;
- }
- for (var tid in session.rpcs[UUID].inboundAudioPipeline){
- delete session.rpcs[UUID].inboundAudioPipeline[tid]; // get rid of old nodes.
- }
- var trackid = track.id;
- session.rpcs[UUID].inboundAudioPipeline[trackid] = {};
-
- session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream = new MediaStream();
- session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream.addTrack(track);
- session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio = new Audio();
- session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.muted = true;
- session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.srcObject = session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream; // needs to be added as an streamed element to be usable, even if its hidden
-
- session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.play().then(_ => {
- log("playing");
- }).catch(warnlog);
-
- // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamTrackSource
- source = session.audioCtx.createMediaStreamSource(session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream);
-
- //////////////////
-
- var screwedUp = false;
- session.rpcs[UUID].inboundAudioPipeline[trackid].destination = false;
- if (session.sync!==false){
- log("adding a delay node to audio");
- source = addDelayNode( source, UUID, trackid);
- screwedUp = true;
- }
-
- if (session.style===2){
- log("adding a fftwave node to audio");
- source = fftWaveform( source, UUID, trackid);
- } else if (session.style===3){
- log("adding a loudness meter node to audio");
- source = audioMeterGuest(source, UUID, trackid);
- } else if (session.audioMeterGuest){
- log("adding a loudness meter node to audio");
- source = audioMeterGuest(source, UUID, trackid);
- } else if (session.activeSpeaker){
- log("adding a loudness meter node to audio");
- source = audioMeterGuest(source, UUID, trackid);
- }
-
- if (session.rpcs[UUID].channelOffset !== false){
- log("custom offset set");
- session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
- source = offsetChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.rpcs[UUID].channelOffset);
- screwedUp = true;
- } else if (session.offsetChannel !== false){ // proably better to do this last.
- log("adding offset channels");
- session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
- source = offsetChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.offsetChannel);
- screwedUp = true;
- }
-
- if (screwedUp){
- if (session.rpcs[UUID].inboundAudioPipeline[trackid].destination===false){
- session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
- }
- source.connect(session.rpcs[UUID].inboundAudioPipeline[trackid].destination);
- stream.getTracks().forEach((trk)=>{
- if (trackid != trk.id){
- session.rpcs[UUID].inboundAudioPipeline[trackid].destination.stream.addTrack(trk);
- log("secondary stream added");
- log(trk);
- }
- });
-
- return session.rpcs[UUID].inboundAudioPipeline[trackid].destination.stream;
- }
- return stream;
- } catch(e) {errorlog(e);}
- return stream;
-}
-
-function changeChannelOffset(UUID, channel){
-
-
- var ele = document.querySelectorAll('[data-action-type="add-channel"][data--u-u-i-d="' + UUID + '"]');
- for (var i=0;i
+ VDO.Ninja MIDI test app
+
+ About
+
You can download a virtual MIDI I/O controller for Windows here:
+ http://www.tobias-erichsen.de/software/loopmidi.html
+
This code uses the WebMIDI.js library, referenced here:
+ https://github.com/djipco/webmidi
+
+ Below you can test the MIDI hotkey commands for VDO.Ninja below:
+
+ The idea of this app is to select a MIDI output loopback device. Pressing any of the below buttons will trigger the respective MIDI note/command, sending it to the output MIDI device.
+ Since the MIDI output device should be a loopback device, you can select the MIDI loopback device in VDO.Ninja as its MIDI input source.
+
Refer to the documentation for what a MIDI note/value does in VDO.Ninja, but it can be used to control the director's room, either locally or remotely.
+ Select the MIDI Output device:
+ &midi=1
+ &midi=3
+ &midi=4 ; director
+ &midi=4 ; guest 1
+ &midi=4 ; guest 2
+ Sample Remote Director Control links
+ General Settings
+ Aspect Ratio
+ 16:9
+ 9:16
+ 1:1
+
This just impacts the aspect ratio of the local preview.
+
+ Canvas Size
+ 720p
+ 1080p
+
+ Unit type for saved scene edits 📏
+ Relative (Percentage)
+ Absolute (Pixels)
+
+ Layout switching
+ Update layout on a slot change
+ Linked OBS scenes
+ Activate linked OBS scene on layout change
+ Slot assignment
+ Assign a slot to new guests automatically
+ Show advanced controls
+ Show the advanced director control options
+
+ 🗑 Remove all Layouts
+
+ 🚿 Load Default Layouts
+
+
+ 📤 Export settings
+
+ 📥 Import settings
+ Import scenes and settings from local file:
+ Room Setup
+
+
+ Invite options
+ Your room name is: ROOMNAME
+ Copy invite to clipboard
+ Invite Link
+
+ Customize invite link
+
+
+
+
+ You can manually customize the invite link further; see the documentation at docs.vdo.ninja
+
+
+ Testing Regions for VDO.Ninja Tech Check
+
General Settings
+ Remove all Layouts
+
+ Load Default Layouts
+
+
+ Export settings
+
+ Import settings
+ Import buttons and settings from local file:
+ Learn a button
+
+
+ Press a button on a MIDI controller to capture it's value
+
+
+ Note
+
+
+
+
+
+
+
+
+
+ Control Change
+
+
+
+
+
+
+
+ Raw MIDI Command
+
+
+
+
+
+
+ Raw MIDI Command - Range of Values
+
+
+
+
+
+
+
+
+ Video and stream quality check results
+
+ Bitrate (kbps)
+ 0
+
+ Buffer delay (ms)
+ 0
+
+ Packet Loss (%)
+ 0
+
+
+ VDO.Ninja's video streaming quality test
+
+ Bitrate (kbps)
+ 0
+
+ Buffer delay (ms)
+ 0
+
+ Packet Loss (%)
+ 0
+
+ Log
+
+
How to use
+
+
+ Unsupervised guest check option
+
+ You can also use this tool to have a guest perform a system and connection test, with those results being available to you via a result's page for up to a week.
+ You can either ask the remote guest to send you the link to their results page when they complete the test, or you can use check.html?id=xxx to pre-assign the test ID (ie: xxx), which will have the results then be available at results.html?id=xxx.
+
If you have questions, join the Discord here.
+
+
+ More testing options below
+
+
+
+ Testing location:
+
- OBS.Ninja Speed Test
-
- Bitrate (kbps)
- 0
-
- Buffer delay (ms)
- 0
-
- Packet Loss (%)
- 0
-
- Log
-
-
How to use
-
-
-
-