Compare commits

...

31 Commits

Author SHA1 Message Date
Boris Grozev
b22f1965aa doc: Remove obsolete reference to browserify. 2017-01-25 15:48:32 -06:00
Samuel Liard
df6f920b44 Force jquery-ui version to fix pb with beta version we can get with npm
Use Maven in place of ant
Add --host parameter for jicofo
Add build for jitsi Meet

Revert "Force jquery-ui version to fix pb with beta version we can get with npm"

This reverts commit 35a8641e62.
2017-01-25 15:47:23 -06:00
Дамян Минков
f13b2462c8 Merge pull request #1257 from saghul/ds-options-cleanup
cleanup: Remove default Chrome extension ID
2017-01-23 17:04:59 -06:00
Paweł Domas
c0e80c14f8 Merge pull request #1192 from jitsi/remotecontrol
Implement remote control support
2017-01-23 17:00:11 -06:00
hristoterezov
2b1176df53 style(remotecontrol): getRequestedParticipant method comments 2017-01-23 16:06:51 -06:00
hristoterezov
05bfbf5620 fix(remotecontrol): Execute Reciever.enable only when the value is changed 2017-01-23 15:29:25 -06:00
hristoterezov
4af706bd83 style(keycode): Comment 2017-01-23 15:29:25 -06:00
hristoterezov
b62e4d5ee9 fix(remotecontrol): Log level 2017-01-23 15:29:25 -06:00
hristoterezov
bd98d661d3 ref(remotecontrol): Moves hangup logic to remote control module 2017-01-23 15:29:25 -06:00
hristoterezov
1f7c5529e9 fix(remotecontrol): Pin the controlled participant only on remote control permissions granted 2017-01-23 15:29:25 -06:00
hristoterezov
0453346cf4 ref(remotecontrol): Pass the largeVideoWrapper as parameter to remote control module 2017-01-23 15:29:25 -06:00
hristoterezov
15090243d0 fix(API): JS errors caused by remote control merge 2017-01-23 15:29:25 -06:00
hristoterezov
b22e3ee253 style(remotecontrol): Fix JSDoc for RemoteControlEvent 2017-01-23 15:29:25 -06:00
hristoterezov
e693554961 fix(remotecontrol): Logging 2017-01-23 15:29:25 -06:00
hristoterezov
0efca9a9a8 fix(remotecontrol): Fixing issues after peer review. 2017-01-23 15:29:25 -06:00
hristoterezov
5d22061c0a fix(remotecontrol): Handle on-stage participant changes 2017-01-23 15:29:25 -06:00
hristoterezov
84be7fd739 fix(remotecontrol): import of remote control 2017-01-23 15:29:25 -06:00
hristoterezov
5d269ad0aa fix(remotecontrol): Disable the keyboard shortcuts during remote control 2017-01-23 15:29:25 -06:00
hristoterezov
a4d5c41b3a feat(remotecontrol): UI for requesting permissions 2017-01-23 15:29:25 -06:00
hristoterezov
846fb9abb0 feat(remotecontrol): Implement requesting remote control permissions 2017-01-23 15:29:25 -06:00
hristoterezov
0f33e59e4d feat(remotecontrol): announce remotecontrol support 2017-01-23 15:29:25 -06:00
hristoterezov
896650d005 feat(remotecontrol): Implement basic remote control support 2017-01-23 15:29:25 -06:00
Saúl Ibarra Corretgé
6d6b5a7917 cleanup: Remove default Chrome extension ID
It makes for a bad first-time experience for users, since the desktop sharing
button will be visible, but it will never work.

Also get rid of the now deprecated `desktopSharingChromeMethod` option.
2017-01-23 15:21:28 -06:00
Любомир Маринов
db5010be9d Merge pull request #1261 from jitsi/clean-css-module-update
Stick to clean-css version 3.x.
2017-01-23 15:18:18 -06:00
damencho
be8860080c Fixes build.
Starting version 4.x clean-css is split into two packages and we should depend on clean-css-cli for versions 4 and above. Tested it and we have currently some problem with it like fonts and images got referenced under css folder. So sticking version to 3.x for now.
2017-01-23 14:35:26 -06:00
Lyubomir Marinov
42f124b0f6 Fix typo 2017-01-23 13:01:12 -06:00
Lyubomir Marinov
7249ababb7 Provide microphone and camera usage descriptions
iTunes Connect/TestFlight requires these now and is not satisfied with
empty values anymore.
2017-01-23 12:11:48 -06:00
Lyubomir Marinov
079be92468 Revert "[RN][iOS] Make universal builds"
This reverts commit b96ca538e4. While iPad
support is nice to have, we are not ready for a publish in App Store.
During the upload to iTunes Connect, a verification of the .ipa fails
because:

- app icons for iPad are missing:
  - 76x76 pixels in .png format for iOS versions >= 7.0,
  - 167x167 pixels in .png format for iOS versions supporting iPad Pro,
  - 152x152 pixels in .png format for iOS versions >= 7.0,
- orientations required for iPad Mutitasking are missing:
  - UIInterfaceOrientationPortraitUpsideDown.
2017-01-23 11:26:15 -06:00
Lyubomir Marinov
dda2a2feba Automatic Xcode project file fixes 2017-01-22 16:02:57 -06:00
Lyubomir Marinov
ae726cfdc0 react-native 0.40.0 2017-01-22 12:32:08 -06:00
Saúl Ibarra Corretgé
1a04e5a9af [RN] Use a translucent status bar on Android 2017-01-21 15:42:13 -06:00
32 changed files with 1468 additions and 106 deletions

1
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
*.bundle.js -text -diff
*.pbxproj -text
lib-jitsi-meet.js -text -diff

18
.gitignore vendored
View File

@@ -36,12 +36,13 @@ DerivedData
*.xcuserstate
project.xcworkspace
# Android/IJ
# Android/IntelliJ
#
*.iml
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
@@ -53,4 +54,15 @@ npm-debug.log
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use
# fastlane to re-generate the screenshots whenever they are needed. For more
# information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
#
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots

4
ConferenceEvents.js Normal file
View File

@@ -0,0 +1,4 @@
/**
* Notifies interested parties that hangup procedure will start.
*/
export const BEFORE_HANGUP = "conference.before_hangup";

View File

@@ -144,7 +144,7 @@ dependencies {
compile project(':react-native-webrtc')
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.react:react-native:+'
compile 'com.facebook.react:react-native:+' // From node_modules
}
apply from: '../../node_modules/react-native-vector-icons/fonts.gradle'
@@ -152,6 +152,6 @@ apply from: '../../node_modules/react-native-vector-icons/fonts.gradle'
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
from configurations.compile
into 'libs'
}

View File

