From eee79d2ef95a52067c9d618bc69531ddb105fd29 Mon Sep 17 00:00:00 2001 From: SilverServerT <78113640+SilverServerT@users.noreply.github.com> Date: Wed, 27 Jan 2021 22:57:28 +0100 Subject: [PATCH 01/30] Update nl.json --- translations/nl.json | 212 +++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/translations/nl.json b/translations/nl.json index 4cb6d40..d0f47e4 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -1,90 +1,90 @@ { "titles": { - "toggle-the-chat": "Toggle the Chat", - "mute-the-speaker": "Mute the Speaker", - "mute-the-mic": "Mute the Mic", - "disable-the-camera": "Disable the Camera", - "settings": "Settings", - "hangup-the-call": "Hangup the Call", - "show-help-info": "Show Help Info", - "language-options": "Language Options", - "tip-hold-ctrl-command-to-select-multiple": "tip: Hold CTRL (command) to select Multiple", - "ideal-for-1080p60-gaming-if-your-computer-and-upload-are-up-for-it": "Ideal for 1080p60 gaming, if your computer and upload are up for it", - "better-video-compression-and-quality-at-the-cost-of-increased-cpu-encoding-load": "Better video compression and quality at the cost of increased CPU encoding load", - "disable-digital-audio-effects-and-increase-audio-bitrate": "Disable digital audio-effects and increase audio bitrate", - "the-guest-will-not-have-a-choice-over-audio-options": "The guest will not have a choice over audio-options", - "the-guest-will-only-be-able-to-select-their-webcam-as-an-option": "The guest will only be able to select their webcam as an option", - "hold-ctrl-and-the-mouse-wheel-to-zoom-in-and-out-remotely-of-compatible-video-streams": "Hold CTRL and the mouse wheel to zoom in and out remotely of compatible video streams", - "add-a-password-to-make-the-stream-inaccessible-to-those-without-the-password": "Add a password to make the stream inaccessible to those without the password", - "add-the-guest-to-a-group-chat-room-it-will-be-created-automatically-if-needed-": "Add the guest to a group-chat room; it will be created automatically if needed.", - "customize-the-room-settings-for-this-guest": "Customize the room settings for this guest", - "hold-ctrl-or-cmd-to-select-multiple-files": "Hold CTRL (or CMD) to select multiple files", - "enter-an-https-url": "Enter an HTTPS URL", + "toggle-the-chat": "Chat aan/uit", + "mute-the-speaker": "Demp de sSreker", + "mute-the-mic": "Demp de Mic", + "disable-the-camera": "Camera uitzetten", + "settings": "Instellingen", + "hangup-the-call": "Gesprek ophangen", + "show-help-info": "Geef Help weer", + "language-options": "Taal opties", + "tip-hold-ctrl-command-to-select-multiple": "tip: Houd CTRL (command) vast om meerdere te selecteren", + "ideal-for-1080p60-gaming-if-your-computer-and-upload-are-up-for-it": "Ideaal voor 1080p60 game streaming, als de pc krachtig genoeg is", + "better-video-compression-and-quality-at-the-cost-of-increased-cpu-encoding-load": "Betere videocompressie en kwaliteit ten koste van verhoogde CPU encodering belasting", + "disable-digital-audio-effects-and-increase-audio-bitrate": "Schakel digitale audio effecten uit en verhoogt audio bitrate", + "the-guest-will-not-have-a-choice-over-audio-options": "Gasten kunnen audio opties niet wijzigen", + "the-guest-will-only-be-able-to-select-their-webcam-as-an-option": "Gast kan alleen een webcam selecteren", + "hold-ctrl-and-the-mouse-wheel-to-zoom-in-and-out-remotely-of-compatible-video-streams": "Scroll in en uit door CTRL vast te houden en te scrollen met d emuis (wanneer mogelijk)", + "add-a-password-to-make-the-stream-inaccessible-to-those-without-the-password": "Voeg een wachtwoord toe en eis deze voor toegang", + "add-the-guest-to-a-group-chat-room-it-will-be-created-automatically-if-needed-": "Gast toevoegen aan groepschat.(Word automatisch gemaakt)", + "customize-the-room-settings-for-this-guest": "Geef (kamer) instellingen op voor deze gast", + "hold-ctrl-or-cmd-to-select-multiple-files": "Meerdere selecteren? CTRL vasthouden tijdens selecteren", + "enter-an-https-url": "Voer een HTTPS URL in", "lucy-g": "Lucy G", "flaticon": "Flaticon", "creative-commons-by-3-0": "Creative Commons BY 3.0", "gregor-cresnar": "Gregor Cresnar", "add-this-video-to-any-remote-scene-1-": "Add this Video to any remote '&scene=1'", - "forward-user-to-another-room-they-can-always-return-": "Forward user to another room. They can always return.", - "start-recording-this-stream-experimental-views": "Start Recording this stream. *experimental*' views", - "force-the-user-to-disconnect-they-can-always-reconnect-": "Force the user to Disconnect. They can always reconnect.", + "forward-user-to-another-room-they-can-always-return-": "Stuur gast door naar andere room, kan terugkeren.", + "start-recording-this-stream-experimental-views": "Stream opnemen starten. *experimenteel*' views", + "force-the-user-to-disconnect-they-can-always-reconnect-": "Forceer einde verbinding, kan wel opnieuw verbinden.", "change-this-audio-s-volume-in-all-remote-scene-views": "Change this Audio's volume in all remote '&scene' views", "remotely-mute-this-audio-in-all-remote-scene-views": "Remotely Mute this Audio in all remote '&scene' views", - "disable-video-preview": "Disable Video Preview", - "low-quality-preview": "Low-Quality Preview", - "high-quality-preview": "High-Quality Preview", - "send-direct-message": "Send Direct Message", - "advanced-settings-and-remote-control": "Advanced Settings and Remote Control", - "toggle-voice-chat-with-this-guest": "Toggle Voice Chat with this Guest", - "join-by-room-name-here": "Enter a room name to quick join", - "join-room": "Join room", - "share-a-screen-with-others": "Share a Screen with others", - "alert-the-host-you-want-to-speak": "Alert the host you want to speak", - "record-your-stream-to-disk": "Record your stream to disk", - "cancel-the-director-s-video-audio": "Cancel the Director's Video/Audio", - "submit-any-error-logs": "Submit any error logs", - "add-group-chat-to-obs": "Add Group Chat to OBS", - "for-large-group-rooms-this-option-can-reduce-the-load-on-remote-guests-substantially": "For large group rooms, this option can reduce the load on remote guests substantially", - "which-video-codec-would-you-want-used-by-default-": "Which video codec would you want used by default?", - "you-ll-enter-as-the-room-s-director": "You'll enter as the room's director", - "add-your-camera-to-obs": "Add your Camera to OBS", - "remote-screenshare-into-obs": "Remote Screenshare into OBS", - "create-reusable-invite": "Create Reusable Invite", + "disable-video-preview": "Schakel Video Preview uit", + "low-quality-preview": "Lage kwaliteit Preview", + "high-quality-preview": "Hoge kwaliteit Preview", + "send-direct-message": "Stuur een Direct Message", + "advanced-settings-and-remote-control": "Geavanceerde instellingen en Remote Conntrol", + "toggle-voice-chat-with-this-guest": "Schakel Voice chat aan/uit met deze gast", + "join-by-room-name-here": "Voer een naam in voor snelle toegang", + "join-room": "Ga de kamer in", + "share-a-screen-with-others": "Deel je scherm met anderen", + "alert-the-host-you-want-to-speak": "Geef de director een seintje", + "record-your-stream-to-disk": "Streamopname naar lokale opslag", + "cancel-the-director-s-video-audio": "Annuleer de Director zijn Video/Audio", + "submit-any-error-logs": "Verzend foutmeldingslog", + "add-group-chat-to-obs": "Voeg een groepschat toe aan OBS", + "for-large-group-rooms-this-option-can-reduce-the-load-on-remote-guests-substantially": "Bij grotere groepen kan deze optie voor een lagere belasting aan gast zijde zorgen", + "which-video-codec-would-you-want-used-by-default-": "Welke video codec wil je standaard gebruiken?", + "you-ll-enter-as-the-room-s-director": "Je gaat de kamer binnen als Director", + "add-your-camera-to-obs": "Voeg je camera aan OBS toe", + "remote-screenshare-into-obs": "Schermdelen naar OBS", + "create-reusable-invite": "creëer herbruikbaare link", "encode-the-url-so-that-it-s-harder-for-a-guest-to-modify-the-settings-": "Encode the URL so that it's harder for a guest to modify the settings.", - "more-options": "More Options", - "youtube-video-demoing-how-to-do-this": "Youtube Video demoing how to do this", - "invite-a-guest-or-camera-source-to-publish-into-the-group-room": "Invite a guest or camera source to publish into the group room", - "if-enabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "If enabled, the invited guest will not be able to see or hear anyone in the room.", - "use-this-link-in-the-obs-browser-source-to-capture-the-video-or-audio": "Use this link in the OBS Browser Source to capture the video or audio", - "if-enabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "If enabled, you must manually add a video to a scene for it to appear.", - "disables-echo-cancellation-and-improves-audio-quality": "Disables Echo Cancellation and improves audio quality", - "audio-only-sources-are-visually-hidden-from-scenes": "Audio-only sources are visually hidden from scenes", - "guest-will-be-prompted-to-enter-a-display-name": "Guest will be prompted to enter a Display Name", - "display-names-will-be-shown-in-the-bottom-left-corner-of-videos": "Display Names will be shown in the bottom-left corner of videos", - "request-1080p60-from-the-guest-instead-of-720p60-if-possible": "Request 1080p60 from the Guest instead of 720p60, if possible", - "the-default-microphone-will-be-pre-selected-for-the-guest": "The default microphone will be pre-selected for the guest", - "the-default-camera-device-will-selected-automatically": "The default camera device will selected automatically", - "the-guest-won-t-have-access-to-changing-camera-settings-or-screenshare": "The guest won't have access to changing camera settings or screenshare", - "the-guest-will-not-see-their-own-self-preview-after-joining": "The guest will not see their own self-preview after joining", - "guests-will-have-an-option-to-poke-the-director-by-pressing-a-button": "Guests will have an option to poke the Director by pressing a button", - "add-an-audio-compressor-to-the-guest-s-microphone": "Add an audio compressor to the guest's microphone", - "add-an-equalizer-to-the-guest-s-microphone-that-the-director-can-control": "Add an Equalizer to the guest's microphone that the director can control", - "the-guest-can-only-see-the-director-s-video-if-provided": "The guest can only see the Director's video, if provided", - "the-guest-s-microphone-will-be-muted-on-joining-they-can-unmute-themselves-": "The guest's microphone will be muted on joining. They can unmute themselves.", - "have-the-guest-join-muted-so-only-the-director-can-unmute-the-guest-": "Have the guest join muted, so only the director can Unmute the guest.", - "make-the-invite-url-encoded-so-parameters-are-harder-to-tinker-with-by-guests": "Make the invite URL encoded, so parameters are harder to tinker with by guests", - "move-the-user-to-another-room-controlled-by-another-director": "Move the user to another room, controlled by another director", - "send-a-direct-message-to-this-user-": "Send a Direct Message to this user.", - "remotely-change-the-volume-of-this-guest": "Remotely change the volume of this guest", - "mute-this-guest-everywhere": "Mute this guest everywhere", - "start-recording-this-remote-stream-to-this-local-drive-experimental-": "Start Recording this remote stream to this local drive. *experimental*'", - "the-remote-guest-will-record-their-local-stream-to-their-local-drive-experimental-": "The Remote Guest will record their local stream to their local drive. *experimental*", - "shift-this-video-down-in-order": "Shift this Video Down in Order", - "current-index-order-of-this-video": "Current Index Order of this Video", - "shift-this-video-up-in-order": "Shift this Video Up in Order", - "remote-audio-settings": "Remote Audio Settings", - "advanced-video-settings": "Advanced Video Settings", - "activate-or-reload-this-video-device-": "Activate or Reload this video device." + "more-options": "Meer opties", + "youtube-video-demoing-how-to-do-this": "Youtube video voorbeelden, hoe dit te doen!", + "invite-a-guest-or-camera-source-to-publish-into-the-group-room": "Vraag een gast zijn beeld te publiceren in de kamer", + "if-enabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "Wanneer deze optie aan staat kan de gast niemand zien of horen", + "use-this-link-in-the-obs-browser-source-to-capture-the-video-or-audio": "Gebruik deze link als OBS Browser source om video/audio binnen te halen", + "if-enabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "Wanneer deze optie aanstaat moet je video handmatig toevoegen aan een scene om hem te zien.", + "disables-echo-cancellation-and-improves-audio-quality": "Schakelt Echo Cancellation uit en verbetert audio kwaliteit", + "audio-only-sources-are-visually-hidden-from-scenes": "Bronnen met alleen audio niet laten zien in de scene", + "guest-will-be-prompted-to-enter-a-display-name": "Vraag gasten om een naam om weer te geven", + "display-names-will-be-shown-in-the-bottom-left-corner-of-videos": "Namen van gasten komen links onder in de hoek van de video", + "request-1080p60-from-the-guest-instead-of-720p60-if-possible": "Vraag om 1080p60 i.p.v. 720p60, wanneer mogelijk", + "the-default-microphone-will-be-pre-selected-for-the-guest": "Voor deze gast standaard microfoon selecteren vooraf", + "the-default-camera-device-will-selected-automatically": "Standaard camera word automatisch geselecteerd", + "the-guest-won-t-have-access-to-changing-camera-settings-or-screenshare": "De gast kan hierdoor niet wisselen tussen camera en scherm delen", + "the-guest-will-not-see-their-own-self-preview-after-joining": "Hierdoor zit de gast zichzelf niet nadat deze het gesprek is binnengekomen", + "guests-will-have-an-option-to-poke-the-director-by-pressing-a-button": "Gasten krijgen een optie de director een verzoek tot aandacht te sturen", + "add-an-audio-compressor-to-the-guest-s-microphone": "Voeg een audio compressor toe aan de gast zijn microfoon", + "add-an-equalizer-to-the-guest-s-microphone-that-the-director-can-control": "Voeg een audio equalizer toe aan gast, onder controle van director", + "the-guest-can-only-see-the-director-s-video-if-provided": "Gat kan alleen de director zien,wanneer deze aanwezig is", + "the-guest-s-microphone-will-be-muted-on-joining-they-can-unmute-themselves-": "Microfoon van gast standaard uit, kunnen zichzelf wel aanzetten", + "have-the-guest-join-muted-so-only-the-director-can-unmute-the-guest-": "Microfoon van gast standaard uit, alleen director kan deze aanzetten", + "make-the-invite-url-encoded-so-parameters-are-harder-to-tinker-with-by-guests": "Encodeer de invitatie URL. Om aanpassingen door gast moeilijker te maken", + "move-the-user-to-another-room-controlled-by-another-director": "Verplaats gast naar andere room. Beheerd door een andere director", + "send-a-direct-message-to-this-user-": "Stuur een Direct Message naar deze gast", + "remotely-change-the-volume-of-this-guest": "Op afstand veranderen volume gast", + "mute-this-guest-everywhere": "Deze gast niet meer hoorbaar maken overal", + "start-recording-this-remote-stream-to-this-local-drive-experimental-": "Start opname van de remote audio/video stream op de lokale drive *experimental*'", + "the-remote-guest-will-record-their-local-stream-to-their-local-drive-experimental-": "Zet opname aan bij gasten zelf op lokale schijf van gast *experimental*", + "shift-this-video-down-in-order": "Schuif deze video lager in orde", + "current-index-order-of-this-video": "Huidige index plek van deze video", + "shift-this-video-up-in-order": "Schuif deze video hoger in orde", + "remote-audio-settings": "Audio instellingen op afstand", + "advanced-video-settings": "Geavanceerde instellingen van video", + "activate-or-reload-this-video-device-": "Activeer of herlaadt deze video bron." }, "innerHTML": { "logo-header": "OBS Ninja", @@ -92,7 +92,7 @@ "you-are-in-the-control-center": "U bent in het kamer beheers centrum", "joining-room": "U neemt deel aan de kamer", "add-group-chat": "Voeg Groepsgesprek toe", - "rooms-allow-for": "Kamers maken eenvoudige groepsgespreken en geavanceerd beheer van meerdere streams tegelijk mogelijk.", + "rooms-allow-for": "Kamers maken eenvoudige groepsgespreken en geavanceerd beheer van meerdere streams tegelijkertijd mogelijk.", "room-name": "Kamer Naam", "password-input-field": "Password", "enter-the-rooms-control": "Ga de Kamer's Controle Centrum in", @@ -100,7 +100,7 @@ "added-notes": "\n\t\t\t\tNotities:\n\t\t\t\t
  • Iedereen kan de kamer binnenkomen als ze de naam kennen, dus hou hem uniek
  • \n\t\t\t\t
  • Meer dan vier (4) mensen in een kamer is niet aan te raden vanwege prestatie redenen, maar is afhankelijk van uw hardware.
  • \n\t\t\t\t
  • Bij iOS apparaten is de video alleen zichtbaar voor de regiseur. Dit is een hardware beperking.
  • \n\t\t\t\t
  • De \"Opname\" optie is nieuw en is experimenteel.
  • \n\t\t\t\t
  • U moet een video stroom \"Toevoegen\" aan de \"Groeps Scene\" om het hier te tonen.
  • \n\t\t\t\t
  • Er is een nieuwe \"uitgebreid volledig scherm\" knop toegevoegd aan het Gasten scherm.
  • \n\t\t\t\t", "back": "Terug", "add-your-camera": "Voeg je Camera toe", - "ask-for-permissions": "Allow Access to Camera/Microphone", + "ask-for-permissions": "Geef toestemming voor gebruik Camera/Microfoon", "waiting-for-camera": "Wachten op het Laden van de Camera", "video-source": "Video bron", "max-resolution": "Max Resolutie", @@ -154,39 +154,39 @@ "more-than-four-can-join": "These four guest slots are just for demonstration. More than four guests can actually join a room.", "welcome-to-obs-ninja-chat": "\n\t\t\t\t\tWelcome to OBS.Ninja! You can send text messages directly to connected peers from here.\n\t\t\t\t", "names-and-labels-coming-soon": "\n\t\t\t\t\tNames identifying connected peers will be a feature in an upcoming release.\n\t\t\t\t", - "send-chat": "Send", - "available-languages": "Available Languages:", - "add-more-here": "Add More Here!", - "waiting-for-camera-to-load": "waiting-for-camera-to-load", + "send-chat": "Verstuur", + "available-languages": "Beschikbare talen:", + "add-more-here": "Voer hier meer toe!", + "waiting-for-camera-to-load": "Wachten tot camera geladen is", "start": "START", - "share-your-mic": "Share your microphone", - "share-your-camera": "Share your Camera", - "share-your-screen": "Share your Screen", - "join-room-with-mic": "Join room with Microphone", - "share-screen-with-room": "Share-screen with Room", - "join-room-with-camera": "Join room with Camera", - "click-start-to-join": "Click Start to Join", - "guests-only-see-director": "Guests can only see the Director's Video", - "default-codec-select": "Preferred Video Codec: ", - "obfuscate_url": "Obfuscate the Invite URL", + "share-your-mic": "Deel je microfoon", + "share-your-camera": "Deel je camera", + "share-your-screen": "Deel je scherm", + "join-room-with-mic": "Ga de kamer binnen met microfoon", + "share-screen-with-room": "Deel je scherm met de kamer", + "join-room-with-camera": "Ga de kamer binnen met camera", + "click-start-to-join": "Druk op start om erin te gaan", + "guests-only-see-director": "Gasten kunnen alleen de directors video zien", + "default-codec-select": "Geprefereerde Video Codec: ", + "obfuscate_url": "Verhul originele uitnodigingslink", "hide-the-links": " LINKS (GUEST INVITES & SCENES)", - "invite-users-to-join": "Guests can use the link to join the group room", - "this-is-obs-browser-source-link": "Use in OBS or other studio software to capture the group video mix", + "invite-users-to-join": "Gasten kunnen de link gebruiken om de kamer binnen te gaan", + "this-is-obs-browser-source-link": "Gebruik deze link om de video mix te gebruiken in OBS of andere studio software", "mute-scene": "mute in scene", "mute-guest": "mute guest", - "record-local": " Record Local", - "record-remote": " Record Remote", + "record-local": " Lokale opname", + "record-remote": " Opname remote", "order-down": "", "order-up": "", - "advanced-audio-settings": " Audio Settings" + "advanced-audio-settings": " Audio instellingen" }, "placeholders": { - "join-by-room-name-here": "Join by Room Name here", - "enter-a-room-name-here": "Enter a Room Name here", - "optional-room-password-here": "Optional room password here", - "give-this-media-source-a-name-optional-": "Give this media source a name (optional)", - "add-an-optional-password": "Add an optional password", - "enter-room-name-here": "Enter Room name here", - "enter-chat-message-to-send-here": "Enter chat message to send here" + "join-by-room-name-here": "Ga binnen met een kamer naam", + "enter-a-room-name-here": "Geef hier een kamer naam op", + "optional-room-password-here": "Optionele wachtwoord voor kamer", + "give-this-media-source-a-name-optional-": "Geef de media bron een naam(optioneel)", + "add-an-optional-password": "Voeg optioneel wachtwoord toe", + "enter-room-name-here": "Geef hier de kamer naam op", + "enter-chat-message-to-send-here": "Type hier om te chatten" } -} \ No newline at end of file +} From b6b39b893fd307b68f305c713f71607cadbbbafa Mon Sep 17 00:00:00 2001 From: Steve Seguin Date: Wed, 27 Jan 2021 17:29:08 -0500 Subject: [PATCH 02/30] Update nl.json --- translations/nl.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/translations/nl.json b/translations/nl.json index d0f47e4..72caf7b 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -84,7 +84,16 @@ "shift-this-video-up-in-order": "Schuif deze video hoger in orde", "remote-audio-settings": "Audio instellingen op afstand", "advanced-video-settings": "Geavanceerde instellingen van video", - "activate-or-reload-this-video-device-": "Activeer of herlaadt deze video bron." + "activate-or-reload-this-video-device-": "Activeer of herlaadt deze video bron.", + "create-a-seconary-stream": "Create a Seconary Stream", + "the-director-will-be-visible-in-scenes-as-if-a-performer-themselves-": "The director will be visible in scenes, as if a performer themselves.", + "useful-if-you-want-to-perform-and-direct-at-the-same-time": "Useful if you want to perform and direct at the same time", + "start-streaming": "start streaming", + "if-disabled-the-invited-guest-will-not-be-able-to-see-or-hear-anyone-in-the-room-": "If disabled, the invited guest will not be able to see or hear anyone in the room.", + "if-disabled-you-must-manually-add-a-video-to-a-scene-for-it-to-appear-": "If disabled, you must manually add a video to a scene for it to appear.", + "toggle-solo-voice-chat": "Toggle Solo Voice Chat", + "toggle-the-remote-guest-s-speaker-output": "Toggle the remote guest's speaker output", + "toggle-the-remote-guest-s-display-output": "Toggle the remote guest's display output" }, "innerHTML": { "logo-header": "OBS Ninja", @@ -178,7 +187,10 @@ "record-remote": " Opname remote", "order-down": "", "order-up": "", - "advanced-audio-settings": " Audio instellingen" + "advanced-audio-settings": " Audio instellingen", + "scenes-can-see-director": "Director will also be a performer", + "toggle-remote-speaker": "Deafen Guest", + "toggle-remote-display": "Blind Guest" }, "placeholders": { "join-by-room-name-here": "Ga binnen met een kamer naam", From 5c7648d86aaef768c17e53815140d58aeb049c01 Mon Sep 17 00:00:00 2001 From: Steve Seguin Date: Thu, 4 Feb 2021 11:11:46 -0500 Subject: [PATCH 03/30] Create turnserver3.conf --- turnserver3.conf | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 turnserver3.conf diff --git a/turnserver3.conf b/turnserver3.conf new file mode 100644 index 0000000..19ef825 --- /dev/null +++ b/turnserver3.conf @@ -0,0 +1,16 @@ +# Another TURNSERVER conf file; this one is a basic UDP-based setup. Port 3478, no SSL/TLS, and basic username/password. + +listening-port=3478 +alt-listening-port=0 +fingerprint +no-stun +lt-cred-mech +user=obsninja:somepasswordwhere +stale-nonce=600 +realm=turn-eu2.obs.ninja +server-name=turn-eu2.obs.ninja +no-multicast-peers +stale-nonce=600 +dh2066 +#verbose +no-stdout-log From d6d4d93dabd6a57eb8d463ab98c97668a54055f8 Mon Sep 17 00:00:00 2001 From: Duncan Barnes Date: Sat, 6 Feb 2021 17:32:31 +0000 Subject: [PATCH 04/30] Feature: &rooms Permits a list of rooms to be defined for room transfer operations. Buttons are added to the control bar, when selected and a transfer button is pushed the user will be transfered straight to that room rather than the room name having to be entered. --- index.html | 1 + main.css | 19 ++++++++++++++++--- main.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index e58b426..ad9b020 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ +
    diff --git a/main.css b/main.css index df35fb0..6f93fb1 100644 --- a/main.css +++ b/main.css @@ -501,10 +501,23 @@ button.glyphicon-button.active.focus { padding: 0px; } } +/* Node selector to prioritise this selector above .float */ +button.btnArmTransferRoom{ + width:auto; + margin-left: 2px; + height:38px; + border-radius: 15px; +} +button.btnArmTransferRoom i{ + margin-right:3px; +} +button.btnArmTransferRoom:hover{ + background-color: var(--green-accent); +} - - - +button.btnArmTransferRoom.selected{ + background-color: var(--red-accent); +} @media only screen and (max-height: 540px){ #subControlButtons { diff --git a/main.js b/main.js index efc0b1b..3da0279 100644 --- a/main.js +++ b/main.js @@ -707,6 +707,11 @@ if (urlParams.has('director') || urlParams.has('dir')) { filename = false; } +if (urlParams.has('rooms')) { + session.rooms = urlParams.get('rooms').split(",").map(function(e) { + return sanitizeRoomName(e); + }); +} if (urlParams.has('showdirector')) { session.showDirector = true; @@ -3718,6 +3723,7 @@ function lowerhand() { var previousRoom = ""; var stillNeedRoom = true; var transferCancelled = false; +var armedTransfer = true; function directMigrate(ele, event) { // everyone in the room will hangup this guest also? I like that idea. What about the STREAM ID? I suppose we don't kick out if the viewID matches. @@ -3741,6 +3747,8 @@ function directMigrate(ele, event) { // everyone in the room will hangup this gu stillNeedRoom = false; log("Migrate queued"); return; + } else if (armedTransfer){ + migrateRoom = sanitizeRoomName(previousRoom); } else { var migrateRoom = prompt("Transfer guests to room:\n\n(Please note rooms must share the same password)", previousRoom); stillNeedRoom = true; @@ -4990,6 +4998,18 @@ function createRoomCallback(passAdd, passAdd2) { getById("miniPerformer").innerHTML = ''; } getById("miniPerformer").className = ""; + + var tabindex = 26; + if(session.rooms && session.rooms.length > 0){ + var container = getById("rooms"); + container.innerHTML += 'Arm Transfer: '; + session.rooms.forEach(function (r) { + if(session.roomid == r) return; //don't include self + container.innerHTML += ''; + tabindex++; + }); + } + } else { getById("miniPerformer").style.display = "none"; getById("controlButtons").style.display = "none"; @@ -5005,6 +5025,26 @@ function createRoomCallback(passAdd, passAdd2) { joinRoom(session.roomid); } +/** + * Handles click actions on the room selection buttons in #controlButtons + * @param {string} room - Room name to select/deselect for the next transfer call + */ +function handleRoomSelect(room) { + var elems = document.querySelectorAll(".btnArmTransferRoom"); + [].forEach.call(elems, function(el) { + el.classList.remove("selected"); + }); + if (previousRoom == room) { + previousRoom = undefined; + armedTransfer = false; + stillNeedRoom = true; + } else { + previousRoom = room; + stillNeedRoom = false; + armedTransfer = true; + getById("roomselect_" + room).classList.add('selected'); + } +} function requestAudioSettings(ele) { var UUID = ele.dataset.UUID; From 944e619f0ade575ab63289d06b439b8e232496db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 10:12:30 +0100 Subject: [PATCH 05/30] Add hyperlink to turnserver --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8720087..be78680 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ https://steves.app/ A browser-based studio solution and simplified alternative to OBS, with built-in OBS.Ninja functionality. It is a server-based approach to group interactions and live production. Steve Seguin is affiliated with StageTEn, yet StageTEN is not affiliated with OBS.Ninja. ## Privacy -I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: turnserver.md +I try to avoid data collection whenever possible and video streams are generally designed to be private, but use at your own risk. It is best to not share links created with OBS.Ninja with those you do not trust. I've provided instructions on how to deploy a TURN server if IP-address privacy is an issue for you. See: [turnserver.md](turnserver.md) https://obs.ninja may unavoidably use cookies that are exempt from EU laws of requiring notice of their use; they are exempt as they are required and necessary for the technical functioning of the web service. Our webserver is cached by Cloudflare and it provides denial of server protection for the users of OBS.Ninja. From 17403d816b33577dfc783b742187ec1d6ca1cfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 10:38:08 +0100 Subject: [PATCH 06/30] Add comments to the commands Add some explanations for the commands so that the user is better aware of what he is currently running. --- turnserver.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/turnserver.md b/turnserver.md index 5908b09..b310273 100644 --- a/turnserver.md +++ b/turnserver.md @@ -5,22 +5,25 @@ This install script and config file was used with a standard virtual machine server loaded with Ubuntu 20. GCP/AWS servers might need slightly different settings. ``` -sudo apt-get update +sudo apt-get update # update package lists -sudo apt-get install coturn -y -sudo add-apt-repository ppa:certbot/certbot -sudo apt-get install certbot -y +sudo apt-get install coturn -y # install coturn, the implementation of the TURN server +sudo add-apt-repository ppa:certbot/certbot # Add the certbot repository +sudo apt-get install certbot -y # Install certbot required for the HTTPS certificate -sudo vi /etc/default/coturn +sudo vi /etc/default/coturn # open the coturn configuration in Vim (you can also use nano or any other editor) ``` ...and we uncomment the line: +``` #TURNSERVER_ENABLED=1 +``` ….leaving it like this: +``` TURNSERVER_ENABLED=1 - +``` Next make sure you have the DNS pointing to your IP address for this next step (ipv4, and ipv6 if possible). You will need to validate that in the next step. ``` -sudo certbot certonly --standalone +sudo certbot certonly --standalone # only generate the HTTPS certificate without actually changing any configs sudo apt install net-tools ``` note: If you run into error 701 issues with your TURN server, check that the coturn service has access to your new SSL certificates: @@ -37,10 +40,10 @@ sudo systemctl daemon-reload Next, we are going to open up some ports... just in case they are blocked by default. Which exactly? well, these are default ports. TCP may not be needed? ``` -sudo ufw allow 3478/tcp -sudo ufw allow 3478/udp -sudo ufw allow 443/tcp -sudo ufw allow 443/udp +sudo ufw allow 3478/tcp # The default coturn TCP port +sudo ufw allow 3478/udp # The default coturn UDP port +sudo ufw allow 443/tcp # The HTTPS UDP port +sudo ufw allow 443/udp # The HTTPS TCP port sudo ufw allow 49152:65535/tcp sudo ufw allow 49152:65535/udp ``` From 4e922c13aa459ce280c75a98adb432c3d96463b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 10:55:27 +0100 Subject: [PATCH 07/30] Fix the incorrect placement of UDP and TCP --- turnserver.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turnserver.md b/turnserver.md index b310273..18b4815 100644 --- a/turnserver.md +++ b/turnserver.md @@ -42,8 +42,8 @@ Next, we are going to open up some ports... just in case they are blocked by def ``` sudo ufw allow 3478/tcp # The default coturn TCP port sudo ufw allow 3478/udp # The default coturn UDP port -sudo ufw allow 443/tcp # The HTTPS UDP port -sudo ufw allow 443/udp # The HTTPS TCP port +sudo ufw allow 443/tcp # The HTTPS TCP port +sudo ufw allow 443/udp # The HTTPS UDP port sudo ufw allow 49152:65535/tcp sudo ufw allow 49152:65535/udp ``` From 193409787570c030a5cb57eddf831722faa4fe34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:40:48 +0100 Subject: [PATCH 08/30] JS syntax highlighting - add syntax highlighting in IFRAME.md - update syntax to ES6 --- IFRAME.md | 314 +++++++++++++++++++++++++++--------------------------- 1 file changed, 157 insertions(+), 157 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 0b93c64..565e629 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -9,12 +9,12 @@ The solution decided on isn't an SDK framework, but rather the use of embeddable Modern web browsers allow the parent website to communicate with the child webpage, giving a high-level of control to a developer, while also abstracting the complex code and hosting requirements. Functionality, we can make an OBSN video stream act much like an HTML video element tag, where you can issue commands like play, pause, or change video sources with ease. Creating an OBSN iframe can be done in HTML or programmatically with Javascript like so: - - var iframe = document.createElement("iframe"); +```js + const iframe = document.createElement("iframe"); iframe.allow="autoplay;camera;microphone"; iframe.allowtransparency="false" iframe.src = "https://obs.ninja/?webcam"; - +``` Adding that iframe to the DOM will reveal a simple page accessing for a user to select and share their webcam. For a developer wishing to access a remote guest's stream, this makes the ingestion of that stream into production software like OBS Studios very easy. The level of customization and control opens up opportunities, such as a pay-to-join audience option for a streaming interactive broadcast experience. 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. @@ -52,161 +52,161 @@ As for API, allow for dynamic messaging, below are examples of the options avail - 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: - - var button = document.createElement("button"); - button.innerHTML = "Mute Speaker"; - button.onclick = function(){iframe.contentWindow.postMessage({"mute":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Un-Mute Speaker"; - button.onclick = function(){iframe.contentWindow.postMessage({"mute":false}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Toggle Speaker"; - button.onclick = function(){iframe.contentWindow.postMessage({"mute":"toggle"}, '*');} - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Mute Mic"; - button.onclick = function(){iframe.contentWindow.postMessage({"mic":false}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Un-Mute Mic"; - button.onclick = function(){iframe.contentWindow.postMessage({"mic":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Toggle Mic"; - button.onclick = function(){iframe.contentWindow.postMessage({"mic":"toggle"}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Disconnect"; - button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Low Bitrate"; - button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "High Bitrate"; - button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":5000}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Default Bitrate"; - button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Reload"; - button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "50% Volume"; - button.onclick = function(){iframe.contentWindow.postMessage({"volume":0.5}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "100% Volume"; - button.onclick = function(){iframe.contentWindow.postMessage({"volume":1.0}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Request Stats"; - button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Request Loudness Levels"; - button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":true}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Stop Sending Loudness Levels"; - button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "Say Hello"; - button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "previewWebcam()"; - button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; - iframeContainer.appendChild(button); - - var button = document.createElement("button"); - button.innerHTML = "CLOSE IFRAME"; - button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);}; - iframeContainer.appendChild(button); - +```js +var button = document.createElement("button"); +button.innerHTML = "Mute Speaker"; +button.onclick = function(){iframe.contentWindow.postMessage({"mute":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Un-Mute Speaker"; +button.onclick = function(){iframe.contentWindow.postMessage({"mute":false}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Toggle Speaker"; +button.onclick = function(){iframe.contentWindow.postMessage({"mute":"toggle"}, '*');} +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Mute Mic"; +button.onclick = function(){iframe.contentWindow.postMessage({"mic":false}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Un-Mute Mic"; +button.onclick = function(){iframe.contentWindow.postMessage({"mic":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Toggle Mic"; +button.onclick = function(){iframe.contentWindow.postMessage({"mic":"toggle"}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Disconnect"; +button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Low Bitrate"; +button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "High Bitrate"; +button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":5000}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Default Bitrate"; +button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Reload"; +button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "50% Volume"; +button.onclick = function(){iframe.contentWindow.postMessage({"volume":0.5}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "100% Volume"; +button.onclick = function(){iframe.contentWindow.postMessage({"volume":1.0}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Request Stats"; +button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Request Loudness Levels"; +button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":true}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Stop Sending Loudness Levels"; +button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "Say Hello"; +button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "previewWebcam()"; +button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; +iframeContainer.appendChild(button); + +var button = document.createElement("button"); +button.innerHTML = "CLOSE IFRAME"; +button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);}; +iframeContainer.appendChild(button); + As for listening events, where the parent listens for responses or events from the OBSN child frame: - - //////////// LISTEN FOR EVENTS - - var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; - var eventer = window[eventMethod]; - var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; - - eventer(messageEvent, function (e) { - if (e.source != iframe.contentWindow){return} // reject messages send from other iframes - - if ("stats" in e.data){ - var outputWindow = document.createElement("div"); - - var out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; - out += "
    total_outbound_connections:"+e.data.stats.total_outbound_connections; - - for (var streamID in e.data.stats.inbound_stats){ - out += "

    streamID: "+streamID+"
    "; - out += printValues(e.data.stats.inbound_stats[streamID]); - } - - outputWindow.innerHTML = out; - iframeContainer.appendChild(outputWindow); - } - - if ("gotChat" in e.data){ - var outputWindow = document.createElement("div"); - outputWindow.innerHTML = e.data.gotChat.msg; - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - } - - if ("action" in e.data){ - var outputWindow = document.createElement("div"); - outputWindow.innerHTML = "child-page-action: "+e.data.action+"
    "; - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - } - - if ("loudness" in e.data){ - console.log(e.data); - if (document.getElementById("loudness")){ - outputWindow = document.getElementById("loudness"); - } else { - var outputWindow = document.createElement("div"); - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - outputWindow.id = "loudness"; - } - outputWindow.innerHTML = "child-page-action: loudness
    "; - for (var key in e.data.loudness) { - outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; - } - outputWindow.style.border="1px black"; - - } - }); - + +//////////// LISTEN FOR EVENTS + +var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; +var eventer = window[eventMethod]; +var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; + +eventer(messageEvent, function (e) { + if (e.source != iframe.contentWindow){return} // reject messages send from other iframes + + if ("stats" in e.data){ + var outputWindow = document.createElement("div"); + + var out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; + out += "
    total_outbound_connections:"+e.data.stats.total_outbound_connections; + + for (var streamID in e.data.stats.inbound_stats){ + out += "

    streamID: "+streamID+"
    "; + out += printValues(e.data.stats.inbound_stats[streamID]); + } + + outputWindow.innerHTML = out; + iframeContainer.appendChild(outputWindow); + } + + if ("gotChat" in e.data){ + var outputWindow = document.createElement("div"); + outputWindow.innerHTML = e.data.gotChat.msg; + outputWindow.style.border="1px dotted black"; + iframeContainer.appendChild(outputWindow); + } + + if ("action" in e.data){ + var outputWindow = document.createElement("div"); + outputWindow.innerHTML = "child-page-action: "+e.data.action+"
    "; + outputWindow.style.border="1px dotted black"; + iframeContainer.appendChild(outputWindow); + } + + if ("loudness" in e.data){ + console.log(e.data); + if (document.getElementById("loudness")){ + outputWindow = document.getElementById("loudness"); + } else { + var outputWindow = document.createElement("div"); + outputWindow.style.border="1px dotted black"; + iframeContainer.appendChild(outputWindow); + outputWindow.id = "loudness"; + } + outputWindow.innerHTML = "child-page-action: loudness
    "; + for (var key in e.data.loudness) { + outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; +} + outputWindow.style.border="1px black"; + + } +}); +``` This OBS.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete. Regarding versioning, I currently host past versions of OBS.Ninja, so using those past versions can ensure some level of consistency and expectation. https://obs.ninja/v12/ for example is the version 12 hosted version. The active and main production version of OBSN of course undergoes constant updates, and while I try to maintain backwards compatibility with changes to the API, it is still early days and changes might happen. From 8ee5be2960f65af9e503b447082b9c141cb67a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:43:13 +0100 Subject: [PATCH 09/30] Var -> const --- IFRAME.md | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 565e629..3dfe08d 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -53,97 +53,97 @@ As for API, allow for dynamic messaging, below are examples of the options avail As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: *iframe.html*. Below is a snippet from that file: ```js -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Mute Speaker"; button.onclick = function(){iframe.contentWindow.postMessage({"mute":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Un-Mute Speaker"; button.onclick = function(){iframe.contentWindow.postMessage({"mute":false}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Toggle Speaker"; button.onclick = function(){iframe.contentWindow.postMessage({"mute":"toggle"}, '*');} iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Mute Mic"; button.onclick = function(){iframe.contentWindow.postMessage({"mic":false}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Un-Mute Mic"; button.onclick = function(){iframe.contentWindow.postMessage({"mic":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Toggle Mic"; button.onclick = function(){iframe.contentWindow.postMessage({"mic":"toggle"}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Disconnect"; button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Low Bitrate"; button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "High Bitrate"; button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":5000}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Default Bitrate"; button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Reload"; button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "50% Volume"; button.onclick = function(){iframe.contentWindow.postMessage({"volume":0.5}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "100% Volume"; button.onclick = function(){iframe.contentWindow.postMessage({"volume":1.0}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Request Stats"; button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Request Loudness Levels"; button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":true}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Stop Sending Loudness Levels"; button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "Say Hello"; button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "previewWebcam()"; button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; iframeContainer.appendChild(button); -var button = document.createElement("button"); +const button = document.createElement("button"); button.innerHTML = "CLOSE IFRAME"; button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);}; iframeContainer.appendChild(button); @@ -152,20 +152,20 @@ As for listening events, where the parent listens for responses or events from t //////////// LISTEN FOR EVENTS -var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; -var eventer = window[eventMethod]; -var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; +const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; +const eventer = window[eventMethod]; +const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { if (e.source != iframe.contentWindow){return} // reject messages send from other iframes if ("stats" in e.data){ - var outputWindow = document.createElement("div"); + const outputWindow = document.createElement("div"); - var out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; + const out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; out += "
    total_outbound_connections:"+e.data.stats.total_outbound_connections; - for (var streamID in e.data.stats.inbound_stats){ + for (const streamID in e.data.stats.inbound_stats){ out += "

    streamID: "+streamID+"
    "; out += printValues(e.data.stats.inbound_stats[streamID]); } @@ -175,14 +175,14 @@ eventer(messageEvent, function (e) { } if ("gotChat" in e.data){ - var outputWindow = document.createElement("div"); + const outputWindow = document.createElement("div"); outputWindow.innerHTML = e.data.gotChat.msg; outputWindow.style.border="1px dotted black"; iframeContainer.appendChild(outputWindow); } if ("action" in e.data){ - var outputWindow = document.createElement("div"); + const outputWindow = document.createElement("div"); outputWindow.innerHTML = "child-page-action: "+e.data.action+"
    "; outputWindow.style.border="1px dotted black"; iframeContainer.appendChild(outputWindow); @@ -193,13 +193,13 @@ eventer(messageEvent, function (e) { if (document.getElementById("loudness")){ outputWindow = document.getElementById("loudness"); } else { - var outputWindow = document.createElement("div"); + const outputWindow = document.createElement("div"); outputWindow.style.border="1px dotted black"; iframeContainer.appendChild(outputWindow); outputWindow.id = "loudness"; } outputWindow.innerHTML = "child-page-action: loudness
    "; - for (var key in e.data.loudness) { + for (const key in e.data.loudness) { outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; } outputWindow.style.border="1px black"; From f0cfa1effd24390c8a143be20cdac89f0859149c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:44:04 +0100 Subject: [PATCH 10/30] Format IFRAME.md --- IFRAME.md | 68 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 3dfe08d..49c8d4d 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -1,57 +1,59 @@ -## OBS.Ninja - iFrame API documentation +## OBS.Ninja - iFrame API documentation OBS.Ninja (OBSN) is offers here a simple and free solution to quickly enable real-time video streaming in their websites. OBSN wishes to make live video streaming development accessible to any developer, even novices, yet still remain flexible and powerful. While OBS.Ninja does offer source-code to customize the application and UI at a low level, this isn't for beginners and it is rather hard to maintain. As well, due to the complexity of video streaming in the web, typical approaches for offering API access isn't quite feasible either. -The solution decided on isn't an SDK framework, but rather the use of embeddable *IFrames* and a corresponding bi-directional iframe API. An [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) allows us to embed a webpage inside a webpage, including OBS.Ninja into your own website. +The solution decided on isn't an SDK framework, but rather the use of embeddable _IFrames_ and a corresponding bi-directional iframe API. An [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) allows us to embed a webpage inside a webpage, including OBS.Ninja into your own website. Modern web browsers allow the parent website to communicate with the child webpage, giving a high-level of control to a developer, while also abstracting the complex code and hosting requirements. Functionality, we can make an OBSN video stream act much like an HTML video element tag, where you can issue commands like play, pause, or change video sources with ease. Creating an OBSN iframe can be done in HTML or programmatically with Javascript like so: + ```js - const iframe = document.createElement("iframe"); - iframe.allow="autoplay;camera;microphone"; - iframe.allowtransparency="false" - iframe.src = "https://obs.ninja/?webcam"; +const iframe = document.createElement("iframe"); +iframe.allow = "autoplay;camera;microphone"; +iframe.allowtransparency = "false"; +iframe.src = "https://obs.ninja/?webcam"; ``` -Adding that iframe to the DOM will reveal a simple page accessing for a user to select and share their webcam. For a developer wishing to access a remote guest's stream, this makes the ingestion of that stream into production software like OBS Studios very easy. The level of customization and control opens up opportunities, such as a pay-to-join audience option for a streaming interactive broadcast experience. + +Adding that iframe to the DOM will reveal a simple page accessing for a user to select and share their webcam. For a developer wishing to access a remote guest's stream, this makes the ingestion of that stream into production software like OBS Studios very easy. The level of customization and control opens up opportunities, such as a pay-to-join audience option for a streaming interactive broadcast experience. An example of how this API is used by OBS.Ninja is with its Internet Speedtest, which has two OBS.Ninja IFrames on a single page. One iframe feeds video to the other iframe, and the speed at which it does this is a measure of the system's performance. Detailed stats of the connection are made available to the parent window, which displays the results. https://obs.ninja/speedtest More community-contributed IFRAME examples can be found here: https://github.com/steveseguin/obsninja/tree/master/examples -A sandbox of options is available at this page, too: https://obs.ninja/iframe You can enter an OBS.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/obsninja/blob/master/iframe.html +A sandbox of options is available at this page, too: https://obs.ninja/iframe You can enter an OBS.Ninja URL in the input box to start using it. For developers, viewing the source of that page will reveal examples of how all the available functions work, along with a way to test and play with each of them. You can also see here for the source-code on GitHub: https://github.com/steveseguin/obsninja/blob/master/iframe.html -One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe *src* URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control OBS.Ninja, while the former is used to initiate the instance to a desired state. +One thing to note about this iframe API is that it is a mix of URL parameters given to the iframe _src_ URL, but also the postMessage and addEventListener methods of the browser. The later is used to dynamically control OBS.Ninja, while the former is used to initiate the instance to a desired state. For more information on the URL parameters thare are available, please see: https://github.com/steveseguin/obsninja/wiki/Advanced-Settings Some of the more interesting ones primarily for iframe users might include: - - &webcam - - &screenshare - - &videodevice=1 or 0 - - &audiodevice=1 or 0 - - &autostart - - &chroma - - &transparency - - -As for API, allow for dynamic messaging, below are examples of the options available: +- &webcam +- &screenshare +- &videodevice=1 or 0 +- &audiodevice=1 or 0 +- &autostart +- &chroma +- &transparency +- As for API, allow for dynamic messaging, below are examples of the options available: - - Mute Speaker - - Mute Mic - - Disconnect - - Change Video Bitrate - - Reload the page - - Change the volume - - Request detailed connection stats - - Access the loudness level of the audio - - Send/Recieve a chat message to other connected guests - - Get notified when there is a video connection +- Mute Speaker +- Mute Mic +- Disconnect +- Change Video Bitrate +- Reload the page +- Change the volume +- Request detailed connection stats +- Access the loudness level of the audio +- Send/Recieve a chat message to other connected guests +- Get notified when there is a video connection + +As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file: -As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: *iframe.html*. Below is a snippet from that file: ```js const button = document.createElement("button"); button.innerHTML = "Mute Speaker"; @@ -140,7 +142,7 @@ iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "previewWebcam()"; -button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; +button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; iframeContainer.appendChild(button); const button = document.createElement("button"); @@ -148,7 +150,7 @@ button.innerHTML = "CLOSE IFRAME"; button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);}; iframeContainer.appendChild(button); -As for listening events, where the parent listens for responses or events from the OBSN child frame: +As for listening events, where the parent listens for responses or events from the OBSN child frame: //////////// LISTEN FOR EVENTS @@ -200,18 +202,18 @@ eventer(messageEvent, function (e) { } outputWindow.innerHTML = "child-page-action: loudness
    "; for (const key in e.data.loudness) { - outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; + outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; } outputWindow.style.border="1px black"; } }); ``` + This OBS.Ninja API is developed and expanded based on user feedback and requests. It is by no means complete. Regarding versioning, I currently host past versions of OBS.Ninja, so using those past versions can ensure some level of consistency and expectation. https://obs.ninja/v12/ for example is the version 12 hosted version. The active and main production version of OBSN of course undergoes constant updates, and while I try to maintain backwards compatibility with changes to the API, it is still early days and changes might happen. Please feel free to follow me in the OBS.Ninja Discord channel, where I post news about updates and listen to requests. The upcoming version of OBS.Ninja is also often hosted at https://obs.ninja/beta, where you can explore new features and help crush any unexpected bugs. - -steve From e762910907e152d43ae44c7d5dc6a8241b5ae288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:44:40 +0100 Subject: [PATCH 11/30] Eqeqeq --- IFRAME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IFRAME.md b/IFRAME.md index 49c8d4d..9c3d618 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -159,7 +159,7 @@ const eventer = window[eventMethod]; const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { - if (e.source != iframe.contentWindow){return} // reject messages send from other iframes + if (e.source !== iframe.contentWindow){return} // reject messages send from other iframes if ("stats" in e.data){ const outputWindow = document.createElement("div"); From c487bed90631e9e1887a629b0001e85fb950fb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:45:46 +0100 Subject: [PATCH 12/30] Invalid assignment to const --- IFRAME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IFRAME.md b/IFRAME.md index 9c3d618..c6f9efb 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -164,7 +164,7 @@ eventer(messageEvent, function (e) { if ("stats" in e.data){ const outputWindow = document.createElement("div"); - const out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; + let out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; out += "
    total_outbound_connections:"+e.data.stats.total_outbound_connections; for (const streamID in e.data.stats.inbound_stats){ From 8c4c1cd040c4d8993c7347eb3df976cfd2f74274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:52:15 +0100 Subject: [PATCH 13/30] Arrow functions as onclick handlers --- IFRAME.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index c6f9efb..3eed646 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -57,97 +57,97 @@ As for the actually details for methods and options available to dynamically con ```js const button = document.createElement("button"); button.innerHTML = "Mute Speaker"; -button.onclick = function(){iframe.contentWindow.postMessage({"mute":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "mute": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Un-Mute Speaker"; -button.onclick = function(){iframe.contentWindow.postMessage({"mute":false}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "mute": false }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Toggle Speaker"; -button.onclick = function(){iframe.contentWindow.postMessage({"mute":"toggle"}, '*');} +button.onclick = () => { iframe.contentWindow.postMessage({ "mute": "toggle" }, '*'); } iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Mute Mic"; -button.onclick = function(){iframe.contentWindow.postMessage({"mic":false}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "mic": false }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Un-Mute Mic"; -button.onclick = function(){iframe.contentWindow.postMessage({"mic":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "mic": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Toggle Mic"; -button.onclick = function(){iframe.contentWindow.postMessage({"mic":"toggle"}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "mic": "toggle" }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Disconnect"; -button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "close": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Low Bitrate"; -button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 30 }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "High Bitrate"; -button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":5000}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 5000 }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Default Bitrate"; -button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": -1 }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Reload"; -button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "reload": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "50% Volume"; -button.onclick = function(){iframe.contentWindow.postMessage({"volume":0.5}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 0.5 }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "100% Volume"; -button.onclick = function(){iframe.contentWindow.postMessage({"volume":1.0}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 1.0 }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Request Stats"; -button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "getStats": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Request Loudness Levels"; -button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":true}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": true }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Stop Sending Loudness Levels"; -button.onclick = function(){iframe.contentWindow.postMessage({"getLoudness":false}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": false }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "Say Hello"; -button.onclick = function(){iframe.contentWindow.postMessage({"sendChat":"Hello!"}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "sendChat": "Hello!" }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "previewWebcam()"; -button.onclick = function(){iframe.contentWindow.postMessage({"function":"previewWebcam"}, '*');}; +button.onclick = () => { iframe.contentWindow.postMessage({ "function": "previewWebcam" }, '*'); }; iframeContainer.appendChild(button); const button = document.createElement("button"); button.innerHTML = "CLOSE IFRAME"; -button.onclick = function(){iframeContainer.parentNode.removeChild(iframeContainer);}; +button.onclick = () => { iframeContainer.parentNode.removeChild(iframeContainer); }; iframeContainer.appendChild(button); As for listening events, where the parent listens for responses or events from the OBSN child frame: From c7a801c0e074fdcda7ff049584a5d0d65e5f1e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:52:27 +0100 Subject: [PATCH 14/30] Template strings --- IFRAME.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 3eed646..5da537d 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -164,11 +164,11 @@ eventer(messageEvent, function (e) { if ("stats" in e.data){ const outputWindow = document.createElement("div"); - let out = "
    total_inbound_connections:"+e.data.stats.total_inbound_connections; - out += "
    total_outbound_connections:"+e.data.stats.total_outbound_connections; + let out = `
    total_inbound_connections:${e.data.stats.total_inbound_connections}`; + out += `
    total_outbound_connections:${e.data.stats.total_outbound_connections}`; for (const streamID in e.data.stats.inbound_stats){ - out += "

    streamID: "+streamID+"
    "; + out += `

    streamID: ${streamID}
    `; out += printValues(e.data.stats.inbound_stats[streamID]); } @@ -185,7 +185,7 @@ eventer(messageEvent, function (e) { if ("action" in e.data){ const outputWindow = document.createElement("div"); - outputWindow.innerHTML = "child-page-action: "+e.data.action+"
    "; + outputWindow.innerHTML = `child-page-action: ${e.data.action}
    `; outputWindow.style.border="1px dotted black"; iframeContainer.appendChild(outputWindow); } @@ -202,7 +202,7 @@ eventer(messageEvent, function (e) { } outputWindow.innerHTML = "child-page-action: loudness
    "; for (const key in e.data.loudness) { - outputWindow.innerHTML += key + " Loudness: " + e.data.loudness[key] + "\n"; + outputWindow.innerHTML += `${key} Loudness: ${e.data.loudness[key]}\n`; } outputWindow.style.border="1px black"; From 1c3d45ae6ffa173946d91ceaed733b9caf5f20cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:55:26 +0100 Subject: [PATCH 15/30] Const -> let because of re-declaration issues --- IFRAME.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 5da537d..32fc099 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -55,97 +55,97 @@ Some of the more interesting ones primarily for iframe users might include: As for the actually details for methods and options available to dynamically control child OBSN iframe, they are primarily kept up to via the iframe.html file that is mentioned previously. see: _iframe.html_. Below is a snippet from that file: ```js -const button = document.createElement("button"); +let button = document.createElement("button"); button.innerHTML = "Mute Speaker"; button.onclick = () => { iframe.contentWindow.postMessage({ "mute": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Un-Mute Speaker"; button.onclick = () => { iframe.contentWindow.postMessage({ "mute": false }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Toggle Speaker"; button.onclick = () => { iframe.contentWindow.postMessage({ "mute": "toggle" }, '*'); } iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Mute Mic"; button.onclick = () => { iframe.contentWindow.postMessage({ "mic": false }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Un-Mute Mic"; button.onclick = () => { iframe.contentWindow.postMessage({ "mic": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Toggle Mic"; button.onclick = () => { iframe.contentWindow.postMessage({ "mic": "toggle" }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Disconnect"; button.onclick = () => { iframe.contentWindow.postMessage({ "close": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Low Bitrate"; button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 30 }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "High Bitrate"; button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 5000 }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Default Bitrate"; button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": -1 }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Reload"; button.onclick = () => { iframe.contentWindow.postMessage({ "reload": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "50% Volume"; button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 0.5 }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "100% Volume"; button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 1.0 }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Request Stats"; button.onclick = () => { iframe.contentWindow.postMessage({ "getStats": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Request Loudness Levels"; button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": true }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Stop Sending Loudness Levels"; button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": false }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "Say Hello"; button.onclick = () => { iframe.contentWindow.postMessage({ "sendChat": "Hello!" }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "previewWebcam()"; button.onclick = () => { iframe.contentWindow.postMessage({ "function": "previewWebcam" }, '*'); }; iframeContainer.appendChild(button); -const button = document.createElement("button"); +button = document.createElement("button"); button.innerHTML = "CLOSE IFRAME"; button.onclick = () => { iframeContainer.parentNode.removeChild(iframeContainer); }; iframeContainer.appendChild(button); From 4e273219f87ca2148d0a485eb5e3ec2228a5074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 15:57:02 +0100 Subject: [PATCH 16/30] Format the JS and comment a plaintext comment --- IFRAME.md | 206 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 145 insertions(+), 61 deletions(-) diff --git a/IFRAME.md b/IFRAME.md index 32fc099..ce8b529 100644 --- a/IFRAME.md +++ b/IFRAME.md @@ -57,156 +57,240 @@ As for the actually details for methods and options available to dynamically con ```js let button = document.createElement("button"); button.innerHTML = "Mute Speaker"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mute": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mute": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Un-Mute Speaker"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mute": false }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mute": false + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Toggle Speaker"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mute": "toggle" }, '*'); } +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mute": "toggle" + }, '*'); +} iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Mute Mic"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mic": false }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mic": false + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Un-Mute Mic"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mic": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mic": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Toggle Mic"; -button.onclick = () => { iframe.contentWindow.postMessage({ "mic": "toggle" }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "mic": "toggle" + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Disconnect"; -button.onclick = () => { iframe.contentWindow.postMessage({ "close": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "close": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Low Bitrate"; -button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 30 }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "bitrate": 30 + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "High Bitrate"; -button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": 5000 }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "bitrate": 5000 + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Default Bitrate"; -button.onclick = () => { iframe.contentWindow.postMessage({ "bitrate": -1 }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "bitrate": -1 + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Reload"; -button.onclick = () => { iframe.contentWindow.postMessage({ "reload": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "reload": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "50% Volume"; -button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 0.5 }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "volume": 0.5 + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "100% Volume"; -button.onclick = () => { iframe.contentWindow.postMessage({ "volume": 1.0 }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "volume": 1.0 + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Request Stats"; -button.onclick = () => { iframe.contentWindow.postMessage({ "getStats": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "getStats": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Request Loudness Levels"; -button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": true }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "getLoudness": true + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Stop Sending Loudness Levels"; -button.onclick = () => { iframe.contentWindow.postMessage({ "getLoudness": false }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "getLoudness": false + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "Say Hello"; -button.onclick = () => { iframe.contentWindow.postMessage({ "sendChat": "Hello!" }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "sendChat": "Hello!" + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "previewWebcam()"; -button.onclick = () => { iframe.contentWindow.postMessage({ "function": "previewWebcam" }, '*'); }; +button.onclick = () => { + iframe.contentWindow.postMessage({ + "function": "previewWebcam" + }, '*'); +}; iframeContainer.appendChild(button); button = document.createElement("button"); button.innerHTML = "CLOSE IFRAME"; -button.onclick = () => { iframeContainer.parentNode.removeChild(iframeContainer); }; +button.onclick = () => { + iframeContainer.parentNode.removeChild(iframeContainer); +}; iframeContainer.appendChild(button); -As for listening events, where the parent listens for responses or events from the OBSN child frame: +// As for listening events, where the parent listens for responses or events from the OBSN child frame: -//////////// LISTEN FOR EVENTS +// ////////// LISTEN FOR EVENTS const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; const eventer = window[eventMethod]; const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { - if (e.source !== iframe.contentWindow){return} // reject messages send from other iframes + if (e.source !== iframe.contentWindow) { + return + } // reject messages send from other iframes - if ("stats" in e.data){ - const outputWindow = document.createElement("div"); + if ("stats" in e.data) { + const outputWindow = document.createElement("div"); - let out = `
    total_inbound_connections:${e.data.stats.total_inbound_connections}`; - out += `
    total_outbound_connections:${e.data.stats.total_outbound_connections}`; + let out = `
    total_inbound_connections:${ + e.data.stats.total_inbound_connections + }`; + out += `
    total_outbound_connections:${ + e.data.stats.total_outbound_connections + }`; - for (const streamID in e.data.stats.inbound_stats){ - out += `

    streamID: ${streamID}
    `; - out += printValues(e.data.stats.inbound_stats[streamID]); - } + for (const streamID in e.data.stats.inbound_stats) { + out += `

    streamID: ${streamID}
    `; + out += printValues(e.data.stats.inbound_stats[streamID]); + } - outputWindow.innerHTML = out; - iframeContainer.appendChild(outputWindow); - } + outputWindow.innerHTML = out; + iframeContainer.appendChild(outputWindow); + } - if ("gotChat" in e.data){ - const outputWindow = document.createElement("div"); - outputWindow.innerHTML = e.data.gotChat.msg; - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - } + if ("gotChat" in e.data) { + const outputWindow = document.createElement("div"); + outputWindow.innerHTML = e.data.gotChat.msg; + outputWindow.style.border = "1px dotted black"; + iframeContainer.appendChild(outputWindow); + } - if ("action" in e.data){ - const outputWindow = document.createElement("div"); - outputWindow.innerHTML = `child-page-action: ${e.data.action}
    `; - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - } + if ("action" in e.data) { + const outputWindow = document.createElement("div"); + outputWindow.innerHTML = `child-page-action: ${ + e.data.action + }
    `; + outputWindow.style.border = "1px dotted black"; + iframeContainer.appendChild(outputWindow); + } - if ("loudness" in e.data){ - console.log(e.data); - if (document.getElementById("loudness")){ - outputWindow = document.getElementById("loudness"); - } else { - const outputWindow = document.createElement("div"); - outputWindow.style.border="1px dotted black"; - iframeContainer.appendChild(outputWindow); - outputWindow.id = "loudness"; - } - outputWindow.innerHTML = "child-page-action: loudness
    "; - for (const key in e.data.loudness) { - outputWindow.innerHTML += `${key} Loudness: ${e.data.loudness[key]}\n`; -} - outputWindow.style.border="1px black"; + if ("loudness" in e.data) { + console.log(e.data); + if (document.getElementById("loudness")) { + outputWindow = document.getElementById("loudness"); + } else { + const outputWindow = document.createElement("div"); + outputWindow.style.border = "1px dotted black"; + iframeContainer.appendChild(outputWindow); + outputWindow.id = "loudness"; + } + outputWindow.innerHTML = "child-page-action: loudness
    "; + for (const key in e.data.loudness) { + outputWindow.innerHTML += `${key} Loudness: ${ + e.data.loudness[key] + }\n`; + } + outputWindow.style.border = "1px black"; - } + } }); ``` From 43aa080bce3d3252bf21169bee670332f6b2761f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 16:52:28 +0100 Subject: [PATCH 17/30] Update link style in License The license contains a link, which links to the raw file with no markdown features at all, I think if it is markdown, it should be displayed as such. --- LICENCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENCE.md b/LICENCE.md index 24b2a39..4fc7ede 100644 --- a/LICENCE.md +++ b/LICENCE.md @@ -1,5 +1,5 @@ The OBS.Ninja source repository is governed by the GNU AFFERO GENERAL PUBLIC LICENSE. (AGPL-3.0) -That AGPL-3.0 licence can be found here: https://raw.githubusercontent.com/steveseguin/obsninja/master/AGPLv3.md +That AGPL-3.0 licence can be found here: [AGPLv3.md](https://github.com/steveseguin/obsninja/blob/master/AGPLv3.md) In essence, OBS.Ninja is open-source and free to use, both for commercial and non-commercial use. Modifications of AGPL-3.0 licenced code must be made publicly accessible. Please refer to that licence. From 46b00b1ce1821d0d68edf8cf82a343e3c586c36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 17:14:27 +0100 Subject: [PATCH 18/30] First ES6 refactor - Eqeqeq - const and let - arrow functions --- devices.html | 53 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/devices.html b/devices.html index bcb0331..99ae4f5 100644 --- a/devices.html +++ b/devices.html @@ -47,45 +47,46 @@
    From 8338573c108c083f5be7f721c7dedf9e2a79f51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 17:19:28 +0100 Subject: [PATCH 19/30] Template string output --- devices.html | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/devices.html b/devices.html index 99ae4f5..3aea201 100644 --- a/devices.html +++ b/devices.html @@ -146,18 +146,13 @@ Object.entries(json) .sort() .forEach(([key, value]) => { - output += "
    "; - - output += - "" + - value.label + - "" + - value.deviceId + - ""; - - output += "
    "; + output += ` +
    + ${value.label} + + ${value.deviceId} + +
    `; }); output += ""; document.getElementById(element).innerHTML = output; From 9cc109c254da6c06cdcd6b44583938753aa2296b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Mon, 8 Feb 2021 17:21:40 +0100 Subject: [PATCH 20/30] Update target because of the arrow function --- devices.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices.html b/devices.html index 3aea201..49a254c 100644 --- a/devices.html +++ b/devices.html @@ -158,8 +158,8 @@ document.getElementById(element).innerHTML = output; } - document.getElementById("devicesUrl").onclick = () => { - this.select(); + document.getElementById("devicesUrl").onclick = (e) => { + e.target.select(); document.execCommand("copy"); }; From 474e142908a4d1613d2b871427ffdc8b41ca65c8 Mon Sep 17 00:00:00 2001 From: Joel Calado Date: Mon, 8 Feb 2021 21:13:51 +0000 Subject: [PATCH 21/30] tweak /devices CSS --- devices.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/devices.css b/devices.css index db6d72b..79a9458 100644 --- a/devices.css +++ b/devices.css @@ -9,6 +9,7 @@ h1 { padding:10px; background-color:#457b9d; color:white; + border-bottom: 2px solid #3b6a87; } .device { @@ -18,6 +19,9 @@ h1 { font-size: 1rem; padding: 10px; position: relative; + user-select: none; + background: #d0d0d0; + border-radius: 4px; } .device.selected { @@ -55,12 +59,13 @@ h1 { } .notice { - background-color: orange; + background-color: #fff18c; margin: 10px; padding: 20px 20px; font-weight: bold; font-size: 1.2em; text-align: center; + line-height: 1.4em; } .notice a { @@ -89,7 +94,7 @@ h1 { left: 10%; color: white; overflow-wrap: anywhere; - background: #141926; + background: #2c3754; padding: 20px; box-shadow: 0px 0px 10px 5px #00000047; border: 1px solid #333c52; From 864969ebae993b4919f2ebe86833494baaedb8d0 Mon Sep 17 00:00:00 2001 From: Joel Calado Date: Mon, 8 Feb 2021 21:14:21 +0000 Subject: [PATCH 22/30] Update devices.html add some tips to the top notice --- devices.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devices.html b/devices.html index 49a254c..e1374b7 100644 --- a/devices.html +++ b/devices.html @@ -22,7 +22,8 @@
    Device IDs are bound to a combination of domain and browser.
    If you want to use electron-capture, open this URL on the electron-capture - app. + app.
    + Click device names to preset them. Select multiple audio inputs by clicking multiple devices.
    Check for browser and camera capabilities here. From 838acf07acd182749ce09266c47b5ae7f20c7518 Mon Sep 17 00:00:00 2001 From: Joel Calado Date: Mon, 8 Feb 2021 21:14:41 +0000 Subject: [PATCH 23/30] Update devices.html change text --- devices.html | 1 + 1 file changed, 1 insertion(+) diff --git a/devices.html b/devices.html index e1374b7..bdf31a0 100644 --- a/devices.html +++ b/devices.html @@ -43,6 +43,7 @@
    From cbc7971fa5022515a35f9e1484a0595a27a1edcf Mon Sep 17 00:00:00 2001 From: Joel Calado Date: Mon, 8 Feb 2021 21:14:47 +0000 Subject: [PATCH 24/30] Update devices.html --- devices.html | 1 - 1 file changed, 1 deletion(-) diff --git a/devices.html b/devices.html index bdf31a0..05aa496 100644 --- a/devices.html +++ b/devices.html @@ -42,7 +42,6 @@