From 667a8c149318262ded35b97ca0d429d292b3b384 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Wed, 22 Oct 2014 12:03:07 +0300 Subject: [PATCH] Implements API that allows external applications to embed Jitsi Meet. --- api_connector.js | 100 ++++++++++++++++++++++++ app.js | 5 +- doc/api.md | 80 +++++++++++++++++++ external_api.js | 199 +++++++++++++++++++++++++++++++++++++++++++++++ index.html | 5 +- videolayout.js | 48 ++++++------ 6 files changed, 410 insertions(+), 27 deletions(-) create mode 100644 api_connector.js create mode 100644 doc/api.md create mode 100644 external_api.js diff --git a/api_connector.js b/api_connector.js new file mode 100644 index 000000000..b8cb700d4 --- /dev/null +++ b/api_connector.js @@ -0,0 +1,100 @@ +/** + * Implements API class that communicates with external api class + * and provides interface to access Jitsi Meet features by external + * applications that embed Jitsi Meet + */ +var APIConnector = (function () { + + function APIConnector() { } + + /** + * List of the available commands. + * @type {{ + * displayName: inputDisplayNameHandler, + * muteAudio: toggleAudio, + * muteVideo: toggleVideo, + * filmStrip: toggleFilmStrip + * }} + */ + var commands = + { + displayName: VideoLayout.inputDisplayNameHandler, + muteAudio: toggleAudio, + muteVideo: toggleVideo, + filmStrip: BottomToolbar.toggleFilmStrip + }; + + /** + * Check whether the API should be enabled or not. + * @returns {boolean} + */ + APIConnector.isEnabled = function () { + var hash = location.hash; + if(hash && hash.indexOf("external") > -1 && window.postMessage) + return true; + return false; + }; + + /** + * Initializes the APIConnector. Setups message event listeners that will + * receive information from external applications that embed Jitsi Meet. + * It also sends a message to the external application that APIConnector + * is initialized. + */ + APIConnector.init = function () { + if (window.addEventListener) + { + window.addEventListener('message', + APIConnector.processMessage, false); + } + else + { + window.attachEvent('onmessage', APIConnector.processMessage); + } + APIConnector.sendMessage({loaded: true}); + }; + + /** + * Sends message to the external application. + * @param object + */ + APIConnector.sendMessage = function (object) { + window.parent.postMessage(JSON.stringify(object), "*"); + }; + + /** + * Processes a message event from the external application + * @param event the message event + */ + APIConnector.processMessage = function(event) + { + var message; + try { + message = JSON.parse(event.data); + } catch (e) {} + for(var key in message) + { + if(commands[key]) + commands[key].apply(null, message[key]); + } + + }; + + /** + * Removes the listeners. + */ + APIConnector.dispose = function () { + if(window.removeEventListener) + { + window.removeEventListener("message", + APIConnector.processMessage, false); + } + else + { + window.detachEvent('onmessage', APIConnector.processMessage); + } + + }; + + return APIConnector; +})(); \ No newline at end of file diff --git a/app.js b/app.js index 4fd665fcb..b7f7bb703 100644 --- a/app.js +++ b/app.js @@ -1126,6 +1126,8 @@ function getCameraVideoSize(videoWidth, $(document).ready(function () { document.title = brand.appName; + if(APIConnector.isEnabled()) + APIConnector.init(); if(config.enableWelcomePage && window.location.pathname == "/" && (!window.localStorage.welcomePageDisabled @@ -1186,7 +1188,6 @@ $(document).ready(function () { } }); - if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){ var updateTimeout; var animateTimeout; @@ -1322,6 +1323,8 @@ $(window).bind('beforeunload', function () { }); } disposeConference(true); + if(APIConnector.isEnabled()) + APIConnector.dispose(); }); function disposeConference(onUnload) { diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 000000000..9b663183f --- /dev/null +++ b/doc/api.md @@ -0,0 +1,80 @@ +Jitsi Meet API +============ + +You can use Jitsi Meet API to embed Jitsi Meet in to your application. + +Installation +========== + +To embed Jitsi Meet in your application you need to add Jitsi Meet API library +```javascript + +``` + +The next step for embedding Jitsi Meet is to create the Jitsi Meet API object +```javascript + +``` +You can paste that lines in your html code where you want to be placed the Jitsi Meet conference +or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI +constructor. +```javascript + var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement); +``` +If you don't specify room the user will enter in new conference with random room name. + +Controlling embedded Jitsi Meet Conference +========= + +You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object. +You can send command to Jitsi Meet conference using ```executeCommand```. +``` +api.executeCommand(command, arguments) +``` +The ```command``` parameter is String object with the name of the command. +The ```arguments``` parameter is array with the arguments required by the command. +If no arguments are required by the command this parameter can be omitted or you can pass empty array. +Currently we support the following commands: + + +* **displayName** - sets the display name of the local participant. This command requires one argument - +the new display name to be set +``` +api.executeCommand('displayName', ['New Nickname']); +``` +* **muteAudio** - mutes / unmutes the audio for the local participant. No arguments are required. +``` +api.executeCommand('muteAudio', []) +``` +* **muteVideo** - mutes / unmutes the video for the local participant. No arguments are required. +``` +api.executeCommand('muteVideo', []) +``` +* **filmStrip** - hides / shows the film strip. No arguments are required. +``` +api.executeCommand('filmStrip', []) +``` + +You can also execute multiple commands using the method ```executeCommands```. +``` +api.executeCommands(commands) +``` +The ```commands``` parameter is object with keys the names of the commands and values the arguments for the +commands. + +``` +api.executeCommands({displayName: ['nickname'], muteAudio: []}); +``` + +You can also remove the embedded Jitsi Meet Conference with the following code: +``` +api.dispose() +``` + +It is a good practice to remove the conference before the page is unloaded. \ No newline at end of file diff --git a/external_api.js b/external_api.js new file mode 100644 index 000000000..2e2c36a39 --- /dev/null +++ b/external_api.js @@ -0,0 +1,199 @@ +/** + * Implements API class that embeds Jitsi Meet in external applications. + */ +var JitsiMeetExternalAPI = (function() +{ + /** + * The minimum width for the Jitsi Meet frame + * @type {number} + */ + var MIN_WIDTH = 790; + + /** + * The minimum height for the Jitsi Meet frame + * @type {number} + */ + var MIN_HEIGHT = 300; + + /** + * Constructs new API instance. Creates iframe element that loads + * Jitsi Meet. + * @param domain the domain name of the server that hosts the conference + * @param room_name the name of the room to join + * @param width width of the iframe + * @param height height of the iframe + * @param parent_node the node that will contain the iframe + * @constructor + */ + function JitsiMeetExternalAPI(domain, room_name, width, height, parent_node) + { + this.parentNode = null; + if(parent_node) + { + this.parentNode = parent_node; + } + else + { + var scriptTag = document.scripts[document.scripts.length - 1]; + this.parentNode = scriptTag.parentNode; + } + + this.iframeHolder = + this.parentNode.appendChild(document.createElement("div")); + this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id; + if(width < MIN_WIDTH) + width = MIN_WIDTH; + if(height < MIN_HEIGHT) + height = MIN_HEIGHT; + this.iframeHolder.style.width = width + "px"; + this.iframeHolder.style.height = height + "px"; + this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id; + this.url = "https://" + domain + "/"; + if(room_name) + this.url += room_name; + this.url += "#external"; + JitsiMeetExternalAPI.id++; + + this.frame = document.createElement("iframe"); + this.frame.src = this.url; + this.frame.name = this.frameName; + this.frame.width = "100%"; + this.frame.height = "100%"; + this.frame = this.iframeHolder.appendChild(this.frame); + + this.frameLoaded = false; + this.initialCommands = []; + this.initListeners(); + } + + /** + * Last id of api object + * @type {number} + */ + JitsiMeetExternalAPI.id = 0; + + /** + * Sends the passed object to Jitsi Meet + * @param object the object to be sent + */ + JitsiMeetExternalAPI.prototype.sendMessage = function(object) + { + if(this.frameLoaded) + { + this.frame.contentWindow.postMessage( + JSON.stringify(object), this.frame.src); + } + else + { + this.initialCommands.push(object); + } + + }; + + /** + * Executes command. The available commands are: + * displayName - sets the display name of the local participant to the value + * passed in the arguments array. + * muteAudio - mutes / unmutes audio with no arguments + * muteVideo - mutes / unmutes video with no arguments + * filmStrip - hides / shows the film strip with no arguments + * If the command doesn't require any arguments the parameter should be set + * to empty array or it may be omitted. + * @param name the name of the command + * @param arguments array of arguments + */ + JitsiMeetExternalAPI.prototype.executeCommand = function(name, + argumentsList) + { + var argumentsArray = argumentsList; + if(!argumentsArray) + argumentsArray = []; + var object = {}; + object[name] = argumentsArray; + this.sendMessage(object); + }; + + /** + * Executes commands. The available commands are: + * displayName - sets the display name of the local participant to the value + * passed in the arguments array. + * muteAudio - mutes / unmutes audio with no arguments + * muteVideo - mutes / unmutes video with no arguments + * filmStrip - hides / shows the film strip with no arguments + * @param object the object with commands to be executed. The keys of the + * object are the commands that will be executed and the values are the + * arguments for the command. + */ + JitsiMeetExternalAPI.prototype.executeCommands = function (object) { + this.sendMessage(object); + }; + + /** + * Processes message events sent from Jitsi Meet + * @param event the event + */ + JitsiMeetExternalAPI.prototype.processMessage = function(event) + { + var message; + try { + message = JSON.parse(event.data); + } catch (e) {} + if(message.loaded) + { + this.onFrameLoaded(); + } + + }; + + /** + * That method is called when the Jitsi Meet is loaded. Executes saved + * commands that are send before the frame was loaded. + */ + JitsiMeetExternalAPI.prototype.onFrameLoaded = function () { + this.frameLoaded = true; + for (var i = 0; i < this.initialCommands.length; i++) + { + this.sendMessage(this.initialCommands[i]); + } + this.initialCommands = null; + }; + + /** + * Setups the listener for message events from Jitsi Meet. + */ + JitsiMeetExternalAPI.prototype.initListeners = function () { + var self = this; + this.eventListener = function (event) { + self.processMessage(event); + }; + if (window.addEventListener) + { + window.addEventListener('message', + this.eventListener, false); + } + else + { + window.attachEvent('onmessage', this.eventListener); + } + }; + + /** + * Removes the listeners and removes the Jitsi Meet frame. + */ + JitsiMeetExternalAPI.prototype.dispose = function () { + if (window.removeEventListener) + { + window.removeEventListener('message', + this.eventListener, false); + } + else + { + window.detachEvent('onmessage', + this.eventListener); + } + this.iframeHolder.parentNode.removeChild(this.iframeHolder); + }; + + return JitsiMeetExternalAPI; + +})(); \ No newline at end of file diff --git a/index.html b/index.html index 14185c3a2..557823409 100644 --- a/index.html +++ b/index.html @@ -34,7 +34,7 @@ - + @@ -47,7 +47,7 @@ - + @@ -60,6 +60,7 @@ + diff --git a/videolayout.js b/videolayout.js index 1f89ea99c..b3273177b 100644 --- a/videolayout.js +++ b/videolayout.js @@ -622,36 +622,14 @@ var VideoLayout = (function (my) { $('#editDisplayName').focus(); $('#editDisplayName').select(); - var inputDisplayNameHandler = function (name) { - if (nickname !== name) { - nickname = name; - window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); - - Chat.setChatConversationMode(true); - } - - if (!$('#localDisplayName').is(":visible")) { - if (nickname) - $('#localDisplayName').text(nickname + " (me)"); - else - $('#localDisplayName') - .text(defaultLocalDisplayName); - $('#localDisplayName').show(); - } - - $('#editDisplayName').hide(); - }; - $('#editDisplayName').one("focusout", function (e) { - inputDisplayNameHandler(this.value); + VideoLayout.inputDisplayNameHandler(this.value); }); $('#editDisplayName').on('keydown', function (e) { if (e.keyCode === 13) { e.preventDefault(); - inputDisplayNameHandler(this.value); + VideoLayout.inputDisplayNameHandler(this.value); } }); }); @@ -659,6 +637,28 @@ var VideoLayout = (function (my) { } }; + my.inputDisplayNameHandler = function (name) { + if (nickname !== name) { + nickname = name; + window.localStorage.displayname = nickname; + connection.emuc.addDisplayNameToPresence(nickname); + connection.emuc.sendPresence(); + + Chat.setChatConversationMode(true); + } + + if (!$('#localDisplayName').is(":visible")) { + if (nickname) + $('#localDisplayName').text(nickname + " (me)"); + else + $('#localDisplayName') + .text(defaultLocalDisplayName); + $('#localDisplayName').show(); + } + + $('#editDisplayName').hide(); + }; + /** * Shows/hides the display name on the remote video. * @param videoSpanId the identifier of the video span element