@@ -5,6 +5,7 @@ import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
@@ -48,6 +49,8 @@ public class MainApplication extends Application implements ReactApplication {
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!getReactNativeHost()
.getReactInstanceManager()
.getDevSupportManager()

View File

@@ -3,6 +3,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>

4
app.js
View File

@@ -22,6 +22,7 @@ import conference from './conference';
import API from './modules/API/API';
import translation from "./modules/translation/translation";
import remoteControl from "./modules/remotecontrol/RemoteControl";
const APP = {
// Used by do_external_connect.js if we receive the attach data after
@@ -59,7 +60,8 @@ const APP = {
*/
ConferenceUrl : null,
connection: null,
API
API,
remoteControl
};
// TODO The execution of the mobile app starts from react/index.native.js.

View File

@@ -14,9 +14,12 @@ import {reportError} from './modules/util/helpers';
import UIEvents from './service/UI/UIEvents';
import UIUtil from './modules/UI/util/UIUtil';
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -28,6 +31,8 @@ const TrackErrors = JitsiMeetJS.errors.track;
const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
const eventEmitter = new EventEmitter();
let room, connection, localAudio, localVideo;
/**
@@ -485,10 +490,11 @@ export default {
}).then(([tracks, con]) => {
logger.log('initialized with %s local tracks', tracks.length);
APP.connection = connection = con;
this._bindConnectionFailedHandler(con);
this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
APP.remoteControl.init();
this._bindConnectionFailedHandler(con);
this._createRoom(tracks);
if (UIUtil.isButtonEnabled('contacts')
&& !interfaceConfig.filmStripOnly) {
@@ -981,7 +987,7 @@ export default {
let externalInstallation = false;
if (shareScreen) {
createLocalTracks({
this.screenSharingPromise = createLocalTracks({
devices: ['desktop'],
desktopSharingExtensionExternalInstallation: {
interval: 500,
@@ -1070,7 +1076,10 @@ export default {
dialogTitleKey, dialogTxt, false);
});
} else {
createLocalTracks({ devices: ['video'] }).then(
APP.remoteControl.receiver.stop();
this.screenSharingPromise = createLocalTracks(
{ devices: ['video'] })
.then(
([stream]) => this.useVideoStream(stream)
).then(() => {
this.videoSwitchInProgress = false;
@@ -1102,6 +1111,8 @@ export default {
}
);
room.on(ConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
user => APP.UI.onUserFeaturesChanged(user));
room.on(ConferenceEvents.USER_JOINED, (id, user) => {
if (user.isHidden())
return;
@@ -1600,12 +1611,23 @@ export default {
},
/**
* Adds any room listener.
* @param eventName one of the ConferenceEvents
* @param callBack the function to be called when the event occurs
* @param {string} eventName one of the ConferenceEvents
* @param {Function} listener the function to be called when the event
* occurs
*/
addConferenceListener(eventName, callBack) {
room.on(eventName, callBack);
addConferenceListener(eventName, listener) {
room.on(eventName, listener);
},
/**
* Removes any room listener.
* @param {string} eventName one of the ConferenceEvents
* @param {Function} listener the listener to be removed.
*/
removeConferenceListener(eventName, listener) {
room.off(eventName, listener);
},
/**
* Inits list of current devices and event listener for device change.
* @private
@@ -1763,6 +1785,7 @@ export default {
* requested
*/
hangup (requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.hideRingOverLay();
let requestFeedbackPromise = requestFeedback
? APP.UI.requestFeedbackOnHangup()
@@ -1813,5 +1836,36 @@ export default {
APP.settings.setAvatarUrl(url);
APP.UI.setUserAvatarUrl(room.myUserId(), url);
sendData(commands.AVATAR_URL, url);
},
/**
* Sends a message via the data channel.
* @param {string} to the id of the endpoint that should receive the
* message. If "" - the message will be sent to all participants.
* @param {object} payload the payload of the message.
* @throws NetworkError or InvalidStateError or Error if the operation
* fails.
*/
sendEndpointMessage (to, payload) {
room.sendEndpointMessage(to, payload);
},
/**
* Adds new listener.
* @param {String} eventName the name of the event
* @param {Function} listener the listener.
*/
addListener (eventName, listener) {
eventEmitter.addListener(eventName, listener);
},
/**
* Removes listener.
* @param {String} eventName the name of the event that triggers the
* listener
* @param {Function} listener the listener.
*/
removeListener (eventName, listener) {
eventEmitter.removeListener(eventName, listener);
}
};

View File

@@ -20,10 +20,10 @@ var config = { // eslint-disable-line no-unused-vars
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
//defaultSipNumber: '', // Default SIP number
// Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
desktopSharingChromeMethod: 'ext',
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: 'diibjkoicjeejcmhdnailmkgecihlobk',
desktopSharingChromeExtId: null,
// Whether desktop sharing should be disabled on Chrome.
desktopSharingChromeDisabled: true,
// The media sources to use when using screen sharing with the Chrome
// extension.
desktopSharingChromeSources: ['screen', 'window', 'tab'],

View File

@@ -92,7 +92,7 @@
0 0 3px $videoThumbnailSelected !important;
}
.remotevideomenu {
.remotevideomenu > .icon-menu {
display: none;
}
@@ -105,7 +105,7 @@
box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered;
.remotevideomenu {
.remotevideomenu > .icon-menu {
display: inline-block;
}
}
@@ -121,4 +121,4 @@
}
}
}
}
}

View File

@@ -6,7 +6,6 @@
padding: 0;
margin: 2px 0;
bottom: 0;
width: 100px;
height: auto;
&:first-child {
@@ -66,4 +65,9 @@
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important;
}
}
.remote-control-spinner {
top: 6px;
left: 2px;
}

View File

