From a3d005032822274e90a49bb18d6466698140f2d9 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Mon, 27 Oct 2014 15:24:09 +0200 Subject: [PATCH] Implements support for events for the API. Adds toggleChat and toggleContactList commands. Renames filmStrip to toggleFilmStrip command. Fixes issues with removing the embedded Jitsi Meet. --- api_connector.js | 106 ++++++++++++++++++++++++++- app.js | 10 +++ doc/api.md | 93 +++++++++++++++++++++++- external_api.js | 184 +++++++++++++++++++++++++++++++++++++++++++++-- index.html | 8 +-- muc.js | 11 ++- videolayout.js | 12 +++- 7 files changed, 408 insertions(+), 16 deletions(-) diff --git a/api_connector.js b/api_connector.js index b8cb700d4..a28089f3b 100644 --- a/api_connector.js +++ b/api_connector.js @@ -21,7 +21,30 @@ var APIConnector = (function () { displayName: VideoLayout.inputDisplayNameHandler, muteAudio: toggleAudio, muteVideo: toggleVideo, - filmStrip: BottomToolbar.toggleFilmStrip + toggleFilmStrip: BottomToolbar.toggleFilmStrip, + toggleChat: BottomToolbar.toggleChat, + toggleContactList: BottomToolbar.toggleContactList + }; + + + /** + * Maps the supported events and their status + * (true it the event is enabled and false if it is disabled) + * @type {{ + * incommingMessage: boolean, + * outgoingMessage: boolean, + * displayNameChange: boolean, + * participantJoined: boolean, + * participantLeft: boolean + * }} + */ + var events = + { + incommingMessage: false, + outgoingMessage:false, + displayNameChange: false, + participantJoined: false, + participantLeft: false }; /** @@ -51,7 +74,7 @@ var APIConnector = (function () { { window.attachEvent('onmessage', APIConnector.processMessage); } - APIConnector.sendMessage({loaded: true}); + APIConnector.sendMessage({type: "system", loaded: true}); }; /** @@ -72,12 +95,91 @@ var APIConnector = (function () { try { message = JSON.parse(event.data); } catch (e) {} + + if(!message.type) + return; + switch (message.type) + { + case "command": + APIConnector.processCommand(message); + break; + case "event": + APIConnector.processEvent(message); + break; + default: + console.error("Unknown type of the message"); + return; + } + + }; + + /** + * Processes commands from external applicaiton. + * @param message the object with the command + */ + APIConnector.processCommand = function (message) + { + if(message.action != "execute") + { + console.error("Unknown action of the message"); + return; + } for(var key in message) { if(commands[key]) commands[key].apply(null, message[key]); } + }; + /** + * Processes events objects from external applications + * @param event the event + */ + APIConnector.processEvent = function (event) { + if(!event.action) + { + console.error("Event with no action is received."); + return; + } + + switch(event.action) + { + case "add": + for(var i = 0; i < event.events.length; i++) + { + events[event.events[i]] = true; + } + break; + case "remove": + for(var i = 0; i < event.events.length; i++) + { + events[event.events[i]] = false; + } + break; + default: + console.error("Unknown action for event."); + } + + }; + + /** + * Checks whether the event is enabled ot not. + * @param name the name of the event. + * @returns {*} + */ + APIConnector.isEventEnabled = function (name) { + return events[name]; + }; + + /** + * Sends event object to the external application that has been subscribed + * for that event. + * @param name the name event + * @param object data associated with the event + */ + APIConnector.triggerEvent = function (name, object) { + APIConnector.sendMessage({ + type: "event", action: "result", event: name, result: object}); }; /** diff --git a/app.js b/app.js index e22eeafc5..168d50778 100644 --- a/app.js +++ b/app.js @@ -698,6 +698,11 @@ $(document).bind('entered.muc', function (event, jid, info, pres) { // Add Peer's container VideoLayout.ensurePeerContainerExists(jid); + if(APIConnector.isEnabled() && APIConnector.isEventEnabled("participantJoined")) + { + APIConnector.triggerEvent("participantJoined",{jid: jid}); + } + if (focus !== null) { // FIXME: this should prepare the video if (focus.confid === null) { @@ -734,6 +739,11 @@ $(document).bind('left.muc', function (event, jid) { } }, 10); + if(APIConnector.isEnabled() && APIConnector.isEventEnabled("participantLeft")) + { + APIConnector.triggerEvent("participantLeft",{jid: jid}); + } + // Unlock large video if (focusedVideoSrc) { diff --git a/doc/api.md b/doc/api.md index 9b663183f..635be80c3 100644 --- a/doc/api.md +++ b/doc/api.md @@ -33,6 +33,7 @@ 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) @@ -56,10 +57,18 @@ api.executeCommand('muteAudio', []) ``` api.executeCommand('muteVideo', []) ``` -* **filmStrip** - hides / shows the film strip. No arguments are required. +* **toggleFilmStrip** - hides / shows the film strip. No arguments are required. ``` api.executeCommand('filmStrip', []) ``` +* **toggleChat** - hides / shows the chat. No arguments are required. +``` +api.executeCommand('toggleChat', []) +``` +* **toggleContactList** - hides / shows the contact list. No arguments are required. +``` +api.executeCommand('toggleContactList', []) +``` You can also execute multiple commands using the method ```executeCommands```. ``` @@ -72,7 +81,87 @@ commands. api.executeCommands({displayName: ['nickname'], muteAudio: []}); ``` -You can also remove the embedded Jitsi Meet Conference with the following code: +You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method. +``` +api.addEventListener(event, listener) +``` +The ```event``` parameter is String object with the name of the event. +The ```listener``` paramenter is Function object with one argument that will be notified when the event occurs +with data related to the event. + +Currently we support the following events: + +* **incommingMessage** - event notifications about incomming +messages. The listener will receive object with the following structure: +``` +{ +"from": from,//JID of the user that sent the message +"nick": nick,//the nickname of the user that sent the message +"message": txt//the text of the message +} +``` +* **outgoingMessage** - event notifications about outgoing +messages. The listener will receive object with the following structure: +``` +{ +"message": txt//the text of the message +} +``` +* **displayNameChanged** - event notifications about display name +change. The listener will receive object with the following structure: +``` +{ +jid: jid,//the JID of the participant that changed his display name +displayname: displayName //the new display name +} +``` +* **participantJoined** - event notifications about new participant. +The listener will receive object with the following structure: +``` +{ +jid: jid //the jid of the participant +} +``` +* **participantLeft** - event notifications about participant that left room. +The listener will receive object with the following structure: +``` +{ +jid: jid //the jid of the participant +} +``` + +You can also add multiple event listeners by using ```addEventListeners```. +This method requires one argument of type Object. The object argument must +have keys with the names of the events and values the listeners of the events. + +``` +function incommingMessageListener(object) +{ +... +} + +function outgoingMessageListener(object) +{ +... +} + +api.addEventListeners({ + incommingMessage: incommingMessageListener, + outgoingMessage: outgoingMessageListener}) +``` + +If you want to remove a listener you can use ```removeEventListener``` method with argument the name of the event. +``` +api.removeEventListener("incommingMessage"); +``` + +If you want to remove more than one event you can use ```removeEventListeners``` method with argument + array with the names of the events. +``` +api.removeEventListeners(["incommingMessage", "outgoingMessageListener"]); +``` + +You can remove the embedded Jitsi Meet Conference with the following code: ``` api.dispose() ``` diff --git a/external_api.js b/external_api.js index 2e2c36a39..9bf7b08fe 100644 --- a/external_api.js +++ b/external_api.js @@ -48,7 +48,7 @@ var JitsiMeetExternalAPI = (function() this.iframeHolder.style.width = width + "px"; this.iframeHolder.style.height = height + "px"; this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id; - this.url = "https://" + domain + "/"; + this.url = "http://" + domain + "/"; if(room_name) this.url += room_name; this.url += "#external"; @@ -57,12 +57,16 @@ var JitsiMeetExternalAPI = (function() this.frame = document.createElement("iframe"); this.frame.src = this.url; this.frame.name = this.frameName; + this.frame.id = this.frameName; this.frame.width = "100%"; this.frame.height = "100%"; + this.frame.setAttribute("allowFullScreen","true"); this.frame = this.iframeHolder.appendChild(this.frame); + this.frameLoaded = false; this.initialCommands = []; + this.eventHandlers = {}; this.initListeners(); } @@ -108,7 +112,7 @@ var JitsiMeetExternalAPI = (function() var argumentsArray = argumentsList; if(!argumentsArray) argumentsArray = []; - var object = {}; + var object = {type: "command", action: "execute"}; object[name] = argumentsArray; this.sendMessage(object); }; @@ -125,9 +129,147 @@ var JitsiMeetExternalAPI = (function() * arguments for the command. */ JitsiMeetExternalAPI.prototype.executeCommands = function (object) { + object.type = "command"; + object.action = "execute"; this.sendMessage(object); }; + /** + * Adds event listeners to Meet Jitsi. The object key should be the name of the + * event and value - the listener. + * Currently we support the following + * events: + * incommingMessage - receives event notifications about incomming + * messages. The listener will receive object with the following structure: + * {{ + * "from": from,//JID of the user that sent the message + * "nick": nick,//the nickname of the user that sent the message + * "message": txt//the text of the message + * }} + * outgoingMessage - receives event notifications about outgoing + * messages. The listener will receive object with the following structure: + * {{ + * "message": txt//the text of the message + * }} + * displayNameChanged - receives event notifications about display name + * change. The listener will receive object with the following structure: + * {{ + * jid: jid,//the JID of the participant that changed his display name + * displayname: displayName //the new display name + * }} + * participantJoined - receives event notifications about new participant. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * participantLeft - receives event notifications about participant that left room. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * @param object + */ + JitsiMeetExternalAPI.prototype.addEventListeners + = function (object) + { + + var message = {type: "event", action: "add", events: []}; + for(var i in object) + { + message.events.push(i); + this.eventHandlers[i] = object[i]; + } + this.sendMessage(message); + }; + + /** + * Adds event listeners to Meet Jitsi. Currently we support the following + * events: + * incommingMessage - receives event notifications about incomming + * messages. The listener will receive object with the following structure: + * {{ + * "from": from,//JID of the user that sent the message + * "nick": nick,//the nickname of the user that sent the message + * "message": txt//the text of the message + * }} + * outgoingMessage - receives event notifications about outgoing + * messages. The listener will receive object with the following structure: + * {{ + * "message": txt//the text of the message + * }} + * displayNameChanged - receives event notifications about display name + * change. The listener will receive object with the following structure: + * {{ + * jid: jid,//the JID of the participant that changed his display name + * displayname: displayName //the new display name + * }} + * participantJoined - receives event notifications about new participant. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * participantLeft - receives event notifications about participant that left room. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * @param event the name of the event + * @param listener the listener + */ + JitsiMeetExternalAPI.prototype.addEventListener + = function (event, listener) + { + + var message = {type: "event", action: "add", events: [event]}; + this.eventHandlers[event] = listener; + this.sendMessage(message); + }; + + /** + * Removes event listener. + * @param event the name of the event. + */ + JitsiMeetExternalAPI.prototype.removeEventListener + = function (event) + { + if(!this.eventHandlers[event]) + { + console.error("The event " + event + " is not registered."); + return; + } + var message = {type: "event", action: "remove", events: [event]}; + delete this.eventHandlers[event]; + this.sendMessage(message); + }; + + /** + * Removes event listeners. + * @param events array with the names of the events. + */ + JitsiMeetExternalAPI.prototype.removeEventListeners + = function (events) + { + var eventsArray = []; + for(var i = 0; i < events.length; i++) + { + var event = events[i]; + if(!this.eventHandlers[event]) + { + console.error("The event " + event + " is not registered."); + continue; + } + delete this.eventHandlers[event]; + eventsArray.push(event); + } + + if(eventsArray.length > 0) + { + this.sendMessage( + {type: "event", action: "remove", events: eventsArray}); + } + + }; + /** * Processes message events sent from Jitsi Meet * @param event the event @@ -138,10 +280,33 @@ var JitsiMeetExternalAPI = (function() try { message = JSON.parse(event.data); } catch (e) {} - if(message.loaded) - { - this.onFrameLoaded(); + + if(!message.type) { + console.error("Message without type is received."); + return; } + switch (message.type) + { + case "system": + if(message.loaded) + { + this.onFrameLoaded(); + } + break; + case "event": + if(message.action != "result" || + !message.event || !this.eventHandlers[message.event]) + { + console.warn("The received event cannot be parsed."); + return; + } + this.eventHandlers[message.event](message.result); + break; + default : + console.error("Unknown message type."); + return; + } + }; @@ -191,7 +356,14 @@ var JitsiMeetExternalAPI = (function() window.detachEvent('onmessage', this.eventListener); } - this.iframeHolder.parentNode.removeChild(this.iframeHolder); + var frame = document.getElementById(this.frameName); + if(frame) + frame.src = 'about:blank'; + var self = this; + window.setTimeout(function () { + self.iframeHolder.removeChild(self.frame); + self.iframeHolder.parentNode.removeChild(self.iframeHolder); + }, 10); }; return JitsiMeetExternalAPI; diff --git a/index.html b/index.html index a2b7fa5eb..07d1ccb75 100644 --- a/index.html +++ b/index.html @@ -30,11 +30,11 @@ - + - + @@ -47,7 +47,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/muc.js b/muc.js index 3580716e4..a71dc2832 100644 --- a/muc.js +++ b/muc.js @@ -201,6 +201,10 @@ Strophe.addConnectionPlugin('emuc', { msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up(); } this.connection.send(msg); + if(APIConnector.isEnabled() && APIConnector.isEventEnabled("outgoingMessage")) + { + APIConnector.triggerEvent("outgoingMessage", {"message": body}); + } }, setSubject: function (subject){ var msg = $msg({to: this.roomjid, type: 'groupchat'}); @@ -234,8 +238,13 @@ Strophe.addConnectionPlugin('emuc', { if (txt) { console.log('chat', nick, txt); - Chat.updateChatConversation(from, nick, txt); + if(APIConnector.isEnabled() && APIConnector.isEventEnabled("incommingMessage")) + { + if(from != this.myroomjid) + APIConnector.triggerEvent("incommingMessage", + {"from": from, "nick": nick, "message": txt}); + } } return true; }, diff --git a/videolayout.js b/videolayout.js index 59dc36ebd..968c7d265 100644 --- a/videolayout.js +++ b/videolayout.js @@ -1274,18 +1274,28 @@ var VideoLayout = (function (my) { */ $(document).bind('displaynamechanged', function (event, jid, displayName, status) { + var name = null; if (jid === 'localVideoContainer' || jid === connection.emuc.myroomjid) { + name = nickname; setDisplayName('localVideoContainer', displayName); } else { VideoLayout.ensurePeerContainerExists(jid); - + name = $('#participant_' + Strophe.getResourceFromJid(jid) + "_name").text(); setDisplayName( 'participant_' + Strophe.getResourceFromJid(jid), displayName, status); } + + if(APIConnector.isEnabled() && APIConnector.isEventEnabled("displayNameChange")) + { + if(jid === 'localVideoContainer') + jid = connection.emuc.myroomjid; + if(!name || name != displayName) + APIConnector.triggerEvent("displayNameChange",{jid: jid, displayname: displayName}); + } }); /**