mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-08 07:40:18 +00:00
Compare commits
47 Commits
3400
...
util-lua-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ed9d5893b | ||
|
|
07b7f03aa7 | ||
|
|
7ce44f85ca | ||
|
|
41e0d782ce | ||
|
|
2a8fafdd36 | ||
|
|
faee1c139e | ||
|
|
eb644987ce | ||
|
|
de60a70daf | ||
|
|
2904dfa794 | ||
|
|
d51cf7c581 | ||
|
|
f25e6c6a5d | ||
|
|
5fb9422513 | ||
|
|
d01cfc8466 | ||
|
|
fa3888991f | ||
|
|
ded355a807 | ||
|
|
b655c8d54a | ||
|
|
42a6e6faaf | ||
|
|
c7954c284d | ||
|
|
251da1861a | ||
|
|
9712804040 | ||
|
|
fecbef0aff | ||
|
|
d65b71b584 | ||
|
|
579d291bca | ||
|
|
871026f4ba | ||
|
|
9a8a070c62 | ||
|
|
7cf4c7bd78 | ||
|
|
72a1def571 | ||
|
|
0dad99c3b7 | ||
|
|
840c0190c4 | ||
|
|
e0fdeea69b | ||
|
|
e3612929f8 | ||
|
|
70921bb6ef | ||
|
|
371ca4eef1 | ||
|
|
54fdb7066f | ||
|
|
85bcb0c757 | ||
|
|
d387cbe5bd | ||
|
|
1bc28e4904 | ||
|
|
cb3419ba2a | ||
|
|
a2f8e156da | ||
|
|
a4cf79c161 | ||
|
|
9352517705 | ||
|
|
47d5163c52 | ||
|
|
def22b01bb | ||
|
|
9445cf99fd | ||
|
|
96b226de24 | ||
|
|
5101f69e4e | ||
|
|
61b66e0edf |
@@ -10,6 +10,7 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
@@ -64,13 +65,11 @@ pushd ${THIS_DIR}/../
|
||||
./gradlew clean assembleRelease publish
|
||||
popd
|
||||
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
fi
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
|
||||
@@ -52,6 +52,17 @@ class AmplitudeModule
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userId The new value for the user ID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(String instanceName, String userId) {
|
||||
Amplitude.getInstance(instanceName).setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
|
||||
@@ -40,6 +40,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
@@ -70,6 +74,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String subject;
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
@@ -105,6 +110,17 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the conference subject.
|
||||
* @param subject - Subject for the conference.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token to be used for authentication when joining a conference.
|
||||
* @param token - The JWT token to be used for authentication.
|
||||
@@ -185,6 +201,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
options.subject = this.subject;
|
||||
options.token = this.token;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.audioMuted = this.audioMuted;
|
||||
@@ -201,6 +218,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
room = in.readString();
|
||||
subject = in.readString();
|
||||
token = in.readString();
|
||||
colorScheme = in.readBundle();
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
@@ -238,6 +256,9 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
if (subject != null) {
|
||||
config.putString("subject", subject);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
@@ -281,6 +302,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(room);
|
||||
dest.writeString(subject);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
|
||||
@@ -66,7 +66,7 @@ class ReactInstanceManagerHolder {
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("AmplitudeModule");
|
||||
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -51,6 +51,8 @@ import {
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
notifyCameraError,
|
||||
notifyMicError,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
@@ -486,10 +488,13 @@ class ConferenceConnector {
|
||||
* call in hangup() to resolve when all operations are finished.
|
||||
*/
|
||||
function disconnect() {
|
||||
connection.disconnect();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
const onDisconnected = () => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
|
||||
return Promise.resolve();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return connection.disconnect().then(onDisconnected, onDisconnected);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,13 +699,14 @@ export default {
|
||||
// If both requests for 'audio' + 'video' and 'audio'
|
||||
// only failed, we assume that there are some problems
|
||||
// with user's microphone and show corresponding dialog.
|
||||
APP.UI.showMicErrorNotification(audioOnlyError);
|
||||
APP.UI.showCameraErrorNotification(videoOnlyError);
|
||||
APP.store.dispatch(notifyMicError(audioOnlyError));
|
||||
APP.store.dispatch(notifyCameraError(videoOnlyError));
|
||||
} else {
|
||||
// If request for 'audio' + 'video' failed, but request
|
||||
// for 'audio' only was OK, we assume that we had
|
||||
// problems with camera and show corresponding dialog.
|
||||
APP.UI.showCameraErrorNotification(audioAndVideoError);
|
||||
APP.store.dispatch(
|
||||
notifyCameraError(audioAndVideoError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,6 +792,13 @@ export default {
|
||||
this.recorder = new Recorder();
|
||||
}
|
||||
|
||||
if (config.startSilent) {
|
||||
APP.store.dispatch(showNotification({
|
||||
descriptionKey: 'notify.startSilentDescription',
|
||||
titleKey: 'notify.startSilentTitle'
|
||||
}));
|
||||
}
|
||||
|
||||
// XXX The API will take care of disconnecting from the XMPP
|
||||
// server (and, thus, leaving the room) on unload.
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -839,7 +852,7 @@ export default {
|
||||
|
||||
if (!this.localAudio && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showMicErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
@@ -902,7 +915,7 @@ export default {
|
||||
|
||||
if (!this.localVideo && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showCameraErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyCameraError(error));
|
||||
};
|
||||
|
||||
// Try to create local video if there wasn't any.
|
||||
@@ -2109,7 +2122,7 @@ export default {
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showCameraErrorNotification(err);
|
||||
APP.store.dispatch(notifyCameraError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2142,7 +2155,7 @@ export default {
|
||||
this._updateAudioDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showMicErrorNotification(err);
|
||||
APP.store.dispatch(notifyMicError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2600,8 +2613,7 @@ export default {
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave()
|
||||
.then(disconnect, disconnect);
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,6 +90,10 @@ var config = {
|
||||
// applied locally. FIXME: having these 2 options is confusing.
|
||||
// startWithAudioMuted: false,
|
||||
|
||||
// Enabling it (with #params) will disable local audio output of remote
|
||||
// participants and to enable it back a reload is needed.
|
||||
// startSilent: false
|
||||
|
||||
// Video
|
||||
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
@@ -414,6 +418,10 @@ var config = {
|
||||
// use only.
|
||||
// _desktopSharingSourceDevice: 'sample-id-or-label'
|
||||
|
||||
// A property to disable the right click context menu for localVideo
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -428,7 +436,6 @@ var config = {
|
||||
dialOutCodesUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
enableLocalVideoFlip
|
||||
etherpad_base
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
|
||||
@@ -514,6 +514,7 @@
|
||||
}
|
||||
#dominantSpeakerAvatar {
|
||||
background-color: #000000;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dynamic-shadow {
|
||||
@@ -525,12 +526,21 @@
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
@include maxSize(60px);
|
||||
@include absoluteAligning();
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
|
||||
.userAvatar {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#videoNotAvailableScreen {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/**
|
||||
* Let the avatar grow with the tile.
|
||||
*/
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
max-height: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
16
doc/api.md
16
doc/api.md
@@ -264,6 +264,14 @@ 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.
|
||||
|
||||
The following events are currently supported:
|
||||
* **cameraError** - event notifications about Jitsi-Meet having failed to access the camera. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **avatarChanged** - event notifications about avatar
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
@@ -287,6 +295,14 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **micError** - event notifications about Jitsi-Meet having failed to access the mic. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
|
||||
@@ -167,13 +167,7 @@ var interfaceConfig = {
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
RECENT_LIST_ENABLED: true,
|
||||
|
||||
/**
|
||||
* A UX mode where the last screen share participant is automatically
|
||||
* pinned. Note: this mode is experimental and subject to breakage.
|
||||
*/
|
||||
AUTO_PIN_LATEST_SCREEN_SHARE: true
|
||||
RECENT_LIST_ENABLED: true
|
||||
|
||||
/**
|
||||
* How many columns the tile view can expand to. The respected range is
|
||||
@@ -201,6 +195,12 @@ var interfaceConfig = {
|
||||
*/
|
||||
// ANDROID_APP_PACKAGE: 'org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* A UX mode where the last screen share participant is automatically
|
||||
* pinned. Note: this mode is experimental and subject to breakage.
|
||||
*/
|
||||
// AUTO_PIN_LATEST_SCREEN_SHARE: false,
|
||||
|
||||
/**
|
||||
* Override the behavior of some notifications to remain displayed until
|
||||
* explicitly dismissed through a user action. The value is how long, in
|
||||
|
||||
@@ -92,7 +92,7 @@ Leaves the currently active conference.
|
||||
|
||||
#### Universal / deep linking
|
||||
|
||||
In order to support Universal / deep linking, `JitsiMeetView` offers 2 class
|
||||
In order to support Universal / deep linking, `JitsiMeet` offers 2 class
|
||||
methods that you app's delegate should call in order for the app to follow those
|
||||
links.
|
||||
|
||||
@@ -104,7 +104,7 @@ is useful when the host application uses other SDKs which also use linking.
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||
{
|
||||
return [JitsiMeetView application:application
|
||||
return [[JitsiMeet sharedInstance] application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
@@ -117,7 +117,7 @@ And also one of the following:
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [JitsiMeetView application:app
|
||||
return [[JitsiMeet sharedInstance] application:app
|
||||
openURL:url
|
||||
options: options];
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"../../node_modules/react-native-webrtc/ios",
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
|
||||
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
|
||||
withData:(NSDictionary *)data {
|
||||
#if DEBUG
|
||||
NSLog(
|
||||
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
|
||||
__FILE__, __LINE__, name, data);
|
||||
|
||||
#if DEBUG
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.7 KiB |
@@ -14,6 +14,7 @@
|
||||
<objects>
|
||||
<controller title="Meetings" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="JitsiMeetCompanion" customModuleProvider="target">
|
||||
<items>
|
||||
<label alignment="left" textAlignment="left" numberOfLines="0" id="OQN-sx-tDt"/>
|
||||
<table alignment="left" id="gpO-ql-Xsr">
|
||||
<items>
|
||||
<tableRow identifier="MeetingRowType" id="GGl-av-xeJ" customClass="MeetingRowController" customModule="JitsiMeetCompanion_Extension">
|
||||
@@ -39,6 +40,7 @@
|
||||
</table>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="infoLabel" destination="OQN-sx-tDt" id="Juv-tb-SNj"/>
|
||||
<outlet property="table" destination="gpO-ql-Xsr" id="aVV-iZ-z3l"/>
|
||||
</connections>
|
||||
</controller>
|
||||
|
||||
@@ -16,17 +16,31 @@
|
||||
*/
|
||||
|
||||
import WatchKit
|
||||
import WatchConnectivity
|
||||
import Foundation
|
||||
|
||||
|
||||
class InterfaceController: WKInterfaceController {
|
||||
|
||||
@IBOutlet var infoLabel: WKInterfaceLabel!
|
||||
@IBOutlet var table: WKInterfaceTable!
|
||||
|
||||
override func didAppear(){
|
||||
self.updateUI(ExtensionDelegate.currentJitsiMeetContext)
|
||||
}
|
||||
|
||||
func updateUI(_ newContext:JitsiMeetContext) {
|
||||
if let recentURLsArray = newContext.recentURLs {
|
||||
updateRecents(withRecents: recentURLsArray, currentContext: newContext)
|
||||
}
|
||||
if (newContext.recentURLs == nil || newContext.recentURLs!.count == 0) {
|
||||
infoLabel.setText("There are no recent meetings. Please use the app on the phone to start a new call.")
|
||||
|
||||
table.setHidden(true)
|
||||
infoLabel.setHidden(false)
|
||||
} else {
|
||||
updateRecents(withRecents: newContext.recentURLs!, currentContext: newContext)
|
||||
|
||||
table.setHidden(false)
|
||||
infoLabel.setHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRecents(withRecents recents: NSArray, currentContext: JitsiMeetContext) {
|
||||
@@ -68,7 +82,7 @@ class InterfaceController: WKInterfaceController {
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String, in table: WKInterfaceTable, rowIndex: Int) -> Any? {
|
||||
let controller = table.rowController(at: rowIndex) as! MeetingRowController
|
||||
let currentContext = ExtensionDelegate.currentJitsiMeetContext
|
||||
|
||||
|
||||
// Copy the current context and add the joinConferenceURL to trigger the command when the in-call screen is displayed
|
||||
let actionContext = JitsiMeetContext(jmContext: currentContext)
|
||||
actionContext.joinConferenceURL = controller.roomUrl
|
||||
|
||||
@@ -7,6 +7,7 @@ PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
|
||||
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
|
||||
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
@@ -25,7 +26,9 @@ popd
|
||||
pushd ${PROJECT_REPO}
|
||||
rm -rf ios/sdk/JitsiMeet.framework
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
fi
|
||||
popd
|
||||
|
||||
pushd ${RELEASE_REPO}
|
||||
@@ -39,9 +42,11 @@ xcrun bitcode_strip -r Frameworks/JitsiMeet.framework/JitsiMeet -o Frameworks/Ji
|
||||
xcrun bitcode_strip -r Frameworks/WebRTC.framework/WebRTC -o Frameworks/WebRTC.framework/WebRTC
|
||||
|
||||
# Add all files to git
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
git tag ${SDK_VERSION}
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
git tag ${SDK_VERSION}
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */; };
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAFA778229EAD520033A7FA /* RNRootView.m */; };
|
||||
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */; };
|
||||
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535321FB1BF800011A3A /* JitsiMeet.m */; };
|
||||
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535521FB2E8300011A3A /* ReactUtils.m */; };
|
||||
@@ -96,6 +97,8 @@
|
||||
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetConferenceOptions.h; sourceTree = "<group>"; };
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetConferenceOptions.m; sourceTree = "<group>"; };
|
||||
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetConferenceOptions+Private.h"; sourceTree = "<group>"; };
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNRootView.h; sourceTree = "<group>"; };
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNRootView.m; sourceTree = "<group>"; };
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocaleDetector.m; sourceTree = "<group>"; };
|
||||
DEFE535321FB1BF800011A3A /* JitsiMeet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeet.m; sourceTree = "<group>"; };
|
||||
DEFE535521FB2E8300011A3A /* ReactUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactUtils.m; sourceTree = "<group>"; };
|
||||
@@ -173,6 +176,8 @@
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */,
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
|
||||
@@ -478,6 +483,7 @@
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */,
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
@@ -634,7 +640,7 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = src/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
#import "JitsiMeet.h"
|
||||
|
||||
@interface JitsiMeet ()
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
* Room name.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
@@ -56,7 +60,9 @@
|
||||
@interface JitsiMeetConferenceOptions : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable, readonly) NSURL *serverURL;
|
||||
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *room;
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *subject;
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *token;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
if (self = [super init]) {
|
||||
_serverURL = nil;
|
||||
_room = nil;
|
||||
_subject = nil;
|
||||
_token = nil;
|
||||
|
||||
_colorScheme = nil;
|
||||
@@ -138,6 +139,7 @@
|
||||
if (self = [super init]) {
|
||||
_serverURL = builder.serverURL;
|
||||
_room = builder.room;
|
||||
_subject = builder.subject;
|
||||
_token = builder.token;
|
||||
|
||||
_colorScheme = builder.colorScheme;
|
||||
@@ -183,6 +185,9 @@
|
||||
if (_videoMuted != nil) {
|
||||
config[@"startWithVideoMuted"] = @(self.videoMuted);
|
||||
}
|
||||
if (_subject != nil) {
|
||||
config[@"subject"] = self.subject;
|
||||
}
|
||||
|
||||
NSMutableDictionary *urlProps = [[NSMutableDictionary alloc] init];
|
||||
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
@@ -36,7 +35,7 @@
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RCTRootView *rootView;
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,9 +144,9 @@ static void initializeViewsMap() {
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView
|
||||
= [[RCTRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
= [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
|
||||
20
ios/sdk/src/RNRootView.h
Normal file
20
ios/sdk/src/RNRootView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
@interface RNRootView : RCTRootView
|
||||
@end
|
||||
45
ios/sdk/src/RNRootView.m
Normal file
45
ios/sdk/src/RNRootView.m
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTRootContentView.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RNRootView.h"
|
||||
|
||||
@implementation RNRootView
|
||||
|
||||
// Monkey-patch RCTRootView.runApplication to avoid logging initial props.
|
||||
- (void)runApplication:(RCTBridge *)bridge
|
||||
{
|
||||
NSString *moduleName = [self valueForKey:@"_moduleName"] ?: @"";
|
||||
RCTRootContentView *_contentView = [self valueForKey:@"_contentView"];
|
||||
NSNumber *reactTag = [_contentView valueForKey:@"reactTag"];
|
||||
|
||||
NSDictionary *appParameters = @{
|
||||
@"rootTag": reactTag,
|
||||
@"initialProps": self.appProperties ?: @{},
|
||||
};
|
||||
#if DEBUG
|
||||
RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
|
||||
#endif
|
||||
|
||||
[bridge enqueueJSCall:@"AppRegistry"
|
||||
method:@"runApplication"
|
||||
args:@[moduleName, appParameters]
|
||||
completion:NULL];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -33,6 +33,10 @@ RCT_EXPORT_METHOD(init:(NSString*)instanceName API_KEY:(NSString*)apiKey) {
|
||||
[[Amplitude instanceWithName:instanceName] initializeApiKey:apiKey];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setUserId:(NSString*)instanceName userId: (NSString *) userId) {
|
||||
[[Amplitude instanceWithName:instanceName] setUserId:userId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setUserProperties:(NSString*)instanceName userPropsString:(NSDictionary*)userProps) {
|
||||
if (userProps != nil) {
|
||||
[[Amplitude instanceWithName:instanceName] setUserProperties:userProps];
|
||||
|
||||
@@ -21,6 +21,7 @@ import Foundation
|
||||
internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
|
||||
private let listeners = NSMutableArray()
|
||||
private var pendingMuteActions = Set<UUID>()
|
||||
|
||||
internal override init() {}
|
||||
|
||||
@@ -36,6 +37,12 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
// MARK: - Add mute action
|
||||
|
||||
func addMuteAction(_ actionUUID: UUID) {
|
||||
pendingMuteActions.insert(actionUUID)
|
||||
}
|
||||
|
||||
// MARK: - CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
@@ -43,6 +50,7 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.providerDidReset?()
|
||||
}
|
||||
pendingMuteActions.removeAll()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
@@ -64,9 +72,20 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
let uuid = pendingMuteActions.remove(action.uuid)
|
||||
|
||||
// Avoid mute actions ping-pong: if the mute action was caused by
|
||||
// the JS side (we requested a transaction) don't call the delegate
|
||||
// method. If it was called by the provder itself (when the user presses
|
||||
// the mute button in the CallKit view) then call the delegate method.
|
||||
//
|
||||
// NOTE: don't try to be clever and remove this. Been there, done that.
|
||||
// Won't work.
|
||||
if (uuid == nil) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
action.fulfill()
|
||||
|
||||
@@ -160,6 +160,14 @@ import Foundation
|
||||
completion: @escaping (Error?) -> Swift.Void) {
|
||||
guard enabled else { return }
|
||||
|
||||
// XXX keep track of muted actions to avoid "ping-pong"ing. See
|
||||
// JMCallKitEmitter for details on the CXSetMutedCallAction handling.
|
||||
for action in transaction.actions {
|
||||
if (action as? CXSetMutedCallAction) != nil {
|
||||
emitter.addMuteAction(action.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
callController.request(transaction, completion: completion)
|
||||
}
|
||||
|
||||
@@ -187,3 +195,4 @@ import Foundation
|
||||
return update
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Anglés",
|
||||
"af": "Afrikaans",
|
||||
"az": "Azèri",
|
||||
"bg": "Bulgar",
|
||||
"cs": "Chèc",
|
||||
"de": "Aleman",
|
||||
"el": "Grèc",
|
||||
"eo": "Esperanto",
|
||||
"es": "Castelhan",
|
||||
"fr": "Francés",
|
||||
"hy": "Armenian",
|
||||
"it": "Italian",
|
||||
"ja": "Japonés",
|
||||
"ko": "Corean",
|
||||
"nb": "Norvegian Bokmål",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polonés",
|
||||
"ptBR": "Portugués (Brasil)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Eslovèn",
|
||||
"sv": "Suedés",
|
||||
"tr": "Turc",
|
||||
"zhCN": "Chinés (China)",
|
||||
"nb": "Norvegian Bokmål",
|
||||
"eo": "Esperanto"
|
||||
"vi": "Vietnamian",
|
||||
"zhCN": "Chinés (China)"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"en": "Inglês",
|
||||
"af": "Africâner",
|
||||
"az": "Azerbaijanês",
|
||||
"bg": "Búlgaro",
|
||||
"cs": "Checo",
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Tiếng Anh",
|
||||
"af": "Tiếng Afrika",
|
||||
"az": "Tiếng Azecbaizan",
|
||||
"bg": "Tiếng Bulgaria",
|
||||
"cs": "Tiếng Séc",
|
||||
"de": "Tiếng Đức",
|
||||
"el": "Tiếng Nhật",
|
||||
"eo": "Tiếng Esperanto",
|
||||
"es": "Tiếng Tây Ban Nha",
|
||||
"fr": "Tiếng Pháp",
|
||||
"hy": "Tiếng Acmenia",
|
||||
"it": "Tiếng Ý",
|
||||
"ja": "Tiếng Nhật",
|
||||
"ko": "Tiếng Hàn",
|
||||
"nb": "Tiếng Na Uy",
|
||||
"oc": "Tiếng Occitan",
|
||||
"pl": "Tiếng Ba Lan",
|
||||
"ptBR": "Tiếng Bồ Đào Nha (Brazil)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Tiếng Slovenia",
|
||||
"sv": "Tiếng Thụy Điển",
|
||||
"tr": "Tiếng Thổ Nhĩ Kỳ",
|
||||
"zhCN": "Tiếng Hoa (Trung Quốc)",
|
||||
"nb": "Tiếng Na Uy",
|
||||
"eo": "Tiếng Esperanto"
|
||||
"vi": "Tiếng Việt",
|
||||
"zhCN": "Tiếng Hoa (Trung Quốc)"
|
||||
}
|
||||
1185
lang/main-oc.json
1185
lang/main-oc.json
File diff suppressed because it is too large
Load Diff
1260
lang/main-ptBR.json
1260
lang/main-ptBR.json
File diff suppressed because it is too large
Load Diff
1204
lang/main-vi.json
1204
lang/main-vi.json
File diff suppressed because it is too large
Load Diff
@@ -478,6 +478,8 @@
|
||||
"mutedTitle": "You're muted!",
|
||||
"raisedHand": "__name__ would like to speak.",
|
||||
"somebody": "Somebody",
|
||||
"startSilentTitle": "You joined with no audio output!",
|
||||
"startSilentDescription": "Rejoin the meeting to enable audio",
|
||||
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
|
||||
"suboptimalExperienceTitle": "Browser Warning",
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
|
||||
@@ -103,9 +103,9 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(toggleTileView());
|
||||
},
|
||||
'video-hangup': () => {
|
||||
'video-hangup': (showFeedbackDialog = true) => {
|
||||
sendAnalytics(createApiEvent('video.hangup'));
|
||||
APP.conference.hangup(true);
|
||||
APP.conference.hangup(showFeedbackDialog);
|
||||
},
|
||||
'email': email => {
|
||||
sendAnalytics(createApiEvent('email.changed'));
|
||||
@@ -559,6 +559,38 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of an unexpected camera-related error having
|
||||
* occurred.
|
||||
*
|
||||
* @param {string} type - The type of the camera error.
|
||||
* @param {string} message - Additional information about the error.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnCameraError(type: string, message: string) {
|
||||
this._sendEvent({
|
||||
name: 'camera-error',
|
||||
type,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of an unexpected mic-related error having
|
||||
* occurred.
|
||||
*
|
||||
* @param {string} type - The type of the mic error.
|
||||
* @param {string} message - Additional information about the error.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnMicError(type: string, message: string) {
|
||||
this._sendEvent({
|
||||
name: 'mic-error',
|
||||
type,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that conference feedback
|
||||
* has been submitted. Intended to be used in conjunction with the
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -51,6 +51,7 @@ const events = {
|
||||
'avatar-changed': 'avatarChanged',
|
||||
'audio-availability-changed': 'audioAvailabilityChanged',
|
||||
'audio-mute-status-changed': 'audioMuteStatusChanged',
|
||||
'camera-error': 'cameraError',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
'email-change': 'emailChange',
|
||||
@@ -58,6 +59,7 @@ const events = {
|
||||
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
|
||||
'filmstrip-display-changed': 'filmstripDisplayChanged',
|
||||
'incoming-message': 'incomingMessage',
|
||||
'mic-error': 'micError',
|
||||
'outgoing-message': 'outgoingMessage',
|
||||
'participant-joined': 'participantJoined',
|
||||
'participant-left': 'participantLeft',
|
||||
|
||||
104
modules/UI/UI.js
104
modules/UI/UI.js
@@ -13,16 +13,12 @@ import SharedVideoManager from './shared_video/SharedVideo';
|
||||
import VideoLayout from './videolayout/VideoLayout';
|
||||
import Filmstrip from './videolayout/Filmstrip';
|
||||
|
||||
import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../react/features/base/participants';
|
||||
import { toggleChat } from '../../react/features/chat';
|
||||
import { openDisplayNamePrompt } from '../../react/features/display-name';
|
||||
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
|
||||
import { setFilmstripVisible } from '../../react/features/filmstrip';
|
||||
import {
|
||||
setNotificationsEnabled,
|
||||
showWarningNotification
|
||||
} from '../../react/features/notifications';
|
||||
import { setNotificationsEnabled } from '../../react/features/notifications';
|
||||
import {
|
||||
dockToolbox,
|
||||
setToolboxEnabled,
|
||||
@@ -40,39 +36,6 @@ UI.eventEmitter = eventEmitter;
|
||||
let etherpadManager;
|
||||
let sharedVideoManager;
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {},
|
||||
camera: {}
|
||||
};
|
||||
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]
|
||||
= 'dialog.cameraUnsupportedResolutionError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.GENERAL]
|
||||
= 'dialog.cameraUnknownError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.PERMISSION_DENIED]
|
||||
= 'dialog.cameraPermissionDeniedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.NOT_FOUND]
|
||||
= 'dialog.cameraNotFoundError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.CONSTRAINT_FAILED]
|
||||
= 'dialog.cameraConstraintFailedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= 'dialog.cameraNotSendingData';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.GENERAL]
|
||||
= 'dialog.micUnknownError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.PERMISSION_DENIED]
|
||||
= 'dialog.micPermissionDeniedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.NOT_FOUND]
|
||||
= 'dialog.micNotFoundError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.CONSTRAINT_FAILED]
|
||||
= 'dialog.micConstraintFailedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= 'dialog.micNotSendingData';
|
||||
|
||||
const UIListeners = new Map([
|
||||
[
|
||||
UIEvents.ETHERPAD_CLICKED,
|
||||
@@ -295,12 +258,6 @@ UI.addLocalStream = track => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removed remote stream from UI.
|
||||
* @param {JitsiTrack} track stream to remove
|
||||
*/
|
||||
UI.removeRemoteStream = track => VideoLayout.onRemoteStreamRemoved(track);
|
||||
|
||||
/**
|
||||
* Setup and show Etherpad.
|
||||
* @param {string} name etherpad id
|
||||
@@ -774,65 +731,6 @@ UI.showExtensionInlineInstallationDialog = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a notifications about the passed in microphone error.
|
||||
*
|
||||
* @param {JitsiTrackError} micError - An error object related to using or
|
||||
* acquiring an audio stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.showMicErrorNotification = function(micError) {
|
||||
if (!micError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message, name } = micError;
|
||||
|
||||
const micJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[name];
|
||||
const micErrorMsg = micJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.GENERAL];
|
||||
const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
APP.store.dispatch(showWarningNotification({
|
||||
description: additionalMicErrorMsg,
|
||||
descriptionKey: micErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.microphonePermission'
|
||||
: 'deviceError.microphoneError'
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a notifications about the passed in camera error.
|
||||
*
|
||||
* @param {JitsiTrackError} cameraError - An error object related to using or
|
||||
* acquiring a video stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.showCameraErrorNotification = function(cameraError) {
|
||||
if (!cameraError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message, name } = cameraError;
|
||||
|
||||
const cameraJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
|
||||
const cameraErrorMsg = cameraJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.GENERAL];
|
||||
const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
APP.store.dispatch(showWarningNotification({
|
||||
description: additionalCameraErrorMsg,
|
||||
descriptionKey: cameraErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.cameraPermission' : 'deviceError.cameraError'
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows error dialog that informs the user that no data is received from the
|
||||
* device.
|
||||
|
||||
@@ -361,6 +361,12 @@ const Filmstrip = {
|
||||
'min-width': `${local.thumbWidth}px`,
|
||||
width: `${local.thumbWidth}px`
|
||||
});
|
||||
|
||||
const avatarSize = local.thumbHeight / 2;
|
||||
|
||||
thumbs.localThumb.find('.avatar-container')
|
||||
.height(avatarSize)
|
||||
.width(avatarSize);
|
||||
}
|
||||
|
||||
if (thumbs.remoteThumbs) {
|
||||
@@ -371,6 +377,12 @@ const Filmstrip = {
|
||||
'min-width': `${remote.thumbWidth}px`,
|
||||
width: `${remote.thumbWidth}px`
|
||||
});
|
||||
|
||||
const avatarSize = remote.thumbHeight / 2;
|
||||
|
||||
thumbs.remoteThumbs.find('.avatar-container')
|
||||
.height(avatarSize)
|
||||
.width(avatarSize);
|
||||
}
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
|
||||
@@ -32,7 +32,7 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
||||
|
||||
this.localVideoId = null;
|
||||
this.bindHoverHandler();
|
||||
if (config.enableLocalVideoFlip) {
|
||||
if (!config.disableLocalVideoFlip) {
|
||||
this._buildContextMenu();
|
||||
}
|
||||
this.isLocal = true;
|
||||
|
||||
@@ -357,6 +357,11 @@ RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
|
||||
logger.info(`${isVideo ? 'Video' : 'Audio'
|
||||
} removed ${this.id}`, select);
|
||||
|
||||
|
||||
if (stream === this.videoStream) {
|
||||
this.videoStream = null;
|
||||
}
|
||||
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
/* global $, APP, config, interfaceConfig */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
@@ -199,6 +199,8 @@ SmallVideo.createStreamElement = function(stream) {
|
||||
|
||||
if (isVideo) {
|
||||
element.setAttribute('muted', 'true');
|
||||
} else if (config.startSilent) {
|
||||
element.muted = true;
|
||||
}
|
||||
|
||||
element.autoplay = true;
|
||||
|
||||
@@ -348,10 +348,6 @@ const VideoLayout = {
|
||||
remoteVideo.removeRemoteStreamElement(stream);
|
||||
}
|
||||
|
||||
if (stream.isVideoTrack()) {
|
||||
this._updateLargeVideoIfDisplayed(id);
|
||||
}
|
||||
|
||||
this.updateMutedForNoTracks(id, stream.getType());
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
|
||||
import { getAudioOutputDeviceId } from '../../react/features/base/devices';
|
||||
import {
|
||||
getAudioOutputDeviceId,
|
||||
notifyCameraError,
|
||||
notifyMicError
|
||||
} from '../../react/features/base/devices';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId,
|
||||
@@ -176,11 +180,11 @@ export default {
|
||||
]))
|
||||
.then(tracks => {
|
||||
if (audioTrackError) {
|
||||
APP.UI.showMicErrorNotification(audioTrackError);
|
||||
APP.store.dispatch(notifyMicError(audioTrackError));
|
||||
}
|
||||
|
||||
if (videoTrackError) {
|
||||
APP.UI.showCameraErrorNotification(videoTrackError);
|
||||
APP.store.dispatch(notifyCameraError(videoTrackError));
|
||||
}
|
||||
|
||||
return tracks.filter(t => typeof t !== 'undefined');
|
||||
@@ -205,7 +209,7 @@ export default {
|
||||
})
|
||||
.catch(err => {
|
||||
audioTrackError = err;
|
||||
showError && APP.UI.showMicErrorNotification(err);
|
||||
showError && APP.store.disptach(notifyMicError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
@@ -223,7 +227,7 @@ export default {
|
||||
})
|
||||
.catch(err => {
|
||||
videoTrackError = err;
|
||||
showError && APP.UI.showCameraErrorNotification(err);
|
||||
showError && APP.store.dispatch(notifyCameraError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -8927,8 +8927,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"version": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.1",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.11",
|
||||
"moment": "2.19.4",
|
||||
|
||||
@@ -372,6 +372,28 @@ export function createLiveStreamingDialogEvent(dialogName, buttonName) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event with the local tracks duration.
|
||||
*
|
||||
* @param {Object} duration - The object with the duration of the local tracks.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createLocalTracksDurationEvent(duration) {
|
||||
const { audio, video, conference } = duration;
|
||||
const { camera, desktop } = video;
|
||||
|
||||
return {
|
||||
action: 'local.tracks.durations',
|
||||
attributes: {
|
||||
audio: audio.value,
|
||||
camera: camera.value,
|
||||
conference: conference.value,
|
||||
desktop: desktop.value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that an action related to recording has
|
||||
* occured.
|
||||
|
||||
11
react/features/analytics/actionTypes.js
Normal file
11
react/features/analytics/actionTypes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that local media duration has changed.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
* localTracksDuration: Object
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_LOCAL_TRACKS_DURATION = 'UPDATE_LOCAL_TRACKS_DURATION';
|
||||
@@ -15,7 +15,7 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
const { amplitudeAPPKey, host } = options;
|
||||
const { amplitudeAPPKey, host, user } = options;
|
||||
|
||||
if (!amplitudeAPPKey) {
|
||||
throw new Error('Failed to initialize Amplitude handler, no APP key');
|
||||
@@ -28,6 +28,10 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
};
|
||||
|
||||
amplitude.getInstance(this._amplitudeOptions).init(amplitudeAPPKey);
|
||||
|
||||
if (user) {
|
||||
amplitude.getInstance(this._amplitudeOptions).setUserId(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,16 @@ class Amplitude {
|
||||
AmplitudeNative.init(this._instanceName, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an identifier for the current user.
|
||||
*
|
||||
* @param {string} userId - The new user id.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserId(userId) {
|
||||
AmplitudeNative.setUserId(this._instanceName, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user properties for the current user.
|
||||
*
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './AnalyticsEvents';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
import { SET_ROOM } from '../base/conference';
|
||||
// @flow
|
||||
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
SET_ROOM
|
||||
} from '../base/conference';
|
||||
import { SET_CONFIG } from '../base/config';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import {
|
||||
getLocalAudioTrack,
|
||||
getLocalVideoTrack,
|
||||
TRACK_ADDED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED
|
||||
} from '../base/tracks';
|
||||
|
||||
import { initAnalytics, resetAnalytics } from './functions';
|
||||
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
|
||||
import { createLocalTracksDurationEvent } from './AnalyticsEvents';
|
||||
import { initAnalytics, resetAnalytics, sendAnalytics } from './functions';
|
||||
|
||||
/**
|
||||
* Calculates the duration of the local tracks.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object} - The local tracks duration.
|
||||
*/
|
||||
function calculateLocalTrackDuration(state) {
|
||||
const now = Date.now();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { audio, video } = localTracksDuration;
|
||||
const { camera, desktop } = video;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const audioTrack = getLocalAudioTrack(tracks);
|
||||
const videoTrack = getLocalVideoTrack(tracks);
|
||||
const newDuration = { ...localTracksDuration };
|
||||
|
||||
if (!audioTrack || audioTrack.muted || !conference) {
|
||||
newDuration.audio = {
|
||||
startedTime: -1,
|
||||
value: audio.value + (audio.startedTime === -1 ? 0 : now - audio.startedTime)
|
||||
};
|
||||
} else if (audio.startedTime === -1) {
|
||||
newDuration.audio.startedTime = now;
|
||||
}
|
||||
|
||||
if (!videoTrack || videoTrack.muted || !conference) {
|
||||
newDuration.video = {
|
||||
camera: {
|
||||
startedTime: -1,
|
||||
value: camera.value + (camera.startedTime === -1 ? 0 : now - camera.startedTime)
|
||||
},
|
||||
desktop: {
|
||||
startedTime: -1,
|
||||
value: desktop.value + (desktop.startedTime === -1 ? 0 : now - desktop.startedTime)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const { videoType } = videoTrack;
|
||||
|
||||
if (video[videoType].startedTime === -1) {
|
||||
newDuration.video[videoType].startedTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...localTracksDuration,
|
||||
...newDuration
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware which intercepts config actions to handle evaluating analytics
|
||||
@@ -12,25 +78,81 @@ import { initAnalytics, resetAnalytics } from './functions';
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_CONFIG: {
|
||||
if (action.type === SET_CONFIG) {
|
||||
if (navigator.product === 'ReactNative') {
|
||||
// Reseting the analytics is currently not needed for web because
|
||||
// the user will be redirected to another page and new instance of
|
||||
// Analytics will be created and initialized.
|
||||
resetAnalytics();
|
||||
}
|
||||
}
|
||||
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: {
|
||||
...calculateLocalTrackDuration(state),
|
||||
conference: {
|
||||
startedTime: Date.now(),
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_WILL_LEAVE: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
const newLocalTracksDuration = {
|
||||
...calculateLocalTrackDuration(state),
|
||||
conference: {
|
||||
startedTime: -1,
|
||||
value: Date.now() - localTracksDuration.conference.startedTime
|
||||
}
|
||||
};
|
||||
|
||||
sendAnalytics(createLocalTracksDurationEvent(newLocalTracksDuration));
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: newLocalTracksDuration
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SET_ROOM: {
|
||||
const result = next(action);
|
||||
|
||||
initAnalytics(store);
|
||||
break;
|
||||
}
|
||||
case TRACK_ADDED:
|
||||
case TRACK_REMOVED:
|
||||
case TRACK_UPDATED: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
|
||||
return result;
|
||||
if (localTracksDuration.conference.startedTime === -1) {
|
||||
// We don't want to track the media duration if the conference is not joined yet because otherwise we won't
|
||||
// be able to compare them with the conference duration (from conference join to conference will leave).
|
||||
break;
|
||||
}
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: {
|
||||
...localTracksDuration,
|
||||
...calculateLocalTrackDuration(state)
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
return result;
|
||||
});
|
||||
|
||||
51
react/features/analytics/reducer.js
Normal file
51
react/features/analytics/reducer.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Initial state.
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
localTracksDuration: {
|
||||
audio: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
},
|
||||
video: {
|
||||
camera: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
},
|
||||
desktop: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
conference: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of the analytics feature.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature features/analytics.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/analytics', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_LOCAL_TRACKS_DURATION:
|
||||
return {
|
||||
...state,
|
||||
localTracksDuration: action.localTracksDuration
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
setConfig,
|
||||
storeConfig
|
||||
} from '../base/config';
|
||||
import { setLocationURL } from '../base/connection';
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
import { createDesiredLocalTracks } from '../base/tracks';
|
||||
import { parseURIString, toURLString } from '../base/util';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
@@ -58,6 +59,12 @@ export function appNavigate(uri: ?string) {
|
||||
const { contextRoot, host, room } = location;
|
||||
const locationURL = new URL(location.toString());
|
||||
|
||||
// Disconnect from any current conference.
|
||||
// FIXME: unify with web.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
dispatch(disconnect());
|
||||
}
|
||||
|
||||
dispatch(configWillLoad(locationURL, room));
|
||||
|
||||
let protocol = location.protocol.toLowerCase();
|
||||
@@ -74,25 +81,40 @@ export function appNavigate(uri: ?string) {
|
||||
|
||||
let config;
|
||||
|
||||
try {
|
||||
config = await loadConfig(url);
|
||||
dispatch(storeConfig(baseURL, config));
|
||||
} catch (error) {
|
||||
// Avoid (re)loading the config when there is no room.
|
||||
if (!room) {
|
||||
config = restoreConfig(baseURL);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
dispatch(loadConfigError(error, locationURL));
|
||||
if (!config) {
|
||||
try {
|
||||
config = await loadConfig(url);
|
||||
dispatch(storeConfig(baseURL, config));
|
||||
} catch (error) {
|
||||
config = restoreConfig(baseURL);
|
||||
|
||||
return;
|
||||
if (!config) {
|
||||
dispatch(loadConfigError(error, locationURL));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getState()['features/base/config'].locationURL === locationURL) {
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
} else {
|
||||
if (getState()['features/base/config'].locationURL !== locationURL) {
|
||||
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
|
||||
// FIXME: unify with web, currently the connection and track creation happens in conference.js.
|
||||
if (room && navigator.product === 'ReactNative') {
|
||||
dispatch(createDesiredLocalTracks());
|
||||
dispatch(connect());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import React from 'react';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/responsive-ui';
|
||||
import '../../chat';
|
||||
import '../../external-api';
|
||||
import '../../room-lock';
|
||||
import '../../video-layout';
|
||||
|
||||
|
||||
@@ -64,12 +64,14 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelKey = 'dialog.Cancel'
|
||||
contentKey = {
|
||||
{
|
||||
key: 'dialog.WaitForHostMsgWOk',
|
||||
params: { room }
|
||||
}
|
||||
}
|
||||
okKey = 'dialog.Ok'
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onLogin } />
|
||||
);
|
||||
|
||||
@@ -84,6 +84,8 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args) => dispatch(conferenceLeft(conference, ...args)));
|
||||
conference.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
||||
(...args) => dispatch(conferenceSubjectChanged(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.KICKED,
|
||||
@@ -757,10 +759,6 @@ export function setSubject(subject: string = '') {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
if (conference) {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject: undefined
|
||||
});
|
||||
conference.setSubject(subject);
|
||||
} else {
|
||||
dispatch({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
@@ -155,10 +157,10 @@ export function forEachConference(
|
||||
export function getConferenceName(stateful: Function | Object): string {
|
||||
const state = toState(stateful);
|
||||
const { callee } = state['features/base/jwt'];
|
||||
const { callDisplayName } = state['features/base/config'];
|
||||
const { pendingSubjectChange, room, subject } = state['features/base/conference'];
|
||||
|
||||
return state['features/base/config'].callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| state['features/base/conference'].room;
|
||||
return pendingSubjectChange || subject || callDisplayName || (callee && callee.name) || _.startCase(room);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,6 @@ import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
|
||||
|
||||
import {
|
||||
conferenceFailed,
|
||||
conferenceLeft,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
setLastN,
|
||||
@@ -38,6 +37,7 @@ import {
|
||||
DATA_CHANNEL_OPENED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_LASTN,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
import {
|
||||
@@ -329,10 +329,17 @@ function _connectionFailed({ dispatch, getState }, next, action) {
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceSubjectChanged({ getState }, next, action) {
|
||||
function _conferenceSubjectChanged({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
const { subject } = getState()['features/base/conference'];
|
||||
|
||||
if (subject) {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject: undefined
|
||||
});
|
||||
}
|
||||
|
||||
typeof APP === 'object' && APP.API.notifySubjectChanged(subject);
|
||||
|
||||
return result;
|
||||
@@ -567,48 +574,30 @@ function _setReceiverVideoConstraint(conference, preferred, max) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature {@code base/conference} that the redix action
|
||||
* {@link SET_ROOM} is being dispatched within a specific redux store.
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code SET_ROOM} is being dispatched within a specific
|
||||
* redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code SET_ROOM} which is being
|
||||
* dispatched in the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code SET_ROOM}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setRoom({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
// By the time SET_ROOM is dispatched, base/connection's locationURL and
|
||||
// base/conference's leaving should be the only conference-related sources
|
||||
// of truth.
|
||||
const state = getState();
|
||||
const { leaving } = state['features/base/conference'];
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const dispatchConferenceLeft = new Set();
|
||||
const { subject } = state['features/base/config'];
|
||||
const { room } = action;
|
||||
|
||||
// Figure out which of the JitsiConferences referenced by base/conference
|
||||
// have not dispatched or are not likely to dispatch CONFERENCE_FAILED and
|
||||
// CONFERENCE_LEFT.
|
||||
forEachConference(state, (conference, url) => {
|
||||
if (conference !== leaving && url && url !== locationURL) {
|
||||
dispatchConferenceLeft.add(conference);
|
||||
}
|
||||
|
||||
return true; // All JitsiConference instances are to be examined.
|
||||
});
|
||||
|
||||
// Dispatch CONFERENCE_LEFT for the JitsiConferences referenced by
|
||||
// base/conference which have not dispatched or are not likely to dispatch
|
||||
// CONFERENCE_FAILED or CONFERENCE_LEFT.
|
||||
for (const conference of dispatchConferenceLeft) {
|
||||
dispatch(conferenceLeft(conference));
|
||||
if (room) {
|
||||
// Set the stored subject.
|
||||
dispatch(setSubject(subject));
|
||||
}
|
||||
|
||||
return result;
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ const WHITELISTED_KEYS = [
|
||||
'_peerConnStatusOutOfLastNTimeout',
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'abTesting',
|
||||
'analytics.disabled',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
@@ -101,7 +102,7 @@ const WHITELISTED_KEYS = [
|
||||
'enableDisplayNameInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'enableLocalVideoFlip',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
@@ -131,10 +132,12 @@ const WHITELISTED_KEYS = [
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
'useNicks',
|
||||
|
||||
@@ -370,10 +370,7 @@ export function disconnect() {
|
||||
if (connection_) {
|
||||
promise = promise.then(() => connection_.disconnect());
|
||||
} else {
|
||||
// FIXME: We have no connection! Fake a disconnect. Because of how the current disconnec is implemented
|
||||
// (by doing the diconnect() in the Conference component unmount) we have lost the location URL already.
|
||||
// Oh well, at least send the event.
|
||||
promise.then(() => dispatch(_connectionDisconnected({}, '')));
|
||||
logger.info('No connection found while disconnecting.');
|
||||
}
|
||||
|
||||
return promise;
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
/**
|
||||
* The type of Redux action which signals that an error occurred while obtaining
|
||||
* a camera.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFY_CAMERA_ERROR,
|
||||
* error: Object
|
||||
* }
|
||||
*/
|
||||
export const NOTIFY_CAMERA_ERROR = 'NOTIFY_CAMERA_ERROR';
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that an error occurred while obtaining
|
||||
* a microphone.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFY_MIC_ERROR,
|
||||
* error: Object
|
||||
* }
|
||||
*/
|
||||
export const NOTIFY_MIC_ERROR = 'NOTIFY_MIC_ERROR';
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that the currently used audio
|
||||
* input device should be changed.
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import {
|
||||
ADD_PENDING_DEVICE_REQUEST,
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
NOTIFY_CAMERA_ERROR,
|
||||
NOTIFY_MIC_ERROR,
|
||||
REMOVE_PENDING_DEVICE_REQUESTS,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
@@ -148,6 +150,43 @@ export function getAvailableDevices() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that an error occurred while trying to obtain a track from a camera.
|
||||
*
|
||||
* @param {Object} error - The device error, as provided by lib-jitsi-meet.
|
||||
* @param {string} error.name - The constant for the type of the error.
|
||||
* @param {string} error.message - Optional additional information about the
|
||||
* error.
|
||||
* @returns {{
|
||||
* type: NOTIFY_CAMERA_ERROR,
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
export function notifyCameraError(error) {
|
||||
return {
|
||||
type: NOTIFY_CAMERA_ERROR,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that an error occurred while trying to obtain a track from a mic.
|
||||
*
|
||||
* @param {Object} error - The device error, as provided by lib-jitsi-meet.
|
||||
* @param {Object} error.name - The constant for the type of the error.
|
||||
* @param {string} error.message - Optional additional information about the
|
||||
* error.
|
||||
* @returns {{
|
||||
* type: NOTIFY_MIC_ERROR,
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
export function notifyMicError(error) {
|
||||
return {
|
||||
type: NOTIFY_MIC_ERROR,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all pending device requests.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CONFERENCE_JOINED } from '../conference';
|
||||
import { processExternalDeviceRequest } from '../../device-selection';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
|
||||
import {
|
||||
removePendingDeviceRequests,
|
||||
@@ -12,15 +13,35 @@ import {
|
||||
} from './actions';
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
NOTIFY_CAMERA_ERROR,
|
||||
NOTIFY_MIC_ERROR,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE
|
||||
} from './actionTypes';
|
||||
import { showNotification } from '../../notifications';
|
||||
import { showNotification, showWarningNotification } from '../../notifications';
|
||||
import { updateSettings } from '../settings';
|
||||
import { setAudioOutputDeviceId } from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.micNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError'
|
||||
},
|
||||
camera: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.cameraNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
|
||||
[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature base/devices.
|
||||
*
|
||||
@@ -32,6 +53,53 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
case NOTIFY_CAMERA_ERROR: {
|
||||
if (typeof APP !== 'object' || !action.error) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { message, name } = action.error;
|
||||
|
||||
const cameraJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
|
||||
const cameraErrorMsg = cameraJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.GENERAL];
|
||||
const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
store.dispatch(showWarningNotification({
|
||||
description: additionalCameraErrorMsg,
|
||||
descriptionKey: cameraErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.cameraPermission' : 'deviceError.cameraError'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case NOTIFY_MIC_ERROR: {
|
||||
if (typeof APP !== 'object' || !action.error) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { message, name } = action.error;
|
||||
|
||||
const micJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[name];
|
||||
const micErrorMsg = micJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.GENERAL];
|
||||
const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
store.dispatch(showWarningNotification({
|
||||
description: additionalMicErrorMsg,
|
||||
descriptionKey: micErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.microphonePermission'
|
||||
: 'deviceError.microphoneError'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_INPUT_DEVICE:
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent, type Node } from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
import { Platform, SafeAreaView, ScrollView, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||
import { SlidingView } from '../../../react';
|
||||
@@ -36,21 +36,6 @@ type Props = {
|
||||
* A component emulating Android's BottomSheet.
|
||||
*/
|
||||
class BottomSheet extends PureComponent<Props> {
|
||||
/**
|
||||
* Assembles a style for the BottomSheet container.
|
||||
*
|
||||
* @private
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
_getContainerStyle() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return {
|
||||
...styles.container,
|
||||
backgroundColor: _styles.sheet.backgroundColor
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -65,15 +50,46 @@ class BottomSheet extends PureComponent<Props> {
|
||||
onHide = { this.props.onCancel }
|
||||
position = 'bottom'
|
||||
show = { true }>
|
||||
<SafeAreaView
|
||||
style = { this._getContainerStyle() }>
|
||||
<View style = { _styles.sheet }>
|
||||
{ this.props.children }
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.sheetContainer }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.sheetAreaCover } />
|
||||
<View
|
||||
style = { [
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet
|
||||
] }>
|
||||
<ScrollView bounces = { false }>
|
||||
{ this._getWrappedContent() }
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</SlidingView>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the content when needed (iOS 11 and above), or just returns the original children.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_getWrappedContent() {
|
||||
if (Platform.OS === 'ios') {
|
||||
const majorVersionIOS = parseInt(Platform.Version, 10);
|
||||
|
||||
if (majorVersionIOS > 10) {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
{ this.props.children }
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,17 +27,28 @@ export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
|
||||
* been implemented as per the Material Design guidelines:
|
||||
* {@link https://material.io/guidelines/components/bottom-sheets.html}.
|
||||
*/
|
||||
export const bottomSheetStyles = createStyleSheet({
|
||||
export const bottomSheetStyles = {
|
||||
sheetAreaCover: {
|
||||
backgroundColor: ColorPalette.transparent,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the container of the sheet.
|
||||
*/
|
||||
container: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
sheetContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
|
||||
sheetItemContainer: {
|
||||
flex: -1,
|
||||
maxHeight: '60%',
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const brandedDialog = createStyleSheet({
|
||||
|
||||
@@ -137,10 +148,7 @@ ColorSchemeRegistry.register('BottomSheet', {
|
||||
* Bottom sheet's base style.
|
||||
*/
|
||||
sheet: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1,
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING,
|
||||
paddingVertical: 8
|
||||
backgroundColor: schemeColor('background')
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,9 @@ export function createLocalTrack(type: string, deviceId: string) {
|
||||
* otherwise.
|
||||
*/
|
||||
export function isAnalyticsEnabled(stateful: Function | Object) {
|
||||
return !toState(stateful)['features/base/config'].disableThirdPartyRequests;
|
||||
const { disableThirdPartyRequests, analytics = {} } = toState(stateful)['features/base/config'];
|
||||
|
||||
return !disableThirdPartyRequests && !analytics.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,6 +260,25 @@ function _getAllParticipants(stateful) {
|
||||
: toState(stateful)['features/base/participants'] || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all of the meeting participants are moderators.
|
||||
*
|
||||
* @param {Object|Function} stateful -Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEveryoneModerator(stateful: Object | Function) {
|
||||
const participants = _getAllParticipants(stateful);
|
||||
|
||||
for (const participant of participants) {
|
||||
if (participant.role !== PARTICIPANT_ROLE.MODERATOR) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current local participant is a moderator in the
|
||||
* conference.
|
||||
|
||||
@@ -119,12 +119,22 @@ function _initSettings(featureState) {
|
||||
let settings = featureState;
|
||||
|
||||
// Old Settings.js values
|
||||
// FIXME: Let's remove this after a predefined time (e.g. by July 2018) to
|
||||
// avoid garbage in the source.
|
||||
const displayName = _.escape(window.localStorage.getItem('displayname'));
|
||||
const email = _.escape(window.localStorage.getItem('email'));
|
||||
// FIXME: jibri uses old settings.js local storage values to set its display
|
||||
// name and email. Provide another way for jibri to set these values, update
|
||||
// jibri, and remove the old settings.js values.
|
||||
const savedDisplayName = window.localStorage.getItem('displayname');
|
||||
const savedEmail = window.localStorage.getItem('email');
|
||||
let avatarID = _.escape(window.localStorage.getItem('avatarId'));
|
||||
|
||||
// The helper _.escape will convert null to an empty strings. The empty
|
||||
// string will be saved in settings. On app re-load, because an empty string
|
||||
// is a defined value, it will override any value found in local storage.
|
||||
// The workaround is sidestepping _.escape when the value is not set in
|
||||
// local storage.
|
||||
const displayName
|
||||
= savedDisplayName === null ? undefined : _.escape(savedDisplayName);
|
||||
const email = savedEmail === null ? undefined : _.escape(savedEmail);
|
||||
|
||||
if (!avatarID) {
|
||||
// if there is no avatar id, we generate a unique one and use it forever
|
||||
avatarID = randomHexString(32);
|
||||
|
||||
@@ -15,7 +15,6 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { createLocalTracksA } from './actions';
|
||||
import {
|
||||
TOGGLE_SCREENSHARING,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import { getLocalTrack, setTrackMuted } from './functions';
|
||||
@@ -92,14 +91,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case TRACK_REMOVED:
|
||||
// TODO Remove this middleware case once all UI interested in tracks
|
||||
// being removed are converted to react and listening for store changes.
|
||||
if (typeof APP !== 'undefined' && !action.track.local) {
|
||||
APP.UI.removeRemoteStream(action.track.jitsiTrack);
|
||||
}
|
||||
break;
|
||||
|
||||
case TRACK_UPDATED:
|
||||
// TODO Remove the following calls to APP.UI once components interested
|
||||
// in track mute changes are moved into React and/or redux.
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BackHandler, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
|
||||
import { appNavigate } from '../../../app';
|
||||
import { connect, disconnect } from '../../../base/connection';
|
||||
import { getAppProp } from '../../../base/app';
|
||||
import { getParticipantCount } from '../../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||
import { connect as reactReduxConnect } from '../../../base/redux';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { createDesiredLocalTracks } from '../../../base/tracks';
|
||||
import { ConferenceNotification } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
import { DisplayNameLabel } from '../../../display-name';
|
||||
@@ -66,40 +64,6 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_largeVideoParticipantId: string,
|
||||
|
||||
/**
|
||||
* Current conference's full URL.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_locationURL: URL,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action connect.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onConnect: Function,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action disconnect.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDisconnect: Function,
|
||||
|
||||
/**
|
||||
* Handles a hardware button press for back navigation. Leaves the
|
||||
* associated {@code Conference}.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} As the associated conference is unconditionally left
|
||||
* and exiting the app while it renders a {@code Conference} is undesired,
|
||||
* {@code true} is always returned.
|
||||
*/
|
||||
_onHardwareBackPress: Function,
|
||||
|
||||
/**
|
||||
* The number of participants in the conference.
|
||||
*
|
||||
@@ -107,6 +71,13 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_participantCount: number,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_pictureInPictureEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to accommodate
|
||||
* smaller display areas).
|
||||
@@ -138,7 +109,12 @@ type Props = AbstractProps & {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toolboxAlwaysVisible: boolean
|
||||
_toolboxAlwaysVisible: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,6 +132,8 @@ class Conference extends AbstractConference<Props, *> {
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onHardwareBackPress = this._onHardwareBackPress.bind(this);
|
||||
this._setToolboxVisible = this._setToolboxVisible.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,17 +144,11 @@ class Conference extends AbstractConference<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.props._onConnect();
|
||||
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
this.props._onHardwareBackPress);
|
||||
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
|
||||
// Show the toolbox if we are the only participant; otherwise, the whole
|
||||
// UI looks too unpopulated the LargeVideo visible.
|
||||
const { _participantCount, _setToolboxVisible } = this.props;
|
||||
|
||||
_participantCount === 1 && _setToolboxVisible(true);
|
||||
this.props._participantCount === 1 && this._setToolboxVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,34 +156,23 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(pevProps: Props) {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {
|
||||
_locationURL: oldLocationURL,
|
||||
_participantCount: oldParticipantCount,
|
||||
_room: oldRoom
|
||||
} = pevProps;
|
||||
_participantCount: oldParticipantCount
|
||||
} = prevProps;
|
||||
const {
|
||||
_locationURL: newLocationURL,
|
||||
_participantCount: newParticipantCount,
|
||||
_room: newRoom,
|
||||
_setToolboxVisible,
|
||||
_toolboxVisible
|
||||
} = this.props;
|
||||
|
||||
// If the location URL changes we need to reconnect.
|
||||
oldLocationURL !== newLocationURL && newRoom && this.props._onDisconnect();
|
||||
|
||||
// Start the connection process when there is a (valid) room.
|
||||
oldRoom !== newRoom && newRoom && this.props._onConnect();
|
||||
|
||||
if (oldParticipantCount === 1
|
||||
&& newParticipantCount > 1
|
||||
&& _toolboxVisible) {
|
||||
_setToolboxVisible(false);
|
||||
this._setToolboxVisible(false);
|
||||
} else if (oldParticipantCount > 1
|
||||
&& newParticipantCount === 1
|
||||
&& !_toolboxVisible) {
|
||||
_setToolboxVisible(true);
|
||||
this._setToolboxVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +186,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
// Tear handling any hardware button presses for back navigation down.
|
||||
BackHandler.removeEventListener(
|
||||
'hardwareBackPress',
|
||||
this.props._onHardwareBackPress);
|
||||
|
||||
this.props._onDisconnect();
|
||||
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,9 +295,33 @@ class Conference extends AbstractConference<Props, *> {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolboxVisible = !this.props._toolboxVisible;
|
||||
this._setToolboxVisible(!this.props._toolboxVisible);
|
||||
}
|
||||
|
||||
this.props._setToolboxVisible(toolboxVisible);
|
||||
_onHardwareBackPress: () => boolean;
|
||||
|
||||
/**
|
||||
* Handles a hardware button press for back navigation. Enters Picture-in-Picture mode
|
||||
* (if supported) or leaves the associated {@code Conference} otherwise.
|
||||
*
|
||||
* @returns {boolean} Exiting the app is undesired, so {@code true} is always returned.
|
||||
*/
|
||||
_onHardwareBackPress() {
|
||||
let p;
|
||||
|
||||
if (this.props._pictureInPictureEnabled) {
|
||||
const { PictureInPicture } = NativeModules;
|
||||
|
||||
p = PictureInPicture.enterPictureInPicture();
|
||||
} else {
|
||||
p = Promise.reject(new Error('PiP not enabled'));
|
||||
}
|
||||
|
||||
p.catch(() => {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,70 +369,20 @@ class Conference extends AbstractConference<Props, *> {
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps dispatching of some action to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onConnect: Function,
|
||||
* _onDisconnect: Function,
|
||||
* _onHardwareBackPress: Function,
|
||||
* _setToolboxVisible: Function
|
||||
* }}
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
/**
|
||||
* Dispatches actions to create the desired local tracks and for
|
||||
* connecting to the conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onConnect() {
|
||||
dispatch(createDesiredLocalTracks());
|
||||
dispatch(connect());
|
||||
},
|
||||
_setToolboxVisible: (boolean) => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action disconnecting from the conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDisconnect() {
|
||||
dispatch(disconnect());
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a hardware button press for back navigation. Leaves the
|
||||
* associated {@code Conference}.
|
||||
*
|
||||
* @returns {boolean} As the associated conference is unconditionally
|
||||
* left and exiting the app while it renders a {@code Conference} is
|
||||
* undesired, {@code true} is always returned.
|
||||
*/
|
||||
_onHardwareBackPress() {
|
||||
dispatch(appNavigate(undefined));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action changing the visibility of the {@link Toolbox}.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} visible - Pass {@code true} to show the
|
||||
* {@code Toolbox} or {@code false} to hide it.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setToolboxVisible(visible) {
|
||||
dispatch(setToolboxVisible(visible));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Dispatches an action changing the visibility of the {@link Toolbox}.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} visible - Pass {@code true} to show the
|
||||
* {@code Toolbox} or {@code false} to hide it.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setToolboxVisible(visible) {
|
||||
this.props.dispatch(setToolboxVisible(visible));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,8 +393,7 @@ function _mapDispatchToProps(dispatch) {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { connecting, connection, locationURL }
|
||||
= state['features/base/connection'];
|
||||
const { connecting, connection } = state['features/base/connection'];
|
||||
const {
|
||||
conference,
|
||||
joining,
|
||||
@@ -508,14 +438,6 @@ function _mapStateToProps(state) {
|
||||
*/
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
|
||||
/**
|
||||
* Current conference's full URL.
|
||||
*
|
||||
* @private
|
||||
* @type {URL}
|
||||
*/
|
||||
_locationURL: locationURL,
|
||||
|
||||
/**
|
||||
* The number of participants in the conference.
|
||||
*
|
||||
@@ -524,6 +446,14 @@ function _mapStateToProps(state) {
|
||||
*/
|
||||
_participantCount: getParticipantCount(state),
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pictureInPictureEnabled: getAppProp(state, 'pictureInPictureEnabled'),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to
|
||||
* accommodate smaller display areas).
|
||||
@@ -551,5 +481,4 @@ function _mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default reactReduxConnect(_mapStateToProps, _mapDispatchToProps)(
|
||||
makeAspectRatioAware(Conference));
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Conference));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { SafeAreaView, Text, View } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
@@ -82,7 +81,7 @@ class NavigationBar extends Component<Props> {
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_meetingName: _.startCase(getConferenceName(state)),
|
||||
_meetingName: getConferenceName(state),
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._openDesktopApp = this._openDesktopApp.bind(this);
|
||||
this._onLaunchWeb = this._onLaunchWeb.bind(this);
|
||||
this._onTryAgain = this._onTryAgain.bind(this);
|
||||
}
|
||||
@@ -61,7 +60,6 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._openDesktopApp();
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
|
||||
@@ -133,17 +131,6 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
);
|
||||
}
|
||||
|
||||
_openDesktopApp: () => {}
|
||||
|
||||
/**
|
||||
* Dispatches the <tt>openDesktopApp</tt> action.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_openDesktopApp() {
|
||||
this.props.dispatch(openDesktopApp());
|
||||
}
|
||||
|
||||
_onTryAgain: () => {}
|
||||
|
||||
/**
|
||||
@@ -155,7 +142,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'tryAgainButton', { isMobileBrowser: false }));
|
||||
this._openDesktopApp();
|
||||
this.props.dispatch(openDesktopApp());
|
||||
}
|
||||
|
||||
_onLaunchWeb: () => {}
|
||||
|
||||
@@ -8,28 +8,7 @@ import {
|
||||
DeepLinkingMobilePage,
|
||||
NoMobileApp
|
||||
} from './components';
|
||||
import { _shouldShowDeepLinkingDesktopPage }
|
||||
from './shouldShowDeepLinkingDesktopPage';
|
||||
|
||||
/**
|
||||
* Promise that resolves when the window load event is received.
|
||||
*
|
||||
* @type {Promise<void>}
|
||||
*/
|
||||
const windowLoadedPromise = new Promise(resolve => {
|
||||
/**
|
||||
* Handler for the window load event.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onWindowLoad() {
|
||||
resolve();
|
||||
window.removeEventListener('load', onWindowLoad);
|
||||
}
|
||||
|
||||
window.addEventListener('load', onWindowLoad);
|
||||
});
|
||||
|
||||
import { _openDesktopApp } from './openDesktopApp';
|
||||
|
||||
/**
|
||||
* Generates a deep linking URL based on the current window URL.
|
||||
@@ -96,23 +75,17 @@ export function getDeepLinkingPage(state) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return _shouldShowDeepLinkingDesktopPage().then(
|
||||
return _openDesktopApp().then(
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
show => show ? DeepLinkingDesktopPage : undefined);
|
||||
result => result ? DeepLinkingDesktopPage : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the desktop app.
|
||||
*
|
||||
* @returns {void}
|
||||
* @returns {Promise<boolean>} - Resolves with true if the attempt to open the desktop app was successful and resolves
|
||||
* with false otherwise.
|
||||
*/
|
||||
export function openDesktopApp() {
|
||||
windowLoadedPromise.then(() => {
|
||||
// If the code for opening the deep link is executed before the window
|
||||
// load event, something with the internal chrome state goes wrong. The
|
||||
// result is that no window load event is received which is the cause
|
||||
// for some permission prompts to not be displayed. In our case the GUM
|
||||
// prompt wasn't displayed which causes the GUM call to never finish.
|
||||
window.location.href = generateDeepLinkingURL();
|
||||
});
|
||||
return _openDesktopApp();
|
||||
}
|
||||
|
||||
9
react/features/deep-linking/openDesktopApp.js
Normal file
9
react/features/deep-linking/openDesktopApp.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Opens the desktop app.
|
||||
*
|
||||
* @returns {Promise<boolean>} - Resolves with true if the attempt to open the desktop app was successful and resolves
|
||||
* with false otherwise.
|
||||
*/
|
||||
export function _openDesktopApp() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Resolves with <tt>true</tt> if the deep linking page should be shown and with
|
||||
* <tt>false</tt> otherwise.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export function _shouldShowDeepLinkingDesktopPage() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
1
react/features/external-api/index.js
Normal file
1
react/features/external-api/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './middleware';
|
||||
30
react/features/external-api/middleware.js
Normal file
30
react/features/external-api/middleware.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
|
||||
import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* The middleware of the feature {@code external-api}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register((/* store */) => next => action => {
|
||||
switch (action.type) {
|
||||
case NOTIFY_CAMERA_ERROR:
|
||||
if (action.error) {
|
||||
APP.API.notifyOnCameraError(
|
||||
action.error.name, action.error.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case NOTIFY_MIC_ERROR:
|
||||
if (action.error) {
|
||||
APP.API.notifyOnMicError(action.error.name, action.error.message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { Audio, MEDIA_TYPE } from '../../../base/media';
|
||||
import {
|
||||
PARTICIPANT_ROLE,
|
||||
ParticipantView,
|
||||
isEveryoneModerator,
|
||||
isLocalParticipantModerator,
|
||||
pinParticipant
|
||||
} from '../../../base/participants';
|
||||
@@ -38,6 +39,11 @@ type Props = {
|
||||
*/
|
||||
_audioTrack: Object,
|
||||
|
||||
/**
|
||||
* True if everone in the meeting is moderator.
|
||||
*/
|
||||
_isEveryoneModerator: boolean,
|
||||
|
||||
/**
|
||||
* True if the local participant is a moderator.
|
||||
*/
|
||||
@@ -117,6 +123,7 @@ class Thumbnail extends Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
_audioTrack: audioTrack,
|
||||
_isEveryoneModerator,
|
||||
_isModerator,
|
||||
_largeVideo: largeVideo,
|
||||
_onClick,
|
||||
@@ -172,7 +179,7 @@ class Thumbnail extends Component<Props> {
|
||||
|
||||
{ renderDisplayName && <DisplayNameLabel participantId = { participantId } /> }
|
||||
|
||||
{ participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
{ !_isEveryoneModerator && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& <View style = { styles.moderatorIndicatorContainer }>
|
||||
<ModeratorIndicator />
|
||||
</View> }
|
||||
@@ -275,6 +282,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
|
||||
return {
|
||||
_audioTrack: audioTrack,
|
||||
_isEveryoneModerator: isEveryoneModerator(state),
|
||||
_isModerator: isLocalParticipantModerator(state),
|
||||
_largeVideo: largeVideo,
|
||||
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
|
||||
|
||||
@@ -55,6 +55,7 @@ export default {
|
||||
backgroundColor: 'rgb(240, 243, 247)',
|
||||
borderBottomRightRadius: 10,
|
||||
borderTopRightRadius: 10,
|
||||
color: DARK_GREY,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
paddingVertical: 7
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import { toState } from '../base/redux';
|
||||
import { parseStandardURIString } from '../base/util';
|
||||
import { i18next, DEFAULT_LANGUAGE, LANGUAGES } from '../base/i18n';
|
||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../base/participants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
} from '../base/participants';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -83,14 +86,12 @@ export function getMoreTabProps(stateful: Object | Function) {
|
||||
} = state['features/base/conference'];
|
||||
const followMeActive = Boolean(state['features/follow-me'].moderator);
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
|
||||
// The settings sections to display.
|
||||
const showModeratorSettings = Boolean(
|
||||
conference
|
||||
&& configuredTabs.includes('moderator')
|
||||
&& localParticipant.role === PARTICIPANT_ROLE.MODERATOR);
|
||||
&& isLocalParticipantModerator(state));
|
||||
|
||||
return {
|
||||
currentLanguage: language,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { appNavigate } from '../../app';
|
||||
@@ -26,10 +27,33 @@ type Props = AbstractButtonProps & {
|
||||
* @extends AbstractHangupButton
|
||||
*/
|
||||
class HangupButton extends AbstractHangupButton<Props, *> {
|
||||
_hangup: Function;
|
||||
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.hangup';
|
||||
label = 'toolbar.hangup';
|
||||
tooltip = 'toolbar.hangup';
|
||||
|
||||
/**
|
||||
* Initializes a new HangupButton instance.
|
||||
*
|
||||
* @param {Props} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._hangup = _.once(() => {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
this.props.dispatch(disconnect(true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual hangup action.
|
||||
*
|
||||
@@ -38,14 +62,7 @@ class HangupButton extends AbstractHangupButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
this.props.dispatch(disconnect(true));
|
||||
}
|
||||
this._hangup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
getParticipantById
|
||||
} from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { TRACK_ADDED } from '../base/tracks';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../base/tracks';
|
||||
import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
|
||||
|
||||
import './middleware.any';
|
||||
@@ -83,6 +83,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
VideoLayout.onRemoteStreamAdded(action.track.jitsiTrack);
|
||||
}
|
||||
|
||||
break;
|
||||
case TRACK_REMOVED:
|
||||
if (!action.track.local) {
|
||||
VideoLayout.onRemoteStreamRemoved(action.track.jitsiTrack);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ import { AppRegistry } from 'react-native';
|
||||
import { App } from './features/app';
|
||||
import { IncomingCallApp } from './features/mobile/incoming-call';
|
||||
|
||||
declare var __DEV__;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Root}.
|
||||
*/
|
||||
@@ -49,6 +51,26 @@ class Root extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
// HORRIBLE HACK ALERT! React Native logs the initial props with `console.log`. Here we are quickly patching it
|
||||
// to avoid logging potentially sensitive information.
|
||||
if (!__DEV__) {
|
||||
/* eslint-disable */
|
||||
|
||||
const __orig_console_log = console.log;
|
||||
const __orig_appregistry_runapplication = AppRegistry.runApplication;
|
||||
|
||||
AppRegistry.runApplication = (...args) => {
|
||||
// $FlowExpectedError
|
||||
console.log = () => {};
|
||||
__orig_appregistry_runapplication(...args);
|
||||
// $FlowExpectedError
|
||||
console.log = __orig_console_log;
|
||||
};
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
|
||||
// Register the main/root Component of JitsiMeetView.
|
||||
AppRegistry.registerComponent('App', () => Root);
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ function Util.new(module)
|
||||
return self
|
||||
end
|
||||
|
||||
function Util:set_asap_key_server(asapKeyServer)
|
||||
self.asapKeyServer = asapKeyServer
|
||||
end
|
||||
|
||||
--- Returns the public key by keyID
|
||||
-- @param keyId the key ID to request
|
||||
-- @return the public key (the content of requested resource) or nil
|
||||
@@ -239,7 +243,6 @@ end
|
||||
-- @param session the current session
|
||||
-- @return false and error
|
||||
function Util:process_and_verify_token(session)
|
||||
|
||||
if session.auth_token == nil then
|
||||
if self.allowEmptyToken then
|
||||
return true;
|
||||
|
||||
@@ -39,6 +39,25 @@ local function room_jid_match_rewrite(room_jid)
|
||||
return room_jid
|
||||
end
|
||||
|
||||
-- Utility function to check and convert a room JID from real [foo]room1@muc.example.com to virtual room1@muc.foo.example.com
|
||||
local function room_jid_match_rewrite_from_internal(room_jid)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
if host ~= muc_domain or not node then
|
||||
module:log("debug", "No need to rewrite %s (not from the MUC host)", room_jid);
|
||||
|
||||
return room_jid;
|
||||
end
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
|
||||
if not (target_node and target_subdomain) then
|
||||
module:log("debug", "Not rewriting... unexpected node format: %s", node);
|
||||
return room_jid;
|
||||
end
|
||||
-- Ok, rewrite room_jid address to pretty format
|
||||
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
module:log("debug", "Rewrote to %s", room_jid);
|
||||
return room_jid
|
||||
end
|
||||
|
||||
--- Finds and returns room by its jid
|
||||
-- @param room_jid the room jid to search in the muc component
|
||||
@@ -194,5 +213,6 @@ return {
|
||||
wrap_async_run = wrap_async_run;
|
||||
async_handler_wrapper = async_handler_wrapper;
|
||||
room_jid_match_rewrite = room_jid_match_rewrite;
|
||||
room_jid_match_rewrite_from_internal = room_jid_match_rewrite_from_internal
|
||||
update_presence_identity = update_presence_identity;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user