@@ -131,9 +131,9 @@ Or autostart it by adding the line in `/etc/rc.local`:
## Install Jitsi Conference Focus (jicofo)
Install JDK and Ant if missing:
Install JDK and Maven if missing:
```
apt-get install default-jdk ant
apt-get install default-jdk maven
```
_NOTE: When installing on older Debian releases keep in mind that you need JDK >= 1.7._
@@ -145,12 +145,14 @@ git clone https://github.com/jitsi/jicofo.git
Build distribution package. Replace {os-name} with one of: 'lin', 'lin64', 'macosx', 'win', 'win64'.
```sh
cd jicofo
ant dist.{os-name}
mvn package -DskipTests -Dassembly.skipAssembly=false
```
Run jicofo:
```sh
cd dist/{os-name}'
./jicofo.sh --host=127.0.0.1 --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
=======
unzip target/jicofo-{os-name}-1.0-SNAPSHOT.zip
cd jicofo-{os-name}-1.0-SNAPSHOT'
./jicofo.sh --host=localhost --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
```
## Deploy Jitsi Meet
@@ -159,6 +161,8 @@ Checkout and configure Jitsi Meet:
cd /srv
git clone https://github.com/jitsi/jitsi-meet.git
mv jitsi-meet/ jitsi.example.com
npm install
make
```
Edit host names in `/srv/jitsi.example.com/config.js` (see also the example config file):
@@ -167,11 +171,11 @@ var config = {
hosts: {
domain: 'jitsi.example.com',
muc: 'conference.jitsi.example.com',
bridge: 'jitsi-videobridge.jitsi.example.com'
bridge: 'jitsi-videobridge.jitsi.example.com',
focus: 'focus.jitsi.example.com'
},
useNicks: false,
bosh: '//jitsi.example.com/http-bind', // FIXME: use xep-0156 for that
desktopSharing: 'false' // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
//chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
//minChromeExtVersion: '0.1' // Required version of Chrome extension
};

View File

@@ -43,11 +43,11 @@
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string></string>
<string>Participate in conferences with video.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSMicrophoneUsageDescription</key>
<string></string>
<string>Participate in conferences with audio.</string>
<key>UIAppFonts</key>
<array>
<string>jitsi.ttf</string>

View File

@@ -24,6 +24,7 @@
146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
2602576C1D0A7703001E3363 /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2602576B1D0A7703001E3363 /* jitsi.ttf */; };
3847F11906B4479A9162628F /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 821D8ABD506944B4BDBB069B /* libRNVectorIcons.a */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */; };
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B30EF2301DC0ED7C00690F45 /* WebRTC.framework */; };
@@ -113,6 +114,62 @@
remoteGlobalIDString = 5DBEB1501B18CEA900B34395;
remoteInfo = RNVectorIcons;
};
3DAD3EA41DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3C059A1DE3340900C268FA;
remoteInfo = yoga;
};
3DAD3EA61DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3C06751DE3340C00C268FA;
remoteInfo = "yoga-tvOS";
};
3DAD3EA81DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD9251DE5FBEC00167DC4;
remoteInfo = cxxreact;
};
3DAD3EAA1DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD9321DE5FBEE00167DC4;
remoteInfo = "cxxreact-tvOS";
};
3DAD3EAC1DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD90B1DE5FBD600167DC4;
remoteInfo = jschelpers;
};
3DAD3EAE1DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4;
remoteInfo = "jschelpers-tvOS";
};
5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTAnimation;
};
5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28201D9B03D100D4039D;
remoteInfo = "RCTAnimation-tvOS";
};
78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
@@ -221,6 +278,7 @@
22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
2602576B1D0A7703001E3363 /* jitsi.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = jitsi.ttf; path = ../android/app/src/main/assets/fonts/jitsi.ttf; sourceTree = "<group>"; };
5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = KCKeepAwake.xcodeproj; path = "../node_modules/react-native-keep-awake/ios/KCKeepAwake.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
821D8ABD506944B4BDBB069B /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
@@ -259,6 +317,7 @@
BF96438E1C34FBE100B0BBDF /* VideoToolbox.framework in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */,
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */,
@@ -364,7 +423,13 @@
isa = PBXGroup;
children = (
146834041AC3E56700842450 /* libReact.a */,
B3BA19EF1DC6B37B00BCD481 /* libReact-tvOS.a */,
B3BA19EF1DC6B37B00BCD481 /* libReact.a */,
3DAD3EA51DF850E9000B6D8A /* libyoga.a */,
3DAD3EA71DF850E9000B6D8A /* libyoga.a */,
3DAD3EA91DF850E9000B6D8A /* libcxxreact.a */,
3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */,
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */,
);
name = Products;
sourceTree = "<group>";
@@ -377,6 +442,15 @@
name = Products;
sourceTree = "<group>";
};
5E91572E1DD0AC6500FF2AA8 /* Products */ = {
isa = PBXGroup;
children = (
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */,
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
};
6956B374CC3C453DB7B8E82D /* Resources */ = {
isa = PBXGroup;
children = (
@@ -403,6 +477,7 @@
BF9643911C34FBF100B0BBDF /* libsqlite3.tbd */,
BF9643931C34FBF900B0BBDF /* libstdc++.tbd */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
@@ -501,7 +576,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
@@ -534,6 +609,10 @@
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
},
{
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@@ -657,6 +736,62 @@
remoteRef = 26D58A061D0B42EE00FC396B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA51DF850E9000B6D8A /* libyoga.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libyoga.a;
remoteRef = 3DAD3EA41DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA71DF850E9000B6D8A /* libyoga.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libyoga.a;
remoteRef = 3DAD3EA61DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA91DF850E9000B6D8A /* libcxxreact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libcxxreact.a;
remoteRef = 3DAD3EA81DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libcxxreact.a;
remoteRef = 3DAD3EAA1DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libjschelpers.a;
remoteRef = 3DAD3EAC1DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libjschelpers.a;
remoteRef = 3DAD3EAE1DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTAnimation.a;
remoteRef = 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTAnimation-tvOS.a";
remoteRef = 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -713,10 +848,10 @@
remoteRef = B3BA19EA1DC6B37B00BCD481 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B3BA19EF1DC6B37B00BCD481 /* libReact-tvOS.a */ = {
B3BA19EF1DC6B37B00BCD481 /* libReact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libReact-tvOS.a";
path = libReact.a;
remoteRef = B3BA19EE1DC6B37B00BCD481 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@@ -834,7 +969,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -871,7 +1005,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -888,15 +1021,19 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -936,8 +1073,10 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -945,6 +1084,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;

View File

@@ -1,11 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
BuildableName = "libReact.a"
BlueprintName = "React"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"

View File

@@ -156,8 +156,8 @@
"kick": "Kick out",
"muted": "Muted",
"domute": "Mute",
"flip": "Flip"
"flip": "Flip",
"remoteControl": "Remote control"
},
"connectionindicator":
{
@@ -316,7 +316,12 @@
"externalInstallationMsg": "You need to install our desktop sharing extension.",
"muteParticipantTitle": "Mute this participant?",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantButton": "Mute"
"muteParticipantButton": "Mute",
"remoteControlTitle": "Remote Control",
"remoteControlDeniedMessage": "__user__ rejected your remote control request!",
"remoteControlAllowedMessage": "__user__ accepted your remote control request!",
"remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",
"remoteControlStopMessage": "The remote control session ended!"
},
"email":
{

View File

@@ -55,7 +55,9 @@ function initCommands() {
APP.conference.toggleScreenSharing.bind(APP.conference),
"video-hangup": () => APP.conference.hangup(),
"email": APP.conference.changeLocalEmail,
"avatar-url": APP.conference.changeLocalAvatarUrl
"avatar-url": APP.conference.changeLocalAvatarUrl,
"remote-control-event": event =>
APP.remoteControl.onRemoteControlAPIEvent(event)
};
Object.keys(commands).forEach(function (key) {
postis.listen(key, args => commands[key](...args));
@@ -94,7 +96,13 @@ function triggerEvent (name, object) {
}
}
export default {
class API {
/**
* Constructs new instance
* @constructor
*/
constructor() { }
/**
* Initializes the APIConnector. Setups message event listeners that will
* receive information from external applications that embed Jitsi Meet.
@@ -108,6 +116,17 @@ export default {
return;
enabled = true;
if(!postis) {
this._initPostis();
}
}
/**
* initializes postis library.
* @private
*/
_initPostis() {
let postisOptions = {
window: target
};
@@ -116,7 +135,7 @@ export default {
= "jitsi_meet_external_api_" + jitsi_meet_external_api_id;
postis = postisInit(postisOptions);
initCommands();
},
}
/**
* Notify external application (if API is enabled) that message was sent.
@@ -124,7 +143,7 @@ export default {
*/
notifySendingChatMessage (body) {
triggerEvent("outgoing-message", {"message": body});
},
}
/**
* Notify external application (if API is enabled) that
@@ -143,7 +162,7 @@ export default {
"incoming-message",
{"from": id, "nick": nick, "message": body, "stamp": ts}
);
},
}
/**
* Notify external application (if API is enabled) that
@@ -152,7 +171,7 @@ export default {
*/
notifyUserJoined (id) {
triggerEvent("participant-joined", {id});
},
}
/**
* Notify external application (if API is enabled) that
@@ -161,7 +180,7 @@ export default {
*/
notifyUserLeft (id) {
triggerEvent("participant-left", {id});
},
}
/**
* Notify external application (if API is enabled) that
@@ -171,7 +190,7 @@ export default {
*/
notifyDisplayNameChanged (id, displayName) {
triggerEvent("display-name-change", {id, displayname: displayName});
},
}
/**
* Notify external application (if API is enabled) that
@@ -181,7 +200,7 @@ export default {
*/
notifyConferenceJoined (room) {
triggerEvent("video-conference-joined", {roomName: room});
},
}
/**
* Notify external application (if API is enabled) that
@@ -191,7 +210,7 @@ export default {
*/
notifyConferenceLeft (room) {
triggerEvent("video-conference-left", {roomName: room});
},
}
/**
* Notify external application (if API is enabled) that
@@ -199,13 +218,23 @@ export default {
*/
notifyReadyToClose () {
triggerEvent("video-ready-to-close", {});
},
}
/**
* Sends remote control event.
* @param {RemoteControlEvent} event the remote control event.
*/
sendRemoteControlEvent(event) {
sendMessage({method: "remote-control-event", params: event});
}
/**
* Removes the listeners.
*/
dispose: function () {
dispose () {
if(enabled)
postis.destroy();
}
};
}
export default new API();

View File

@@ -1441,4 +1441,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
GumPermissionsOverlay.hide();
};
/**
* Handles user's features changes.
*/
UI.onUserFeaturesChanged = function (user) {
VideoLayout.onUserFeaturesChanged(user);
};
module.exports = UI;

View File

@@ -3,6 +3,7 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
import Avatar from "../avatar/Avatar";
import {createDeferred} from '../../util/helpers';
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer";
@@ -19,6 +20,7 @@ export default class LargeVideoManager {
* @type {Object.<string, LargeContainer>}
*/
this.containers = {};
this.eventEmitter = emitter;
this.state = VIDEO_CONTAINER_TYPE;
this.videoContainer = new VideoContainer(
@@ -164,6 +166,7 @@ export default class LargeVideoManager {
// after everything is done check again if there are any pending
// new streams.
this.updateInProcess = false;
this.eventEmitter.emit(UIEvents.LARGE_VIDEO_ID_CHANGED, this.id);
this.scheduleLargeVideoUpdate();
});
}

View File

@@ -29,6 +29,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.videoSpanId = `participant_${this.id}`;
SmallVideo.call(this, VideoLayout);
this.hasRemoteVideoMenu = false;
this._supportsRemoteControl = false;
this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, this.id);
this.setDisplayName();
@@ -64,7 +65,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
this.initBrowserSpecificProperties();
if (APP.conference.isModerator) {
if (APP.conference.isModerator || this._supportsRemoteControl) {
this.addRemoteVideoMenu();
}
@@ -106,14 +107,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
// call the original show, passing its actual this
origShowFunc.call(this.popover);
}.bind(this);
// override popover hide method so we can cleanup click handlers
let origHideFunc = this.popover.forceHide;
this.popover.forceHide = function () {
$(document).off("click", '#mutelink_' + this.id);
$(document).off("click", '#ejectlink_' + this.id);
origHideFunc.call(this.popover);
}.bind(this);
};
/**
@@ -139,38 +132,68 @@ RemoteVideo.prototype._generatePopupContent = function () {
let popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = `remote_popupmenu_${this.id}`;
let menuItems = [];
let muteTranslationKey;
let muteClassName;
if (this.isAudioMuted) {
muteTranslationKey = 'videothumbnail.muted';
muteClassName = 'mutelink disabled';
} else {
muteTranslationKey = 'videothumbnail.domute';
muteClassName = 'mutelink';
if(APP.conference.isModerator) {
let muteTranslationKey;
let muteClassName;
if (this.isAudioMuted) {
muteTranslationKey = 'videothumbnail.muted';
muteClassName = 'mutelink disabled';
} else {
muteTranslationKey = 'videothumbnail.domute';
muteClassName = 'mutelink';
}
let muteHandler = this._muteHandler.bind(this);
let kickHandler = this._kickHandler.bind(this);
menuItems = [
{
id: 'mutelink_' + this.id,
handler: muteHandler,
icon: 'icon-mic-disabled',
className: muteClassName,
data: {
i18n: muteTranslationKey
}
}, {
id: 'ejectlink_' + this.id,
handler: kickHandler,
icon: 'icon-kick',
data: {
i18n: 'videothumbnail.kick'
}
}
];
}
let muteHandler = this._muteHandler.bind(this);
let kickHandler = this._kickHandler.bind(this);
let menuItems = [
{
id: 'mutelink_' + this.id,
handler: muteHandler,
icon: 'icon-mic-disabled',
className: muteClassName,
data: {
i18n: muteTranslationKey
}
}, {
id: 'ejectlink_' + this.id,
handler: kickHandler,
icon: 'icon-kick',
data: {
i18n: 'videothumbnail.kick'
}
if(this._supportsRemoteControl) {
let icon, handler, className;
if(APP.remoteControl.controller.getRequestedParticipant()
=== this.id) {
handler = () => {};
className = "requestRemoteControlLink disabled";
icon = "remote-control-spinner fa fa-spinner fa-spin";
} else if(!APP.remoteControl.controller.isStarted()) {
handler = this._requestRemoteControlPermissions.bind(this);
icon = "fa fa-play";
className = "requestRemoteControlLink";
} else {
handler = this._stopRemoteControl.bind(this);
icon = "fa fa-stop";
className = "requestRemoteControlLink";
}
];
menuItems.push({
id: 'remoteControl_' + this.id,
handler,
icon,
className,
data: {
i18n: 'videothumbnail.remoteControl'
}
});
}
menuItems.forEach(el => {
let menuItem = this._generatePopupMenuItem(el);
@@ -182,6 +205,76 @@ RemoteVideo.prototype._generatePopupContent = function () {
return popupmenuElement;
};
/**
* Sets the remote control supported value and initializes or updates the menu
* depending on the remote control is supported or not.
* @param {boolean} isSupported
*/
RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
if(this._supportsRemoteControl === isSupported) {
return;
}
this._supportsRemoteControl = isSupported;
if(!isSupported) {
return;
}
if(!this.hasRemoteVideoMenu) {
//create menu
this.addRemoteVideoMenu();
} else {
//update the content
this.updateRemoteVideoMenu(this.isAudioMuted, true);
}
};
/**
* Requests permissions for remote control session.
*/
RemoteVideo.prototype._requestRemoteControlPermissions = function () {
APP.remoteControl.controller.requestPermissions(
this.id, this.VideoLayout.getLargeVideoWrapper()).then(result => {
if(result === null) {
return;
}
this.updateRemoteVideoMenu(this.isAudioMuted, true);
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
(result === false) ? "dialog.remoteControlDeniedMessage"
: "dialog.remoteControlAllowedMessage",
{user: this.user.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
);
if(result === true) {//the remote control permissions has been granted
// pin the controlled participant
let pinnedId = this.VideoLayout.getPinnedId();
if(pinnedId !== this.id) {
this.VideoLayout.handleVideoThumbClicked(this.id);
}
}
}, error => {
logger.error(error);
this.updateRemoteVideoMenu(this.isAudioMuted, true);
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
"dialog.remoteControlErrorMessage",
{user: this.user.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
);
});
this.updateRemoteVideoMenu(this.isAudioMuted, true);
};
/**
* Stops remote control session.
*/
RemoteVideo.prototype._stopRemoteControl = function () {
// send message about stopping
APP.remoteControl.controller.stop();
this.updateRemoteVideoMenu(this.isAudioMuted, true);
};
RemoteVideo.prototype._muteHandler = function () {
if (this.isAudioMuted)
return;
@@ -244,8 +337,7 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) {
linkItem.appendChild(textContent);
linkItem.id = id;
// Delegate event to the document.
$(document).on("click", `#${id}`, handler);
linkItem.onclick = handler;
menuItem.appendChild(linkItem);
return menuItem;

