diff --git a/.gitignore b/.gitignore index 69e79e789..ccffffc3e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ node_modules *.iml .*.tmp deploy-local.sh -libs/app.bundle.* -libs/lib-jitsi-meet* -libs/external_connect.js +libs/ all.css .remote-sync.json diff --git a/Makefile b/Makefile index 51dc88acf..393f72703 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,9 @@ DEPLOY_DIR = libs BROWSERIFY_FLAGS = -d OUTPUT_DIR = . LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/ +IFRAME_API_DIR = ./modules/API/external -all: update-deps compile uglify deploy clean +all: update-deps compile compile-iframe-api uglify uglify-iframe-api deploy clean update-deps: $(NPM) install @@ -17,8 +18,11 @@ update-deps: compile: $(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js +compile-iframe-api: + $(BROWSERIFY) $(BROWSERIFY_FLAGS) -e $(IFRAME_API_DIR)/external_api.js -s JitsiMeetExternalAPI | $(EXORCIST) $(OUTPUT_DIR)/external_api.js.map > $(OUTPUT_DIR)/external_api.js + clean: - rm -f $(OUTPUT_DIR)/app.bundle.* + rm -f $(OUTPUT_DIR)/app.bundle.* $(OUTPUT_DIR)/external_api.* deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local @@ -28,6 +32,8 @@ deploy-init: deploy-appbundle: cp $(OUTPUT_DIR)/app.bundle.min.js $(OUTPUT_DIR)/app.bundle.min.map \ $(OUTPUT_DIR)/app.bundle.js $(OUTPUT_DIR)/app.bundle.js.map \ + $(OUTPUT_DIR)/external_api.js.map $(OUTPUT_DIR)/external_api.js \ + $(OUTPUT_DIR)/external_api.min.map $(OUTPUT_DIR)/external_api.min.js \ $(DEPLOY_DIR) deploy-lib-jitsi-meet: @@ -46,6 +52,9 @@ deploy-local: uglify: $(UGLIFYJS) -p relative $(OUTPUT_DIR)/app.bundle.js -o $(OUTPUT_DIR)/app.bundle.min.js --source-map $(OUTPUT_DIR)/app.bundle.min.map --in-source-map $(OUTPUT_DIR)/app.bundle.js.map +uglify-iframe-api: + $(UGLIFYJS) -p relative $(OUTPUT_DIR)/external_api.js -o $(OUTPUT_DIR)/external_api.min.js --source-map $(OUTPUT_DIR)/external_api.min.map --in-source-map $(OUTPUT_DIR)/external_api.js.map + source-package: mkdir -p source_package/jitsi-meet/css && \ diff --git a/app.js b/app.js index 2b592ff25..155d941c7 100644 --- a/app.js +++ b/app.js @@ -116,10 +116,7 @@ function init() { APP.keyboardshortcut.init(); }).catch(function (err) { APP.UI.hideRingOverLay(); - APP.API.sendPostisMessage({ - method: 'video-conference-left', - params: {roomName: APP.conference.roomName} - }); + APP.API.notifyConferenceLeft(APP.conference.roomName); console.error(err); }); } diff --git a/conference.js b/conference.js index 70da1dc49..bc8e243e9 100644 --- a/conference.js +++ b/conference.js @@ -207,10 +207,7 @@ function maybeRedirectToWelcomePage() { function disconnectAndShowFeedback(requestFeedback) { APP.UI.hideRingOverLay(); connection.disconnect(); - APP.API.sendPostisMessage({ - method: 'video-conference-left', - params: {roomName: APP.conference.roomName} - }); + APP.API.notifyConferenceLeft(APP.conference.roomName); if (requestFeedback) { return APP.UI.requestFeedback(); } else { @@ -456,6 +453,14 @@ export default { videoMuted: false, isSharingScreen: false, isDesktopSharingEnabled: false, + /* + * Whether the local "raisedHand" flag is on. + */ + isHandRaised: false, + /* + * Whether the local participant is the dominant speaker in the conference. + */ + isDominantSpeaker: false, /** * Open new connection and join to the conference. * @param {object} options @@ -496,9 +501,6 @@ export default { this._createRoom(tracks); this.isDesktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled(); - if(this.isDesktopSharingEnabled) - APP.API.addPostisMessageListener('toggle-share-screen', - () => this.toggleScreenSharing()); // if user didn't give access to mic or camera or doesn't have // them at all, we disable corresponding toolbar buttons @@ -939,10 +941,7 @@ export default { // add local streams when joined to the conference room.on(ConferenceEvents.CONFERENCE_JOINED, () => { APP.UI.mucJoined(); - APP.API.sendPostisMessage({ - method: 'video-conference-joined', - params: {roomName: APP.conference.roomName} - }); + APP.API.notifyConferenceJoined(APP.conference.roomName); }); room.on( @@ -1049,6 +1048,16 @@ export default { APP.UI.handleLastNEndpoints(ids, enteringIds); }); room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => { + if (this.isLocalId(id)) { + this.isDominantSpeaker = true; + this.setRaisedHand(false); + } else { + this.isDominantSpeaker = false; + var participant = room.getParticipantById(id); + if (participant) { + APP.UI.setRaisedHandStatus(participant, false); + } + } APP.UI.markDominantSpeaker(id); }); @@ -1071,6 +1080,13 @@ export default { APP.UI.changeDisplayName(id, displayName); }); + room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED, + (participant, name, oldValue, newValue) => { + if (name === "raisedHand") { + APP.UI.setRaisedHandStatus(participant, newValue); + } + }); + room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => { console.log("Received recorder status change: ", status, error); APP.UI.updateRecordingState(status); @@ -1471,5 +1487,30 @@ export default { mediaDeviceHelper.setCurrentMediaDevices(devices); APP.UI.onAvailableDevicesChanged(devices); }); + }, + + /** + * Toggles the local "raised hand" status, if the current state allows + * toggling. + */ + maybeToggleRaisedHand() { + // If we are the dominant speaker, we don't enable "raise hand". + if (this.isHandRaised || !this.isDominantSpeaker) { + this.setRaisedHand(!this.isHandRaised); + } + }, + + /** + * Sets the local "raised hand" status to a particular value. + */ + setRaisedHand(raisedHand) { + if (raisedHand !== this.isHandRaised) + { + this.isHandRaised = raisedHand; + // Advertise the updated status + room.setLocalParticipantProperty("raisedHand", raisedHand); + // Update the view + APP.UI.setLocalRaisedHandStatus(raisedHand); + } } }; \ No newline at end of file diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 9d16b77e2..62dcdcbd6 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -310,7 +310,7 @@ z-index: 3; } -.videocontainer>span.dominantspeakerindicator { +.videocontainer>span.indicator { bottom: 0px; left: 0px; width: 25px; @@ -327,7 +327,7 @@ border: 0px; } -#speakerindicatoricon { +#indicatoricon { padding-top: 5px; } diff --git a/debian/control b/debian/control index 96284058d..bb39000d6 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: net Priority: extra Maintainer: Jitsi Team Uploaders: Emil Ivov , Damian Minkov -Build-Depends: debhelper (>= 8.0.0), yui-compressor +Build-Depends: debhelper (>= 8.0.0) Standards-Version: 3.9.6 Homepage: https://jitsi.org/meet diff --git a/doc/api.md b/doc/api.md index 10d0ee61d..91aba6749 100644 --- a/doc/api.md +++ b/doc/api.md @@ -20,13 +20,13 @@ The next step for embedding Jitsi Meet is to create the Jitsi Meet API object var height = 700; var api = new JitsiMeetExternalAPI(domain, room, width, height); -``` +``` 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. You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use: @@ -34,24 +34,24 @@ You can overwrite options set in config.js and interface_config.js. For example, var configOverwrite = {enableSimulcast: false}; var interfaceConfigOverwrite = {filmStripOnly: true}; var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite); -``` +``` 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```. +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. +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 - +* **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']); @@ -77,7 +77,12 @@ api.executeCommand('toggleChat', []) api.executeCommand('toggleContactList', []) ``` -You can also execute multiple commands using the method ```executeCommands```. +* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required. +``` +api.executeCommand('toggleShareScreen', []) +``` + +You can also execute multiple commands using the method ```executeCommands```. ``` api.executeCommands(commands) ``` @@ -136,9 +141,23 @@ The listener will receive object with the following structure: jid: jid //the jid of the participant } ``` +* **video-conference-joined** - event notifications fired when the local user has joined the video conference. +The listener will receive object with the following structure: +``` +{ +roomName: room //the room name of the conference +} +``` +* **video-conference-left** - event notifications fired when the local user has left the video conference. +The listener will receive object with the following structure: +``` +{ +roomName: room //the room name of the conference +} +``` You can also add multiple event listeners by using ```addEventListeners```. -This method requires one argument of type Object. The object argument must +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. ``` @@ -173,4 +192,4 @@ You can 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. +It is a good practice to remove the conference before the page is unloaded. diff --git a/doc/debian/jitsi-meet/jitsi-meet.example b/doc/debian/jitsi-meet/jitsi-meet.example index 2d1c950bd..1f92fc06c 100644 --- a/doc/debian/jitsi-meet/jitsi-meet.example +++ b/doc/debian/jitsi-meet/jitsi-meet.example @@ -33,6 +33,11 @@ server { ssi on; } + # Backward compatibility + location ~ /external_api.* { + root /usr/share/jitsi-meet/libs; + } + # BOSH location /http-bind { proxy_pass http://localhost:5280/http-bind; diff --git a/external_api.js b/external_api.js deleted file mode 100644 index 2a858dbd8..000000000 --- a/external_api.js +++ /dev/null @@ -1,378 +0,0 @@ -/** - * 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 - * @param filmStripOnly if the value is true only the small videos will be - * visible. - * @param noSsl if the value is true https won't be used - * @constructor - */ - function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode, - configOverwrite, interfaceConfigOverwrite, noSsl) { - if (!width || width < MIN_WIDTH) - width = MIN_WIDTH; - if (!height || height < MIN_HEIGHT) - height = MIN_HEIGHT; - - this.parentNode = null; - if (parentNode) { - this.parentNode = parentNode; - } 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) - this.iframeHolder.style.width = width + "px"; - if(height) - this.iframeHolder.style.height = height + "px"; - this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id; - this.url = (noSsl) ? "http" : "https" +"://" + domain + "/"; - if(room_name) - this.url += room_name; - this.url += "#external=true"; - - var key; - if (configOverwrite) { - for (key in configOverwrite) { - if (!configOverwrite.hasOwnProperty(key) || - typeof key !== 'string') - continue; - this.url += "&config." + key + "=" + configOverwrite[key]; - } - } - - if (interfaceConfigOverwrite) { - for (key in interfaceConfigOverwrite) { - if (!interfaceConfigOverwrite.hasOwnProperty(key) || - typeof key !== 'string') - continue; - this.url += "&interfaceConfig." + key + "=" + - interfaceConfigOverwrite[key]; - } - } - - JitsiMeetExternalAPI.id++; - - 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(); - } - - /** - * 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. - * toggleAudio - mutes / unmutes audio with no arguments - * toggleVideo - 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 = {type: "command", action: "execute"}; - 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. - * toggleAudio - mutes / unmutes audio with no arguments - * toggleVideo - 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) { - 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: - * incomingMessage - receives event notifications about incoming - * 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 the participant that - * left the 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: - * incomingMessage - receives event notifications about incoming - * 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 the that - * left the 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 - */ - JitsiMeetExternalAPI.prototype.processMessage = function(event) { - var message; - try { - message = JSON.parse(event.data); - } catch (e) {} - - 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; - } - }; - - /** - * 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); - } - 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 469b1e2cb..755a504db 100644 --- a/index.html +++ b/index.html @@ -241,7 +241,7 @@
- +