Compare commits

..

20 Commits
1651 ... 1663

Author SHA1 Message Date
yanas
53e784094a Merge pull request #1308 from jitsi/ss_resize_remote
Fix for the size of remote desktop sharing videos
2017-02-08 15:31:06 -06:00
hristoterezov
0e92e48376 fix(ss): resize for remote videos 2017-02-08 14:58:42 -06:00
Lyubomir Marinov
4c9943ac38 Fix an image path on the mobile landing page 2017-02-08 12:41:51 -06:00
Дамян Минков
4bd0fd145d Merge pull request #1293 from jitsi/prosody_plugin_muc_all_owners
prosody plugin to make all users owners/moderators
2017-02-08 13:17:42 +02:00
Lyubomir Marinov
01ae82eb28 No Temasys alert on mobile Web 2017-02-07 21:54:08 -06:00
Lyubomir Marinov
e21eae0933 Prepare for webpack 2 2017-02-07 15:44:37 -06:00
Lyubomir Marinov
2f047c50dc Revert "No Temasys alert on mobile Web"
This reverts commit b09e86352f.
2017-02-07 15:21:34 -06:00
yanas
e397e1a80c Merge pull request #1303 from jitsi/no-temasys-alert-on-mobile
No Temasys alert on mobile (Web)
2017-02-07 15:11:09 -06:00
Lyubomir Marinov
b09e86352f No Temasys alert on mobile Web 2017-02-07 15:08:38 -06:00
Lyubomir Marinov
8687b69167 Consistency
Be consistent about formatting within 1 and the same file.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
6c5468d904 Simplify the source code
If half the file is written in ES6, it is easier to read if the rest of
the file is in ES6 as well. If ES6 is used, then const is better than
let. If source code is shorter yet as readable as the long version, then
prefer the short version.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
d6b0f8d4c5 Use functions, do not re-implement them
We have the functions reload and redirect which modify window.location.
Use them and do not directly modify window.location so that we have
fewer places of direct window.location modifications and it is easier to
refactor them.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
a8cd4ff12c 1, not 2 names for 1 and the same abstraction
window.location calls it reload so util/helpers shouldn't call it
redirect because UI/util/UIUtil has it is own redirect which is the
assign of window.location.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
8509efc8af Make the Web app aware of its context root 2017-02-07 08:27:23 -06:00
Saúl Ibarra Corretgé
23a0053dad doc: add section about embedding to the README 2017-02-06 17:41:19 -06:00
Saúl Ibarra Corretgé
5849980092 external_api: fix jsdoc 2017-02-06 17:41:19 -06:00
Saúl Ibarra Corretgé
e81fc2b254 doc: fix external API documentation
- use proper punctuation
- fix markdown syntax
- always use syntax highlighting
- document missing commands
- miscellaneous grammar fixes
2017-02-06 17:41:19 -06:00
Lyubomir Marinov
2ad869a036 Comply w/ coding style
- Use 1 name for 1 abstraction. Instead of useFullScreen and enabled use
  fullScreen.
- Comments are correct English sentences so no double spaces between
  senteces, no capitalization of the work On midsentence.
- Write as little source code as possible if readability is preserved.
- Utilize Facebook's Flow.
- The name of a private function must start with _ and the jsdoc should
  state that the function is private.
2017-02-06 15:32:03 -06:00
Saúl Ibarra Corretgé
7a8c84e990 [RN] Implement full screen mode while in a conference
The implementation varies across platforms, with the same goal: allow the app to
use the entire screen real state while in a conference.

On Android we use immersive mode, which  will hide the status and navigation bars.

https://developer.android.com/training/system-ui/immersive.html

On iOS the status bar is hidden, with a slide effect.
2017-02-06 13:51:17 -06:00
Aaron van Meerten
a5b706a99e Added a prosody plugin for making all users into muc owners in prosody
Included a patch to prosody-trunk which allows owners to kick each other
2017-02-03 11:41:08 -06:00
25 changed files with 475 additions and 201 deletions

View File