View File

@@ -406,6 +406,7 @@ var VideoLayout = {
remoteVideo = smallVideo;
else
remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
this._setRemoteControlProperties(user, remoteVideo);
this.addRemoteVideoContainer(id, remoteVideo);
},
@@ -1158,12 +1159,44 @@ var VideoLayout = {
* Sets the flipX state of the local video.
* @param {boolean} true for flipped otherwise false;
*/
setLocalFlipX: function (val) {
setLocalFlipX (val) {
this.localFlipX = val;
},
getEventEmitter: () => {return eventEmitter;}
getEventEmitter() {return eventEmitter;},
/**
* Handles user's features changes.
*/
onUserFeaturesChanged (user) {
let video = this.getSmallVideo(user.getId());
if (!video) {
return;
}
this._setRemoteControlProperties(user, video);
},
/**
* Sets the remote control properties (checks whether remote control
* is supported and executes remoteVideo.setRemoteControlSupport).
* @param {JitsiParticipant} user the user that will be checked for remote
* control support.
* @param {RemoteVideo} remoteVideo the remoteVideo on which the properties
* will be set.
*/
_setRemoteControlProperties (user, remoteVideo) {
APP.remoteControl.checkUserRemoteControlSupport(user).then(result =>
remoteVideo.setRemoteControlSupport(result));
},
/**
* Returns the wrapper jquery selector for the largeVideo
* @returns {JQuerySelector} the wrapper jquery selector for the largeVideo
*/
getLargeVideoWrapper() {
return this.getCurrentlyOnLargeContainer().$wrapper;
}
};
export default VideoLayout;

View File

@@ -65,6 +65,12 @@ function showKeyboardShortcutsPanel(show) {
*/
let _shortcuts = {};
/**
* True if the keyboard shortcuts are enabled and false if not.
* @type {boolean}
*/
let enabled = true;
/**
* Maps keycode to character, id of popover for given function and function.
*/
@@ -74,6 +80,9 @@ var KeyboardShortcut = {
var self = this;
window.onkeyup = function(e) {
if(!enabled) {
return;
}
var key = self._getKeyboardKey(e).toUpperCase();
var num = parseInt(key, 10);
if(!($(":focus").is("input[type=text]") ||
@@ -93,6 +102,9 @@ var KeyboardShortcut = {
};
window.onkeydown = function(e) {
if(!enabled) {
return;
}
if(!($(":focus").is("input[type=text]") ||
$(":focus").is("input[type=password]") ||
$(":focus").is("textarea"))) {
@@ -105,6 +117,14 @@ var KeyboardShortcut = {
};
},
/**
* Enables/Disables the keyboard shortcuts.
* @param {boolean} value - the new value.
*/
enable: function (value) {
enabled = value;
},
/**
* Registers a new shortcut.
*

163
modules/keycode/keycode.js Normal file
View File

@@ -0,0 +1,163 @@
/**
* Enumerates the supported keys.
* NOTE: The maps represents physical keys on the keyboard, not chars.
* @readonly
* @enum {string}
*/
export const KEYS = {
BACKSPACE: "backspace" ,
DELETE : "delete",
RETURN : "enter",
TAB : "tab",
ESCAPE : "escape",
UP : "up",
DOWN : "down",
RIGHT : "right",
LEFT : "left",
HOME : "home",
END : "end",
PAGEUP : "pageup",
PAGEDOWN : "pagedown",
F1 : "f1",
F2 : "f2",
F3 : "f3",
F4 : "f4",
F5 : "f5",
F6 : "f6",
F7 : "f7",
F8 : "f8",
F9 : "f9",
F10 : "f10",
F11 : "f11",
F12 : "f12",
META : "command",
CMD_L: "command",
CMD_R: "command",
ALT : "alt",
CONTROL : "control",
SHIFT : "shift",
CAPS_LOCK: "caps_lock", //not supported by robotjs
SPACE : "space",
PRINTSCREEN : "printscreen",
INSERT : "insert",
NUMPAD_0 : "numpad_0",
NUMPAD_1 : "numpad_1",
NUMPAD_2 : "numpad_2",
NUMPAD_3 : "numpad_3",
NUMPAD_4 : "numpad_4",
NUMPAD_5 : "numpad_5",
NUMPAD_6 : "numpad_6",
NUMPAD_7 : "numpad_7",
NUMPAD_8 : "numpad_8",
NUMPAD_9 : "numpad_9",
COMMA: ",",
PERIOD: ".",
SEMICOLON: ";",
QUOTE: "'",
BRACKET_LEFT: "[",
BRACKET_RIGHT: "]",
BACKQUOTE: "`",
BACKSLASH: "\\",
MINUS: "-",
EQUAL: "=",
SLASH: "/"
};
/**
* Mapping between the key codes and keys deined in KEYS.
* The mappings are based on
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Specifications
*/
let keyCodeToKey = {
8: KEYS.BACKSPACE,
9: KEYS.TAB,
13: KEYS.RETURN,
16: KEYS.SHIFT,
17: KEYS.CONTROL,
18: KEYS.ALT,
20: KEYS.CAPS_LOCK,
27: KEYS.ESCAPE,
32: KEYS.SPACE,
33: KEYS.PAGEUP,
34: KEYS.PAGEDOWN,
35: KEYS.END,
36: KEYS.HOME,
37: KEYS.LEFT,
38: KEYS.UP,
39: KEYS.RIGHT,
40: KEYS.DOWN,
42: KEYS.PRINTSCREEN,
44: KEYS.PRINTSCREEN,
45: KEYS.INSERT,
46: KEYS.DELETE,
59: KEYS.SEMICOLON,
61: KEYS.EQUAL,
91: KEYS.CMD_L,
92: KEYS.CMD_R,
93: KEYS.CMD_R,
96: KEYS.NUMPAD_0,
97: KEYS.NUMPAD_1,
98: KEYS.NUMPAD_2,
99: KEYS.NUMPAD_3,
100: KEYS.NUMPAD_4,
101: KEYS.NUMPAD_5,
102: KEYS.NUMPAD_6,
103: KEYS.NUMPAD_7,
104: KEYS.NUMPAD_8,
105: KEYS.NUMPAD_9,
112: KEYS.F1,
113: KEYS.F2,
114: KEYS.F3,
115: KEYS.F4,
116: KEYS.F5,
117: KEYS.F6,
118: KEYS.F7,
119: KEYS.F8,
120: KEYS.F9,
121: KEYS.F10,
122: KEYS.F11,
123: KEYS.F12,
124: KEYS.PRINTSCREEN,
173: KEYS.MINUS,
186: KEYS.SEMICOLON,
187: KEYS.EQUAL,
188: KEYS.COMMA,
189: KEYS.MINUS,
190: KEYS.PERIOD,
191: KEYS.SLASH,
192: KEYS.BACKQUOTE,
219: KEYS.BRACKET_LEFT,
220: KEYS.BACKSLASH,
221: KEYS.BRACKET_RIGHT,
222: KEYS.QUOTE,
224: KEYS.META,
229: KEYS.SEMICOLON
};
/**
* Generate codes for digit keys (0-9)
*/
for(let i = 0; i < 10; i++) {
keyCodeToKey[i + 48] = `${i}`;
}
/**
* Generate codes for letter keys (a-z)
*/
for(let i = 0; i < 26; i++) {
let keyCode = i + 65;
keyCodeToKey[keyCode] = String.fromCharCode(keyCode).toLowerCase();
}
/**
* Returns key associated with the keyCode from the passed event.
* @param {KeyboardEvent} event the event
* @returns {KEYS} the key on the keyboard.
*/
export function keyboardEventToKey(event) {
return keyCodeToKey[event.which];
}

View File

@@ -0,0 +1,374 @@
/* global $, JitsiMeetJS, APP */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import * as KeyCodes from "../keycode/keycode";
import {EVENT_TYPES, REMOTE_CONTROL_EVENT_TYPE, PERMISSIONS_ACTIONS}
from "../../service/remotecontrol/Constants";
import RemoteControlParticipant from "./RemoteControlParticipant";
import UIEvents from "../../service/UI/UIEvents";
const ConferenceEvents = JitsiMeetJS.events.conference;
/**
* Extract the keyboard key from the keyboard event.
* @param event {KeyboardEvent} the event.
* @returns {KEYS} the key that is pressed or undefined.
*/
function getKey(event) {
return KeyCodes.keyboardEventToKey(event);
}
/**
* Extract the modifiers from the keyboard event.
* @param event {KeyboardEvent} the event.
* @returns {Array} with possible values: "shift", "control", "alt", "command".
*/
function getModifiers(event) {
let modifiers = [];
if(event.shiftKey) {
modifiers.push("shift");
}
if(event.ctrlKey) {
modifiers.push("control");
}
if(event.altKey) {
modifiers.push("alt");
}
if(event.metaKey) {
modifiers.push("command");
}
return modifiers;
}
/**
* This class represents the controller party for a remote controller session.
* It listens for mouse and keyboard events and sends them to the receiver
* party of the remote control session.
*/
export default class Controller extends RemoteControlParticipant {
/**
* Creates new instance.
*/
constructor() {
super();
this.isCollectingEvents = false;
this.controlledParticipant = null;
this.requestedParticipant = null;
this._stopListener = this._handleRemoteControlStoppedEvent.bind(this);
this._userLeftListener = this._onUserLeft.bind(this);
this._largeVideoChangedListener
= this._onLargeVideoIdChanged.bind(this);
}
/**
* Requests permissions from the remote control receiver side.
* @param {string} userId the user id of the participant that will be
* requested.
* @param {JQuerySelector} eventCaptureArea the area that is going to be
* used mouse and keyboard event capture.
* @returns {Promise<boolean>} - resolve values:
* true - accept
* false - deny
* null - the participant has left.
*/
requestPermissions(userId, eventCaptureArea) {
if(!this.enabled) {
return Promise.reject(new Error("Remote control is disabled!"));
}
this.area = eventCaptureArea;// $("#largeVideoWrapper")
logger.log("Requsting remote control permissions from: " + userId);
return new Promise((resolve, reject) => {
const clearRequest = () => {
this.requestedParticipant = null;
APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
APP.conference.removeConferenceListener(
ConferenceEvents.USER_LEFT,
onUserLeft);
};
const permissionsReplyListener = (participant, event) => {
let result = null;
try {
result = this._handleReply(participant, event);
} catch (e) {
reject(e);
}
if(result !== null) {
clearRequest();
resolve(result);
}
};
const onUserLeft = (id) => {
if(id === this.requestedParticipant) {
clearRequest();
resolve(null);
}
};
APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
onUserLeft);
this.requestedParticipant = userId;
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.request
}, e => {
clearRequest();
reject(e);
});
});
}
/**
* Handles the reply of the permissions request.
* @param {JitsiParticipant} participant the participant that has sent the
* reply
* @param {RemoteControlEvent} event the remote control event.
*/
_handleReply(participant, event) {
const remoteControlEvent = event.event;
const userId = participant.getId();
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
&& remoteControlEvent.type === EVENT_TYPES.permissions
&& userId === this.requestedParticipant) {
if(remoteControlEvent.action !== PERMISSIONS_ACTIONS.grant) {
this.area = null;
}
switch(remoteControlEvent.action) {
case PERMISSIONS_ACTIONS.grant: {
this.controlledParticipant = userId;
logger.log("Remote control permissions granted to: "
+ userId);
this._start();
return true;
}
case PERMISSIONS_ACTIONS.deny:
return false;
case PERMISSIONS_ACTIONS.error:
throw new Error("Error occurred on receiver side");
default:
throw new Error("Unknown reply received!");
}
} else {
//different message type or another user -> ignoring the message
return null;
}
}
/**
* Handles remote control stopped.
* @param {JitsiParticipant} participant the participant that has sent the
* event
* @param {Object} event EndpointMessage event from the data channels.
* @property {string} type property. The function process only events of
* type REMOTE_CONTROL_EVENT_TYPE
* @property {RemoteControlEvent} event - the remote control event.
*/
_handleRemoteControlStoppedEvent(participant, event) {
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
&& event.event.type === EVENT_TYPES.stop
&& participant.getId() === this.controlledParticipant) {
this._stop();
}
}
/**
* Starts processing the mouse and keyboard events. Sets conference
* listeners. Disables keyboard events.
*/
_start() {
logger.log("Starting remote control controller.");
APP.UI.addListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
this._largeVideoChangedListener);
APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this._stopListener);
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.resume();
}
/**
* Disables the keyboatd shortcuts. Starts collecting remote control
* events.
*
* It can be used to resume an active remote control session wchich was
* paused with this.pause().
*/
resume() {
if(!this.enabled || this.isCollectingEvents) {
return;
}
logger.log("Resuming remote control controller.");
this.isCollectingEvents = true;
APP.keyboardshortcut.enable(false);
this.area.mousemove(event => {
const position = this.area.position();
this._sendRemoteControlEvent(this.controlledParticipant, {
type: EVENT_TYPES.mousemove,
x: (event.pageX - position.left)/this.area.width(),
y: (event.pageY - position.top)/this.area.height()
});
});
this.area.mousedown(this._onMouseClickHandler.bind(this,
EVENT_TYPES.mousedown));
this.area.mouseup(this._onMouseClickHandler.bind(this,
EVENT_TYPES.mouseup));
this.area.dblclick(
this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
this.area.contextmenu(() => false);
this.area[0].onmousewheel = event => {
this._sendRemoteControlEvent(this.controlledParticipant, {
type: EVENT_TYPES.mousescroll,
x: event.deltaX,
y: event.deltaY
});
};
$(window).keydown(this._onKeyPessHandler.bind(this,
EVENT_TYPES.keydown));
$(window).keyup(this._onKeyPessHandler.bind(this, EVENT_TYPES.keyup));
}
/**
* Stops processing the mouse and keyboard events. Removes added listeners.
* Enables the keyboard shortcuts. Displays dialog to notify the user that
* remote control session has ended.
*/
_stop() {
if(!this.controlledParticipant) {
return;
}
logger.log("Stopping remote control controller.");
APP.UI.removeListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
this._largeVideoChangedListener);
APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this._stopListener);
APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controlledParticipant = null;
this.pause();
this.area = null;
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
"dialog.remoteControlStopMessage"
);
}
/**
* Executes this._stop() mehtod:
* Stops processing the mouse and keyboard events. Removes added listeners.
* Enables the keyboard shortcuts. Displays dialog to notify the user that
* remote control session has ended.
*
* In addition:
* Sends stop message to the controlled participant.
*/
stop() {
if(!this.controlledParticipant) {
return;
}
this._sendRemoteControlEvent(this.controlledParticipant, {
type: EVENT_TYPES.stop
});
this._stop();
}
/**
* Pauses the collecting of events and enables the keyboard shortcus. But
* it doesn't removes any other listeners. Basically the remote control
* session will be still active after this.pause(), but no events from the
* controller side will be captured and sent.
*
* You can resume the collecting of the events with this.resume().
*/
pause() {
if(!this.controlledParticipant) {
return;
}
logger.log("Pausing remote control controller.");
this.isCollectingEvents = false;
APP.keyboardshortcut.enable(true);
this.area.off( "mousemove" );
this.area.off( "mousedown" );
this.area.off( "mouseup" );
this.area.off( "contextmenu" );
this.area.off( "dblclick" );
$(window).off( "keydown");
$(window).off( "keyup");
this.area[0].onmousewheel = undefined;
}
/**
* Handler for mouse click events.
* @param {String} type the type of event ("mousedown"/"mouseup")
* @param {Event} event the mouse event.
*/
_onMouseClickHandler(type, event) {
this._sendRemoteControlEvent(this.controlledParticipant, {
type: type,
button: event.which
});
}
/**
* Returns true if the remote control session is started.
* @returns {boolean}
*/
isStarted() {
return this.controlledParticipant !== null;
}
/**
* Returns the id of the requested participant
* @returns {string} this.requestedParticipant.
* NOTE: This id should be the result of JitsiParticipant.getId() call.
*/
getRequestedParticipant() {
return this.requestedParticipant;
}
/**
* Handler for key press events.
* @param {String} type the type of event ("keydown"/"keyup")
* @param {Event} event the key event.
*/
_onKeyPessHandler(type, event) {
this._sendRemoteControlEvent(this.controlledParticipant, {
type: type,
key: getKey(event),
modifiers: getModifiers(event),
});
}
/**
* Calls the stop method if the other side have left.
* @param {string} id - the user id for the participant that have left
*/
_onUserLeft(id) {
if(this.controlledParticipant === id) {
this._stop();
}
}
/**
* Handles changes of the participant displayed on the large video.
* @param {string} id - the user id for the participant that is displayed.
*/
_onLargeVideoIdChanged(id) {
if (!this.controlledParticipant) {
return;
}
if(this.controlledParticipant == id) {
this.resume();
} else {
this.pause();
}
}
}

View File

@@ -0,0 +1,192 @@
/* global APP, JitsiMeetJS, interfaceConfig */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
import RemoteControlParticipant from "./RemoteControlParticipant";
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
const ConferenceEvents = JitsiMeetJS.events.conference;
/**
* This class represents the receiver party for a remote controller session.
* It handles "remote-control-event" events and sends them to the
* API module. From there the events can be received from wrapper application
* and executed.
*/
export default class Receiver extends RemoteControlParticipant {
/**
* Creates new instance.
* @constructor
*/
constructor() {
super();
this.controller = null;
this._remoteControlEventsListener
= this._onRemoteControlEvent.bind(this);
this._userLeftListener = this._onUserLeft.bind(this);
this._hangupListener = this._onHangup.bind(this);
}
/**
* Enables / Disables the remote control
* @param {boolean} enabled the new state.
*/
enable(enabled) {
if(this.enabled === enabled) {
return;
}
this.enabled = enabled;
if(enabled === true) {
logger.log("Remote control receiver enabled.");
// Announce remote control support.
APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this._remoteControlEventsListener);
APP.conference.addListener(JitsiMeetConferenceEvents.BEFORE_HANGUP,
this._hangupListener);
} else {
logger.log("Remote control receiver disabled.");
this._stop(true);
APP.connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE);
APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this._remoteControlEventsListener);
APP.conference.removeListener(
JitsiMeetConferenceEvents.BEFORE_HANGUP,
this._hangupListener);
}
}
/**
* Removes the listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED
* events. Sends stop message to the wrapper application. Optionally
* displays dialog for informing the user that remote control session
* ended.
* @param {boolean} dontShowDialog - if true the dialog won't be displayed.
*/
_stop(dontShowDialog = false) {
if(!this.controller) {
return;
}
logger.log("Remote control receiver stop.");
this.controller = null;
APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
APP.API.sendRemoteControlEvent({
type: EVENT_TYPES.stop
});
if(!dontShowDialog) {
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
"dialog.remoteControlStopMessage"
);
}
}
/**
* Calls this._stop() and sends stop message to the controller participant
*/
stop() {
if(!this.controller) {
return;
}
this._sendRemoteControlEvent(this.controller, {
type: EVENT_TYPES.stop
});
this._stop();
}
/**
* Listens for data channel EndpointMessage events. Handles only events of
* type remote control. Sends "remote-control-event" events to the API
* module.
* @param {JitsiParticipant} participant the controller participant
* @param {Object} event EndpointMessage event from the data channels.
* @property {string} type property. The function process only events of
* type REMOTE_CONTROL_EVENT_TYPE
* @property {RemoteControlEvent} event - the remote control event.
*/
_onRemoteControlEvent(participant, event) {
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE) {
const remoteControlEvent = event.event;
if(this.controller === null
&& remoteControlEvent.type === EVENT_TYPES.permissions
&& remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
remoteControlEvent.userId = participant.getId();
remoteControlEvent.userJID = participant.getJid();
remoteControlEvent.displayName = participant.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
remoteControlEvent.screenSharing
= APP.conference.isSharingScreen;
} else if(this.controller !== participant.getId()) {
return;
} else if(remoteControlEvent.type === EVENT_TYPES.stop) {
this._stop();
return;
}
APP.API.sendRemoteControlEvent(remoteControlEvent);
} else if(event.type === REMOTE_CONTROL_EVENT_TYPE) {
logger.log("Remote control event is ignored because remote "
+ "control is disabled", event);
}
}
/**
* Handles remote control permission events received from the API module.
* @param {String} userId the user id of the participant related to the
* event.
* @param {PERMISSIONS_ACTIONS} action the action related to the event.
*/
_onRemoteControlPermissionsEvent(userId, action) {
if(action === PERMISSIONS_ACTIONS.grant) {
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controller = userId;
logger.log("Remote control permissions granted to: " + userId);
if(!APP.conference.isSharingScreen) {
APP.conference.toggleScreenSharing();
APP.conference.screenSharingPromise.then(() => {
if(APP.conference.isSharingScreen) {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: action
});
} else {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
}
}).catch(() => {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
});
return;
}
}
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: action
});
}
/**
* Calls the stop method if the other side have left.
* @param {string} id - the user id for the participant that have left
*/
_onUserLeft(id) {
if(this.controller === id) {
this._stop();
}
}
/**
* Handles hangup events. Disables the receiver.
*/
_onHangup() {
this.enable(false);
}
}