@@ -1,4 +1,5 @@
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
You can also try it out yourself at https://meet.jit.si .
@@ -34,7 +35,7 @@ To build the Jitsi Meet application, just type
make
```
## Working with the library sources(lib-jitsi-meet).
## Working with the library sources (lib-jitsi-meet)
By default the library is build from its git repository sources. The default dependency path in package.json is :
```json
@@ -81,6 +82,10 @@ npm unlink lib-jitsi-meet
npm install
```
## Embedding in external applications
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
## Mobile app
Jitsi Meet is also available as a React Native application for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).

View File

@@ -139,6 +139,7 @@ if (project.hasProperty('JITSI_SIGNING')
}
dependencies {
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')

View File

@@ -30,6 +30,7 @@ public class MainApplication extends Application implements ReactApplication {
new com.facebook.react.shell.MainReactPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage()
);
}

View File

@@ -1,6 +1,8 @@
rootProject.name = 'jitsi-meet-react'
include ':app'
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-vector-icons'

View File

@@ -10,7 +10,7 @@ import Recorder from './modules/recorder/Recorder';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import {reportError} from './modules/util/helpers';
import { reload, reportError } from './modules/util/helpers';
import UIEvents from './service/UI/UIEvents';
import UIUtil from './modules/UI/util/UIUtil';
@@ -203,10 +203,8 @@ function maybeRedirectToWelcomePage(options) {
// save whether current user is guest or not, before navigating
// to close page
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
if (options.feedbackSubmitted)
window.location.pathname = "close.html";
else
window.location.pathname = "close2.html";
assignWindowLocationPathname(
options.feedbackSubmitted ? "close.html" : "close2.html");
return;
}
@@ -219,11 +217,42 @@ function maybeRedirectToWelcomePage(options) {
if (config.enableWelcomePage) {
setTimeout(() => {
APP.settings.setWelcomePageEnabled(true);
window.location.pathname = "/";
assignWindowLocationPathname('./');
}, 3000);
}
}
/**
* Assigns a specific pathname to window.location.pathname taking into account
* the context root of the Web app.
*
* @param {string} pathname - The pathname to assign to
* window.location.pathname. If the specified pathname is relative, the context
* root of the Web app will be prepended to the specified pathname before
* assigning it to window.location.pathname.
* @return {void}
*/
function assignWindowLocationPathname(pathname) {
const windowLocation = window.location;
if (!pathname.startsWith('/')) {
// XXX To support a deployment in a sub-directory, assume that the room
// (name) is the last non-directory component of the path (name).
let contextRoot = windowLocation.pathname;
contextRoot
= contextRoot.substring(0, contextRoot.lastIndexOf('/') + 1);
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
pathname.startsWith('./') && (pathname = pathname.substring(2));
pathname = contextRoot + pathname;
}
windowLocation.pathname = pathname;
}
/**
* Create local tracks of specified types.
* @param {Object} options
@@ -323,7 +352,7 @@ class ConferenceConnector {
case ConferenceErrors.NOT_ALLOWED_ERROR:
{
// let's show some auth not allowed page
window.location.pathname = "authError.html";
assignWindowLocationPathname('authError.html');
}
break;
@@ -388,7 +417,7 @@ class ConferenceConnector {
APP.UI.notifyMaxUsersLimitReached();
break;
case ConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
window.location.reload();
reload();
break;
default:
this._handleConferenceFailed(err, ...params);
@@ -1445,7 +1474,7 @@ export default {
APP.UI.addListener(UIEvents.LOGOUT, () => {
AuthHandler.logout(room).then(url => {
if (url) {
window.location.href = url;
UIUtil.redirect(url);
} else {
this.hangup(true);
}

View File

@@ -1,17 +1,17 @@
Jitsi Meet API
============
# Jitsi Meet API
You can use Jitsi Meet API to embed Jitsi Meet in to your application.
You can use the Jitsi Meet API to embed Jitsi Meet in to your application.
Installation
==========
## Installation
To embed Jitsi Meet in your application you need to add the Jitsi Meet API library:
To embed Jitsi Meet in your application you need to add Jitsi Meet API library
```javascript
<script src="https://meet.jit.si/external_api.js"></script>
```
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object:
```javascript
<script>
var domain = "meet.jit.si";
@@ -21,169 +21,174 @@ The next step for embedding Jitsi Meet is to create the Jitsi Meet API object
var api = new JitsiMeetExternalAPI(domain, room, width, height);
</script>
```
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.
You can use the above lines to indicate where exactly you want the Jitsi Meet conference to be placed in your HTML code,
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);
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:
If you don't specify the room the user will enter in new conference with a 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:
```javascript
var configOverwrite = {enableSimulcast: false};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
var configOverwrite = {disableSimulcast: true};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
```
Controlling embedded Jitsi Meet Conference
=========
## Controlling the embedded Jitsi Meet Conference
You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object.
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
You can send command to Jitsi Meet conference using ```executeCommand```.
```
```javascript
api.executeCommand(command, ...arguments)
```
The ```command``` parameter is String object with the name of the command.
Currently we support the following commands:
The `command` parameter is String object with the name of the command. The following commands are currently supported:
* **displayName** - sets the display name of the local participant. This command requires one argument -
the new display name to be set
```
* **displayName** - Sets the display name of the local participant. This command requires one argument - the new display name to be set.
```javascript
api.executeCommand('displayName', 'New Nickname');
```
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
```
* **toggleAudio** - Mutes / unmutes the audio for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleAudio')
```
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
```
* **toggleVideo** - Mutes / unmutes the video for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleVideo')
```
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
```
api.executeCommand('filmStrip')
```
* **toggleChat** - hides / shows the chat. No arguments are required.
* **toggleFilmStrip** - Hides / shows the film strip. No arguments are required.
```javascript
api.executeCommand('toggleFilmStrip')
```
* **toggleChat** - Hides / shows the chat. No arguments are required.
```javascript
api.executeCommand('toggleChat')
```
* **toggleContactList** - hides / shows the contact list. No arguments are required.
```
* **toggleContactList** - Hides / shows the contact list. No arguments are required.
```javascript
api.executeCommand('toggleContactList')
```
* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required.
```
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
```javascript
api.executeCommand('toggleShareScreen')
```
* **hangup** - Hangups the call. No arguments are required.
```
```javascript
api.executeCommand('hangup')
```
* **email** - Hangups the call. No arguments are required.
```
* **email** - Changes the local email address. This command requires one argument - the new email address to be set.
```javascript
api.executeCommand('email', 'example@example.com')
```
* **avatarUrl** - Hangups the call. No arguments are required.
```
api.executeCommand('avatarUrl', 'avatarUrl')
* **avatarUrl** - Changes the local avatar URL. This command requires one argument - the new avatar URL to be set.
```javascript
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647')
```
You can also execute multiple commands using the method ```executeCommands```.
```
You can also execute multiple commands using the `executeCommands` method:
```javascript
api.executeCommands(commands)
```
The ```commands``` parameter is object with keys the names of the commands and values the arguments for the
commands.
```
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands asvalues:
```javascript
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method.
```
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
```javascript
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:
The `event` parameter is a String object with the name of the event.
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
* **incomingMessage** - event notifications about incoming
messages. The listener will receive object with the following structure:
```
The following events are currently supported:
* **incomingMessage** - Event notifications about incoming
messages. The listener will receive an object with the following structure:
```javascript
{
"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
"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:
```
* **outgoingMessage** - Event notifications about outgoing
messages. The listener will receive an object with the following structure:
```javascript
{
"message": txt//the text of the message
"message": txt // the text of the message
}
```
* **displayNameChanged** - event notifications about display name
change. The listener will receive object with the following structure:
```
changes. The listener will receive an object with the following structure:
```javascript
{
jid: jid,//the JID of the participant that changed his display name
displayname: displayName //the new display name
"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:
```
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
```javascript
{
jid: jid //the jid of the participant
"jid": jid // the JID of the participant
}
```
* **participantLeft** - event notifications about participant that left room.
The listener will receive object with the following structure:
```
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
```javascript
{
jid: jid //the jid of the participant
"jid": jid // the JID of the participant
}
```
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference.
The listener will receive object with the following structure:
```
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
```javascript
{
roomName: room //the room name of the conference
"roomName": room // the room name of the conference
}
```
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference.
The listener will receive object with the following structure:
```
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
```javascript
{
roomName: room //the room name of the conference
"roomName": room // the room name of the conference
}
```
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
You can also add multiple event listeners by using ```addEventListeners```.
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.
have the names of the events as keys and the listeners of the events as values.
```
```javascript
function incomingMessageListener(object)
{
...
// ...
}
function outgoingMessageListener(object)
{
...
// ...
}
api.addEventListeners({
@@ -191,25 +196,28 @@ api.addEventListeners({
outgoingMessage: outgoingMessageListener})
```
If you want to remove a listener you can use ```removeEventListener``` method with argument the name of the event.
```
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
```javascript
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use ```removeEventListeners``` method with argument
array with the names of the events.
```
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
```javascript
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
You can get the number of participants in the conference with the following code:
```
You can get the number of participants in the conference with the following API function:
```javascript
var numberOfParticipants = api.getNumberOfParticipants();
```
You can remove the embedded Jitsi Meet Conference with the following code:
```
You can remove the embedded Jitsi Meet Conference with the following API function:
```javascript
api.dispose()
```
It is a good practice to remove the conference before the page is unloaded.
NOTE: It's a good practice to remove the conference before the page is unloaded.
[config.js]: https://github.com/jitsi/jitsi-meet/blob/master/config.js
[interface_config.js]: https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js

View File

@@ -88,8 +88,10 @@ function changeParticipantNumber(APIInstance, number) {
* @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 configOverwrite object containing configuration options defined in
* config.js to be overridden.
* @param interfaceConfigOverwrite object containing configuration options
* defined in interface_config.js to be overridden.
* @param noSsl if the value is true https won't be used
* @constructor
*/

View File

@@ -54,7 +54,7 @@ const IndicatorFontSizes = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth: function () {
getAvailableVideoWidth() {
return window.innerWidth;
},
@@ -70,7 +70,7 @@ const IndicatorFontSizes = {
*
* @param el the element
*/
getTextWidth: function (el) {
getTextWidth(el) {
return (el.clientWidth + 1);
},
@@ -79,7 +79,7 @@ const IndicatorFontSizes = {
*
* @param el the element
*/
getTextHeight: function (el) {
getTextHeight(el) {
return (el.clientHeight + 1);
},
@@ -88,14 +88,14 @@ const IndicatorFontSizes = {
*
* @param id the identifier of the audio element.
*/
playSoundNotification: function (id) {
playSoundNotification(id) {
document.getElementById(id).play();
},
/**
* Escapes the given text.
*/
escapeHtml: function (unsafeText) {
escapeHtml(unsafeText) {
return $('<div/>').text(unsafeText).html();
},
@@ -105,11 +105,11 @@ const IndicatorFontSizes = {
* @param {string} safe string which contains escaped html
* @returns {string} unescaped html string.
*/
unescapeHtml: function (safe) {
unescapeHtml(safe) {
return $('<div />').html(safe).text();
},
imageToGrayScale: function (canvas) {
imageToGrayScale(canvas) {
var context = canvas.getContext('2d');
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data;
@@ -156,7 +156,7 @@ const IndicatorFontSizes = {
* @param key the tooltip data-i18n key
* @param position the position of the tooltip in relation to the element
*/
setTooltip: function (element, key, position) {
setTooltip(element, key, position) {
if (element !== null) {
element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
element.setAttribute('data-i18n', '[content]' + key);
@@ -170,7 +170,7 @@ const IndicatorFontSizes = {
*
* @param element the element to remove the tooltip from
*/
removeTooltip: function (element) {
removeTooltip(element) {
element.removeAttribute('data-tooltip', '');
element.removeAttribute('data-i18n','');
element.removeAttribute('content','');
@@ -183,7 +183,7 @@ const IndicatorFontSizes = {
* @returns {string|*}
* @private
*/
_getTooltipText: function (element) {
_getTooltipText(element) {
let title = element.getAttribute('content');
let shortcut = element.getAttribute('shortcut');
if(shortcut) {
@@ -198,7 +198,7 @@ const IndicatorFontSizes = {
* @param container the container to which new child element will be added
* @param newChild the new element that will be inserted into the container
*/
prependChild: function (container, newChild) {
prependChild(container, newChild) {
var firstChild = container.childNodes[0];
if (firstChild) {
container.insertBefore(newChild, firstChild);
@@ -214,7 +214,7 @@ const IndicatorFontSizes = {
* @returns {boolean} {true} to indicate that the given toolbar button
* is enabled, {false} - otherwise
*/
isButtonEnabled: function (name) {
isButtonEnabled(name) {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1
|| interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) !== -1;
},
@@ -226,7 +226,7 @@ const IndicatorFontSizes = {
* @returns {boolean} {true} to indicate that the given setting section
* is enabled, {false} - otherwise
*/
isSettingEnabled: function (name) {
isSettingEnabled(name) {
return interfaceConfig.SETTINGS_SECTIONS.indexOf(name) !== -1;
},
@@ -235,7 +235,7 @@ const IndicatorFontSizes = {
*
* @returns {boolean}
*/
isAuthenticationEnabled: function() {
isAuthenticationEnabled() {
return interfaceConfig.AUTHENTICATION_ENABLE;
},
@@ -303,7 +303,7 @@ const IndicatorFontSizes = {
}
},
hideDisabledButtons: function (mappings) {
hideDisabledButtons(mappings) {
var selector = Object.keys(mappings)
.map(function (buttonName) {
return UIUtil.isButtonEnabled(buttonName)
@@ -313,7 +313,7 @@ const IndicatorFontSizes = {
$(selector).hide();
},
redirect (url) {
redirect(url) {
window.location.href = url;
},
@@ -368,7 +368,7 @@ const IndicatorFontSizes = {
* @param {Object} attrs object with properties
* @returns {String} string of html element attributes
*/
attrsToString: function (attrs) {
attrsToString(attrs) {
return Object.keys(attrs).map(
key => ` ${key}="${attrs[key]}"`
).join(' ');

View File

@@ -23,6 +23,8 @@ export default class LargeVideoManager {
this.eventEmitter = emitter;
this.state = VIDEO_CONTAINER_TYPE;
// FIXME: We are passing resizeContainer as parameter which is calling
// Container.resize. Probably there's better way to implement this.
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE), emitter);
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);

View File

@@ -164,12 +164,20 @@ export class VideoContainer extends LargeContainer {
return getStreamOwnerId(this.stream);
}
constructor (onPlay, emitter) {
/**
* Creates new VideoContainer instance.
* @param resizeContainer {Function} function that takes care of the size
* of the video container.
* @param emitter {EventEmitter} the event emitter that will be used by
* this instance.
*/
constructor (resizeContainer, emitter) {
super();
this.stream = null;
this.videoType = null;
this.localFlipX = true;
this.emitter = emitter;
this.resizeContainer = resizeContainer;
this.isVisible = false;
@@ -199,8 +207,8 @@ export class VideoContainer extends LargeContainer {
this.avatarHeight = $("#dominantSpeakerAvatar").height();
var onPlayCallback = function (event) {
if (typeof onPlay === 'function') {
onPlay(event);
if (typeof resizeContainer === 'function') {
resizeContainer(event);
}
this.wasVideoRendered = true;
}.bind(this);
@@ -336,8 +344,14 @@ export class VideoContainer extends LargeContainer {
* @param {string} videoType video type
*/
setStream (stream, videoType) {
if (this.stream === stream) {
// Handles the use case for the remote participants when the
// videoType is received with delay after turning on/off the
// desktop sharing.
if(this.videoType !== videoType) {
this.videoType = videoType;
this.resizeContainer();
}
return;
} else {
// The stream has changed, so the image will be lost on detach

View File

@@ -1,6 +1,6 @@
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { redirect } from '../util/helpers';
import { replace } from '../util/helpers';
/**
* The modules stores information about the URL used to start the conference and
@@ -68,6 +68,6 @@ export default class ConferenceUrl {
*/
reload() {
logger.info("Reloading the conference using URL: " + this.originalURL);
redirect(this.originalURL);
replace(this.originalURL);
}
}

View File

@@ -2,12 +2,13 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Create deferred object.
*
* @returns {{promise, resolve, reject}}
*/
export function createDeferred () {
let deferred = {};
export function createDeferred() {
const deferred = {};
deferred.promise = new Promise(function (resolve, reject) {
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
@@ -18,63 +19,58 @@ export function createDeferred () {
/**
* Reload page.
*/
export function reload () {
export function reload() {
window.location.reload();
}
/**
* Redirects to new URL.
* Redirects to a specific new URL by replacing the current location (in the
* history).
*
* @param {string} url the URL pointing to the location where the user should
* be redirected to.
*/
export function redirect (url) {
export function replace(url) {
window.location.replace(url);
}
/**
* Prints the error and reports it to the global error handler.
*
* @param e {Error} the error
* @param msg {string} [optional] the message printed in addition to the error
*/
export function reportError (e, msg = "") {
export function reportError(e, msg = "") {
logger.error(msg, e);
if(window.onerror)
window.onerror(msg, null, null,
null, e);
window.onerror && window.onerror(msg, null, null, null, e);
}
/**
* Creates a debounced function that delays invoking func until after wait
* milliseconds have elapsed since the last time the debounced
* function was invoked
* milliseconds have elapsed since the last time the debounced function was
* invoked.
*
* @param fn
* @param wait
* @param options
* @returns {function(...[*])}
*/
export function debounce(fn, wait = 0, options = {}) {
let leading = options.leading || false;
let trailing = true;
let isCalled = false;
if (typeof options.trailing !== 'undefined') {
trailing = options.trailing;
}
const leading = options.leading || false;
const trailing
= (typeof options.trailing === 'undefined') || options.trailing;
let called = false;
return (...args) => {
if(!isCalled) {
if (leading) {
fn(...args);
}
if (!called) {
leading && fn(...args);
setTimeout(() => {
isCalled = false;
if (trailing) {
fn(...args);
}
called = false;
trailing && fn(...args);
}, wait);
isCalled = true;
called = true;
}
};
}

View File

@@ -35,6 +35,7 @@
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.41.2",
"react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2",
"react-native-prompt": "^1.0.0",
"react-native-vector-icons": "^4.0.0",

View File

@@ -0,0 +1,13 @@
-- Copyright (c) 2015 &yet <https://andyet.com>
-- https://github.com/otalk/mod_muc_allowners/blob/9a86266a25ed32ade150742cc79f5a1669765a8f/mod_muc_allowners.lua
--
-- Used under the terms of the MIT License
-- https://github.com/otalk/mod_muc_allowners/blob/9a86266a25ed32ade150742cc79f5a1669765a8f/LICENSE
local muc_service = module:depends("muc");
local room_mt = muc_service.room_mt;
room_mt.get_affiliation = function (room, jid)
return "owner";
end

View File

@@ -0,0 +1,21 @@
--- muc.lib.lua 2016-10-26 18:26:53.432377291 +0000
+++ muc.lib.lua 2016-10-26 18:41:40.754426072 +0000
@@ -1256,15 +1256,16 @@
if actor == true then
actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
else
+ local actor_affiliation = self:get_affiliation(actor);
+
-- Can't do anything to other owners or admins
local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
- if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
+ if (occupant_affiliation == "owner" and actor_affiliation ~= "owner") or (occupant_affiliation == "admin" and actor_affiliation ~= "admin" and actor_affiliation ~= "owner") then
return nil, "cancel", "not-allowed";
end
-- If you are trying to give or take moderator role you need to be an owner or admin
if occupant.role == "moderator" or role == "moderator" then
- local actor_affiliation = self:get_affiliation(actor);
if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
return nil, "cancel", "not-allowed";
end

View File

@@ -38,7 +38,7 @@ export class AbstractApp extends Component {
}
/**
* Initializes a new App instance.
* Initializes a new AbstractApp instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
@@ -334,13 +334,7 @@ export class AbstractApp extends Component {
// (2) A replace function would be provided to the Route in case it
// chose to redirect to another path.
this._onRouteEnter(route, nextState, pathname => {
// FIXME In order to minimize the modifications related to the
// removal of react-router, the Web implementation is provided
// bellow because the replace function is used on Web only at the
// time of this writing. Provide a platform-agnostic implementation.
// It should likely find the best Route matching the specified
// pathname and navigate to it.
window.location.pathname = pathname;
this._openURL(pathname);
// Do not proceed with the route because it chose to redirect to
// another path.

View File

@@ -4,6 +4,7 @@ import { Linking } from 'react-native';
import { Platform } from '../../base/react';
import '../../audio-mode';
import '../../full-screen';
import '../../wake-lock';
import { AbstractApp } from './AbstractApp';

View File

@@ -14,6 +14,27 @@ export class App extends AbstractApp {
*/
static propTypes = AbstractApp.propTypes
/**
* Initializes a new App instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
*/
constructor(props) {
super(props);
this.state = {
...this.state,
/**
* The context root of window.location i.e. this Web App.
*
* @type {string}
*/
windowLocationContextRoot: this._getWindowLocationContextRoot()
};
}
/**
* Inits the app before component will mount.
*
@@ -35,6 +56,22 @@ export class App extends AbstractApp {
return window.location;
}
/**
* Gets the context root of this Web App from window.location.
*
* @private
* @returns {string} The context root of window.location i.e. this Web App.
*/
_getWindowLocationContextRoot() {
const pathname = this._getWindowLocation().pathname;
const contextRootEndIndex = pathname.lastIndexOf('/');
return (
contextRootEndIndex === -1
? '/'
: pathname.substring(0, contextRootEndIndex + 1));
}
/**
* Navigates to a specific Route (via platform-specific means).
*
@@ -53,6 +90,7 @@ export class App extends AbstractApp {
= path.replace(
/:room/g,
store.getState()['features/base/conference'].room);
path = this._routePath2WindowLocationPathname(path);
// Navigate to the specified Route.
const windowLocation = this._getWindowLocation();
@@ -69,4 +107,22 @@ export class App extends AbstractApp {
windowLocation.pathname = path;
}
}
/**
* Converts a specific Route path to a window.location.pathname.
*
* @param {string} path - A Route path to be converted to/represeted as a
* window.location.pathname.
* @private
* @returns {string} A window.location.pathname-compatible representation of
* the specified Route path.
*/
_routePath2WindowLocationPathname(path) {
let pathname = this.state.windowLocationContextRoot;
pathname.endsWith('/') || (pathname += '/');
pathname += path.startsWith('/') ? path.substring(1) : path;
return pathname;
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
import { NativeModules } from 'react-native';
import { APP_WILL_MOUNT } from '../app';
@@ -49,7 +51,9 @@ MiddlewareRegistry.register(store => next => action => {
if (mode !== null) {
AudioMode.setMode(mode)
.catch(err =>
console.error(`Failed to set audio mode ${mode}: ${err}`));
console.error(
`Failed to set audio mode ${String(mode)}: `
+ `${err}`));
}
}

View File

@@ -0,0 +1 @@
import './middleware';

View File

@@ -0,0 +1,76 @@
/* @flow */
import { StatusBar } from 'react-native';
import { Immersive } from 'react-native-immersive';
import {
CONFERENCE_FAILED,
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN
} from '../base/conference';
import { Platform } from '../base/react';
import { MiddlewareRegistry } from '../base/redux';
/**
* Middleware that captures conference actions and activates or deactivates the
* full screen mode. On iOS it hides the status bar, and on Android it uses the
* immersive mode:
* https://developer.android.com/training/system-ui/immersive.html
* In immersive mode the status and navigation bars are hidden and thus the
* entire screen will be covered by our application.
*
* @param {Store} store - Redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
let fullScreen;
switch (action.type) {
case CONFERENCE_WILL_JOIN: {
const conference = store.getState()['features/base/conference'];
fullScreen = !conference.audioOnly;
break;
}
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
fullScreen = false;
break;
default:
fullScreen = null;
break;
}
if (fullScreen !== null) {
_setFullScreen(fullScreen)
.catch(err =>
console.warn(`Failed to set full screen mode: ${err}`));
}
return next(action);
});
/**
* Activates/deactivates the full screen mode. On iOS it will hide the status
* bar, and on Android it will turn immersive mode on.
*
* @param {boolean} fullScreen - True to set full screen mode, false to
* deactivate it.
* @private
* @returns {Promise}
*/
function _setFullScreen(fullScreen: boolean) {
// XXX The React Native module Immersive is only implemented on Android and
// throws on other platforms.
if (Platform.OS === 'android') {
return fullScreen ? Immersive.on() : Immersive.off();
}
// On platforms other than Android go with whatever React Native itself
// supports.
StatusBar.setHidden(fullScreen, 'slide');
return Promise.resolve();
}

View File

@@ -74,7 +74,7 @@ class UnsupportedMobileBrowser extends Component {
<div className = { `${ns}__body` }>
<img
className = { `${ns}__logo` }
src = '/images/logo-blue.svg' />
src = 'images/logo-blue.svg' />
<p className = { `${ns}__text` }>
You need <strong>Jitsi Meet</strong> to join a
conversation on your mobile
@@ -97,9 +97,40 @@ class UnsupportedMobileBrowser extends Component {
</button>
</a>
</div>
{
this._renderStyle()
}
</div>
);
}
/**
* Renders an HTML style element with CSS specific to
* this UnsupportedMobileBrowser.
*
* @private
* @returns {ReactElement}
*/
_renderStyle() {
// Temasys provide lib-jitsi-meet/modules/RTC/adapter.screenshare.js
// which detects whether the browser supports WebRTC. If the browser
// does not support WebRTC, it displays an alert in the form of a yellow
// bar at the top of the page. The alert notifies the user that the
// browser does not support WebRTC and, if Temasys provide a plugin for
// the browser, the alert contains a button to initiate installing the
// browser. When Temasys do not provide a plugin for the browser, we do
// not want the alert on the unsupported-browser page because the
// notification about the lack of WebRTC support is the whole point of
// the unsupported-browser page.
return (
<style type = 'text/css'>
{
'iframe[name="adapterjs-alert"] { display: none; }'
}
</style>
);
}
}
/**

View File

@@ -76,15 +76,18 @@ class WelcomePage extends AbstractWelcomePage {
}
/**
* Returns the domain name.
* Returns the URL of this WelcomePage for display purposes. For
* historic/legacy reasons, the return value is referred to as domain.
*
* @private
* @returns {string} Domain name.
* @returns {string} The URL of this WelcomePage for display purposes.
*/
_getDomain() {
const windowLocation = window.location;
// As the returned URL is for display purposes, do not return the
// userinfo, query and fragment URI parts.
const wl = window.location;
return `${windowLocation.protocol}//${windowLocation.host}/`;
return `${wl.protocol}//${wl.host}${wl.pathname}`;
}
/**

View File

@@ -10,26 +10,22 @@
* Builds and returns the room name.
*/
function getRoomName () { // eslint-disable-line no-unused-vars
var getroomnode = config.getroomnode;
var path = window.location.pathname;
var roomName;
// determinde the room node from the url
// TODO: just the roomnode or the whole bare jid?
if (config.getroomnode && typeof config.getroomnode === 'function') {
// Determine the room node from the URL.
if (getroomnode && typeof getroomnode === 'function') {
// custom function might be responsible for doing the pushstate
roomName = config.getroomnode(path);
roomName = getroomnode.call(config, path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
* It currently assumes deployment at root, with a rewrite like the
* following one (for nginx):
location ~ ^/([a-zA-Z0-9]+)$ {
rewrite ^/(.*)$ / break;
}
*/
if (path.length > 1) {
roomName = path.substr(1).toLowerCase();
}
// Fall back to the default strategy of making assumptions about how the
// URL maps to the room (name). It currently assumes a deployment in
// which the last non-directory component of the path (name) is the
// room.
roomName
= path.substring(path.lastIndexOf('/') + 1).toLowerCase()
|| undefined;
}
return roomName;

View File

@@ -33,6 +33,23 @@ if (minimize) {
NODE_ENV: JSON.stringify('production')
}
}));
// While webpack will automatically insert UglifyJsPlugin when minimize is
// true, the defaults of UglifyJsPlugin in webpack 1 and webpack 2 are
// different. Explicitly state what we want even if we want defaults in
// order to prepare for webpack 2.
plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: {
// It is nice to see warnings from UglifyJsPlugin that something is
// unused and, consequently, is removed. The default is false in
// webpack 2.
warnings: true
},
// Use the source map to map error message locations to modules. The
// default is false in webpack 2.
sourceMap: true
}));
}
// The base Webpack configuration to bundle the JavaScript artifacts of
@@ -56,7 +73,7 @@ var config = {
// as well.
exclude: node_modules,
loader: 'babel',
loader: 'babel-loader',
query: {
// XXX The require.resolve bellow solves failures to locate the
// presets when lib-jitsi-meet, for example, is npm linked in
@@ -74,20 +91,20 @@ var config = {
// to be available in such a form by multiple jitsi-meet
// dependencies including AUI, lib-jitsi-meet.
loader: 'expose?$!expose?jQuery',
loader: 'expose-loader?$!expose-loader?jQuery',
test: /\/node_modules\/jquery\/.*\.js$/
}, {
// Disable AMD for the Strophe.js library or its imports will fail
// at runtime.
loader: 'imports?define=>false&this=>window',
loader: 'imports-loader?define=>false&this=>window',
test: strophe
}, {
// Allow CSS to be imported into JavaScript.
loaders: [
'style',
'css'
'style-loader',
'css-loader'
],
test: /\.css$/
}, {
@@ -95,7 +112,7 @@ var config = {
// by CSS into the output path.
include: aui_css,
loader: 'file',
loader: 'file-loader',
query: {
context: aui_css,
name: '[path][name].[ext]'
@@ -104,7 +121,7 @@ var config = {
}, {
// Enable the import of JSON files.
loader: 'json',
loader: 'json-loader',
exclude: node_modules,
test: /\.json$/
} ],