View File

@@ -0,0 +1,89 @@
/* global APP, config */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import Controller from "./Controller";
import Receiver from "./Receiver";
import {EVENT_TYPES, DISCO_REMOTE_CONTROL_FEATURE}
from "../../service/remotecontrol/Constants";
/**
* Implements the remote control functionality.
*/
class RemoteControl {
/**
* Constructs new instance. Creates controller and receiver properties.
* @constructor
*/
constructor() {
this.controller = new Controller();
this.receiver = new Receiver();
this.enabled = false;
this.initialized = false;
}
/**
* Initializes the remote control - checks if the remote control should be
* enabled or not, initializes the API module.
*/
init() {
if(config.disableRemoteControl || this.initialized
|| !APP.conference.isDesktopSharingEnabled) {
return;
}
logger.log("Initializing remote control.");
this.initialized = true;
APP.API.init({
forceEnable: true,
});
this.controller.enable(true);
if(this.enabled) { // supported message came before init.
this._onRemoteControlSupported();
}
}
/**
* Handles remote control events from the API module. Currently only events
* with type = EVENT_TYPES.supported or EVENT_TYPES.permissions
* @param {RemoteControlEvent} event the remote control event.
*/
onRemoteControlAPIEvent(event) {
switch(event.type) {
case EVENT_TYPES.supported:
this._onRemoteControlSupported();
break;
case EVENT_TYPES.permissions:
this.receiver._onRemoteControlPermissionsEvent(
event.userId, event.action);
break;
}
}
/**
* Handles API event for support for executing remote control events into
* the wrapper application.
*/
_onRemoteControlSupported() {
logger.log("Remote Control supported.");
if(!config.disableRemoteControl) {
this.enabled = true;
if(this.initialized) {
this.receiver.enable(true);
}
} else {
logger.log("Remote Control disabled.");
}
}
/**
* Checks whether the passed user supports remote control or not
* @param {JitsiParticipant} user the user to be tested
* @returns {Promise<boolean>} the promise will be resolved with true if
* the user supports remote control and with false if not.
*/
checkUserRemoteControlSupport(user) {
return user.getFeatures().then(features =>
features.has(DISCO_REMOTE_CONTROL_FEATURE), () => false
);
}
}
export default new RemoteControl();

View File

@@ -0,0 +1,42 @@
/* global APP */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import {REMOTE_CONTROL_EVENT_TYPE}
from "../../service/remotecontrol/Constants";
export default class RemoteControlParticipant {
/**
* Creates new instance.
*/
constructor() {
this.enabled = false;
}
/**
* Enables / Disables the remote control
* @param {boolean} enabled the new state.
*/
enable(enabled) {
this.enabled = enabled;
}
/**
* Sends remote control event to other participant trough data channel.
* @param {RemoteControlEvent} event the remote control event.
* @param {Function} onDataChannelFail handler for data channel failure.
*/
_sendRemoteControlEvent(to, event, onDataChannelFail = () => {}) {
if(!this.enabled || !to) {
logger.warn("Remote control: Skip sending remote control event."
+ " Params:", this.enable, to);
return;
}
try{
APP.conference.sendEndpointMessage(to,
{type: REMOTE_CONTROL_EVENT_TYPE, event});
} catch (e) {
logger.error("Failed to send EndpointMessage via the datachannels",
e);
onDataChannelFail(e);
}
}
}

View File

@@ -73,10 +73,6 @@ class TokenData{
this.jwt = jwt;
//External API settings
this.externalAPISettings = {
forceEnable: true
};
this._decode();
// Use JWT param as token if there is not other token set and if the
// iss field is not anonymous. If you want to pass data with JWT token

View File

@@ -34,10 +34,10 @@
"postis": "^2.2.0",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.39.2",
"react-native": "0.40.0",
"react-native-keep-awake": "^1.0.7",
"react-native-prompt": "^1.0.0",
"react-native-vector-icons": "^3.0.0",
"react-native-vector-icons": "^4.0.0",
"react-native-webrtc": "jitsi/react-native-webrtc",
"react-redux": "^5.0.2",
"redux": "^3.5.2",
@@ -57,7 +57,7 @@
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-1": "^6.16.0",
"clean-css": "*",
"clean-css": "^3.0.0",
"css-loader": "*",
"eslint": "^3.13.1",
"eslint-plugin-jsdoc": "*",

View File

@@ -23,7 +23,11 @@ export function init() {
APP.keyboardshortcut = KeyboardShortcut;
APP.tokenData = getTokenData();
APP.API.init(APP.tokenData.externalAPISettings);
// Force enable the API if jwt token is passed because most probably
// jitsi meet is displayed inside of wrapper that will need to communicate
// with jitsi meet.
APP.API.init(APP.tokenData.jwt ? { forceEnable: true } : undefined);
APP.translation.init(settings.getLanguage());
}

View File

@@ -120,6 +120,11 @@ export default {
*/
LARGE_VIDEO_AVATAR_VISIBLE: "UI.large_video_avatar_visible",
/**
* Notifies that the displayed particpant id on the largeVideo is changed.
*/
LARGE_VIDEO_ID_CHANGED: "UI.large_video_id_changed",
/**
* Toggling room lock
*/

View File

@@ -0,0 +1,69 @@
/**
* The value for the "var" attribute of feature tag in disco-info packets.
*/
export const DISCO_REMOTE_CONTROL_FEATURE
= "http://jitsi.org/meet/remotecontrol";
/**
* Types of remote-control-event events.
* @readonly
* @enum {string}
*/
export const EVENT_TYPES = {
mousemove: "mousemove",
mousedown: "mousedown",
mouseup: "mouseup",
mousedblclick: "mousedblclick",
mousescroll: "mousescroll",
keydown: "keydown",
keyup: "keyup",
permissions: "permissions",
stop: "stop",
supported: "supported"
};
/**
* Actions for the remote control permission events.
* @readonly
* @enum {string}
*/
export const PERMISSIONS_ACTIONS = {
request: "request",
grant: "grant",
deny: "deny",
error: "error"
};
/**
* The type of remote control events sent trough the API module.
*/
export const REMOTE_CONTROL_EVENT_TYPE = "remote-control-event";
/**
* The remote control event.
* @typedef {object} RemoteControlEvent
* @property {EVENT_TYPES} type - the type of the event
* @property {int} x - avaibale for type === mousemove only. The new x
* coordinate of the mouse
* @property {int} y - For mousemove type - the new y
* coordinate of the mouse and for mousescroll - represents the vertical
* scrolling diff value
* @property {int} button - 1(left), 2(middle) or 3 (right). Supported by
* mousedown, mouseup and mousedblclick types.
* @property {KEYS} key - Represents the key related to the event. Supported by
* keydown and keyup types.
* @property {KEYS[]} modifiers - Represents the modifier related to the event.
* Supported by keydown and keyup types.
* @property {PERMISSIONS_ACTIONS} action - Supported by type === permissions.
* Represents the action related to the permissions event.
*
* Optional properties. Supported for permissions event for action === request:
* @property {string} userId - The user id of the participant that has sent the
* request.
* @property {string} userJID - The full JID in the MUC of the user that has
* sent the request.
* @property {string} displayName - the displayName of the participant that has
* sent the request.
* @property {boolean} screenSharing - true if the SS is started for the local
* participant and false if not.
*/