Compare commits

..

6 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
27d41604df fix(script) add commits list to update LJM message 2020-12-18 09:45:09 +01:00
Saúl Ibarra Corretgé
99ac60ed74 feat(ios) rename SDK target to JitsiMeetSDK
Swift has a longstanding bug where a framework and a type cannot be named the
same. We have somehow managed to not run into this, but it now seems to be
hitting us.

Since this is a breaking change, this starts the road for SDK 3.0.
2020-12-17 23:02:48 +01:00
Дамян Минков
4f52fd5e01 fix: Skip sending multiple times disco-info to jicofo.
* fix: Skip sending multiple times disco-info to jicofo.
* build(deps): bump ini from 1.3.5 to 1.3.7

9f65e8fab3...8bb653f1d6
2020-12-17 13:02:28 -06:00
bhlee
edf415a7da fix(welcome-page) fix .insecure-room-name-warning margin 2020-12-17 09:28:24 +01:00
bhlee
5637b37fd2 fix(main-ko) add keyboardShortcuts videoQuality (#8264)
* fix(main-ko.json) Update overall korean spelling & words

* fix(_welcome_page.css) update .insecure-room-name-warning_margin-top from 5px to 15px

* fix(_welcome_page.css) initialize .insecure-room-name-warning_margin-top from 15px to 5px

* fix(main-ko.json) add keyboardShortcuts videoQuality
2020-12-16 23:17:44 -06:00
bhlee
dc0c1f0d93 fix(main-ko.json) Update some korean spelling & words (#8253)
* fix(main-ko.json) Update overall korean spelling & words

* fix(_welcome_page.css) update .insecure-room-name-warning_margin-top from 5px to 15px

* fix(_welcome_page.css) initialize .insecure-room-name-warning_margin-top from 15px to 5px
2020-12-16 19:45:24 -06:00
39 changed files with 1062 additions and 1081 deletions

View File

@@ -2011,6 +2011,7 @@ export default {
formattedDisplayName
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
});
APP.UI.changeDisplayName(id, formattedDisplayName);
}
);
room.on(
@@ -2049,7 +2050,10 @@ export default {
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
room.on(JitsiConferenceEvents.KICKED, participant => {
APP.UI.hideStats();
APP.store.dispatch(kickedOut(room, participant));
// FIXME close
});
room.on(JitsiConferenceEvents.PARTICIPANT_KICKED, (kicker, kicked) => {
@@ -2382,6 +2386,11 @@ export default {
APP.keyboardshortcut.init();
APP.store.dispatch(conferenceJoined(room));
const displayName
= APP.store.getState()['features/base/settings'].displayName;
APP.UI.changeDisplayName('localVideoContainer', displayName);
},
/**
@@ -2859,6 +2868,10 @@ export default {
APP.store.dispatch(updateSettings({
displayName: formattedNickname
}));
if (room) {
APP.UI.changeDisplayName(id, formattedNickname);
}
},
/**

View File

@@ -90,7 +90,7 @@ body.welcome-page {
color: $defaultWarningColor;
display: flex;
flex-direction: row;
margin-top: 5px;
margin-top: 15px;
.jitsi-icon {
margin-right: 15px;

View File

@@ -3,7 +3,7 @@ workspace 'jitsi-meet'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
install! 'cocoapods', :deterministic_uuids => false
target 'jitsi-meet' do
target 'JitsiMeet' do
project 'app/app.xcodeproj'
pod 'Firebase/Analytics', '~> 6.33.0'
@@ -11,7 +11,7 @@ target 'jitsi-meet' do
pod 'Firebase/DynamicLinks', '~> 6.33.0'
end
target 'JitsiMeet' do
target 'JitsiMeetSDK' do
project 'sdk/sdk.xcodeproj'
# React Native and its dependencies

View File

@@ -582,6 +582,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: f6626cd705333112182cedbe175ae2f9006e8874
PODFILE CHECKSUM: e77f7134cda53f089046f29cb1f32b08c00c8a2e
COCOAPODS: 1.10.0

View File

@@ -7,8 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
0B26BE6E1EC5BC3C00EEFB41 /* JitsiMeet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */; };
0B26BE6F1EC5BC3C00EEFB41 /* JitsiMeet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0B412F1F1EDEE6E800B1A0A6 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */; };
0B412F211EDEE95300B1A0A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0B412F201EDEE95300B1A0A6 /* Main.storyboard */; };
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */; };
@@ -25,10 +23,12 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */; };
55BEDABDA92D47D399A70A5E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */; };
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; };
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */; };
DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; };
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
/* End PBXBuildFile section */
@@ -57,8 +57,8 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */,
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
0B26BE6F1EC5BC3C00EEFB41 /* JitsiMeet.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -115,14 +115,17 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.debug.xcconfig"; sourceTree = "<group>"; };
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-jitsi-meet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DE050388256E904600DEE3A5 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/apple/WebRTC.xcframework"; sourceTree = "<group>"; };
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUtilities.m; sourceTree = "<group>"; };
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRUtilities.h; sourceTree = "<group>"; };
DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JitsiMeetSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
FC040BBED70876444D89E91C /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -137,9 +140,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0B26BE6E1EC5BC3C00EEFB41 /* JitsiMeet.framework in Frameworks */,
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */,
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */,
55BEDABDA92D47D399A70A5E /* libPods-JitsiMeet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -156,11 +159,12 @@
0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
isa = PBXGroup;
children = (
DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */,
DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */,
D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -217,6 +221,8 @@
children = (
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */,
09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */,
609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */,
FC040BBED70876444D89E91C /* Pods-JitsiMeet.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -284,9 +290,9 @@
productReference = 0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */;
productType = "com.apple.product-type.watchkit2-extension";
};
13B07F861A680F5B00A75B9A /* jitsi-meet */ = {
13B07F861A680F5B00A75B9A /* JitsiMeet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
buildPhases = (
B6607F42A5CF0C76E98929E2 /* [CP] Check Pods Manifest.lock */,
0BBA83C41EC9F7600075A103 /* Run React packager */,
@@ -305,7 +311,7 @@
dependencies = (
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
);
name = "jitsi-meet";
name = JitsiMeet;
productName = "Jitsi Meet";
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
productType = "com.apple.product-type.application";
@@ -356,7 +362,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* jitsi-meet */,
13B07F861A680F5B00A75B9A /* JitsiMeet */,
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
);
@@ -437,7 +443,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-jitsi-meet-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-JitsiMeet-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -707,7 +713,7 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */;
baseConfigurationReference = 609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
@@ -736,7 +742,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */;
baseConfigurationReference = FC040BBED70876444D89E91C /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
@@ -897,7 +903,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */ = {
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "JitsiMeet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,

View File

@@ -15,8 +15,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
BuildableName = "JitsiMeetSDK.framework"
BlueprintName = "JitsiMeetSDK"
ReferencedContainer = "container:../sdk/sdk.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -30,7 +30,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -46,7 +46,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -69,7 +69,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
@@ -86,7 +86,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>

View File

@@ -21,7 +21,7 @@
#import "ViewController.h"
@import Firebase;
@import JitsiMeet;
@import JitsiMeetSDK;
@implementation AppDelegate

View File

@@ -16,7 +16,7 @@
#import "FIRUtilities.h"
@import JitsiMeet;
@import JitsiMeetSDK;
@implementation FIRUtilities

View File

@@ -14,9 +14,8 @@
* limitations under the License.
*/
#import <UIKit/UIKit.h>
#import <JitsiMeet/JitsiMeet.h>
@import UIKit;
@import JitsiMeetSDK;
@interface ViewController : UIViewController<JitsiMeetViewDelegate>

View File

@@ -21,7 +21,7 @@
@import MobileCoreServices;
@import Intents; // Needed for NSUserActivity suggestedInvocationPhrase
@import JitsiMeet;
@import JitsiMeetSDK;
#import "Types.h"
#import "ViewController.h"

View File

@@ -27,10 +27,10 @@ pushd ${PROJECT_REPO}
rm -rf ios/sdk/out
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet
-scheme JitsiMeetSDK
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-scheme JitsiMeetSDK \
-configuration Release \
-sdk iphonesimulator \
-destination='generic/platform=iOS Simulator' \
@@ -41,7 +41,7 @@ xcodebuild archive \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-scheme JitsiMeetSDK \
-configuration Release \
-sdk iphoneos \
-destination='generic/platform=iOS' \
@@ -51,9 +51,9 @@ xcodebuild archive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-output ios/sdk/out/JitsiMeet.xcframework
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-output ios/sdk/out/JitsiMeetSDK.xcframework
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-${SDK_VERSION}
fi
@@ -62,7 +62,7 @@ popd
pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeet.xcframework Frameworks/
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework Frameworks/
cp -a ${PROJECT_REPO}/node_modules/react-native-webrtc/apple/WebRTC.xcframework Frameworks/
# Add all files to git

View File

@@ -24,9 +24,9 @@
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495D1EC4B6C600B793EE /* POSIX.m */; };
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495E1EC4B6C600B793EE /* Proximity.m */; };
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
6C31EDC820C06D490089C899 /* recordingOn.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC720C06D490089C899 /* recordingOn.mp3 */; };
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */ = {isa = PBXBuildFile; fileRef = 87FE6F3221E52437004A5DC7 /* incomingMessage.wav */; };
@@ -54,6 +54,7 @@
DE81A2D92316AC7600AE1940 /* LogBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = DE81A2D72316AC7600AE1940 /* LogBridge.m */; };
DE81A2DE2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = DE81A2DC2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
DE81A2DF2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = DE81A2DD2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m */; };
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = DEA9F283258A5D9900D4CD74 /* JitsiMeetSDK.h */; settings = {ATTRIBUTES = (Public, ); }; };
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 */; };
@@ -63,7 +64,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
09A78016288AF50ACD28A10D /* Pods-JitsiMeetSDK.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.debug.xcconfig"; sourceTree = "<group>"; };
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitsiMeetView.h; sourceTree = "<group>"; };
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
@@ -81,7 +82,7 @@
0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = "<group>"; };
0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = "<group>"; };
0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = "<group>"; };
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BD906E51EC0C00300C8C18E /* JitsiMeetSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeetSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6C31EDC720C06D490089C899 /* recordingOn.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOn.mp3; path = ../../sounds/recordingOn.mp3; sourceTree = "<group>"; };
@@ -89,6 +90,7 @@
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
87FE6F3221E52437004A5DC7 /* incomingMessage.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = incomingMessage.wav; path = ../../sounds/incomingMessage.wav; sourceTree = "<group>"; };
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.release.xcconfig"; sourceTree = "<group>"; };
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
@@ -118,6 +120,7 @@
DE81A2D72316AC7600AE1940 /* LogBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LogBridge.m; sourceTree = "<group>"; };
DE81A2DC2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetBaseLogHandler.h; sourceTree = "<group>"; };
DE81A2DD2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetBaseLogHandler.m; sourceTree = "<group>"; };
DEA9F283258A5D9900D4CD74 /* JitsiMeetSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetSDK.h; sourceTree = "<group>"; };
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>"; };
@@ -128,6 +131,7 @@
DEFE535521FB2E8300011A3A /* ReactUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactUtils.m; sourceTree = "<group>"; };
DEFE535721FB2E9E00011A3A /* ReactUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactUtils.h; sourceTree = "<group>"; };
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeet+Private.h"; sourceTree = "<group>"; };
E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeetSDK.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -137,7 +141,7 @@
files = (
0BB9AD791F5EC6D7001C08DB /* Intents.framework in Frameworks */,
0BB9AD771F5EC6CE001C08DB /* CallKit.framework in Frameworks */,
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */,
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -178,7 +182,7 @@
0BD906E61EC0C00300C8C18E /* Products */ = {
isa = PBXGroup;
children = (
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */,
0BD906E51EC0C00300C8C18E /* JitsiMeetSDK.framework */,
);
name = Products;
sourceTree = "<group>";
@@ -196,6 +200,7 @@
DE438CD82350934700DD541D /* JavaScriptSandbox.m */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
DEA9F283258A5D9900D4CD74 /* JitsiMeetSDK.h */,
DEFE535321FB1BF800011A3A /* JitsiMeet.m */,
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */,
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */,
@@ -236,7 +241,7 @@
0BB9AD761F5EC6CE001C08DB /* CallKit.framework */,
0B93EF7A1EC608550030D24D /* CoreText.framework */,
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -264,6 +269,8 @@
children = (
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */,
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */,
09A78016288AF50ACD28A10D /* Pods-JitsiMeetSDK.debug.xcconfig */,
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -299,6 +306,7 @@
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
DE81A2DE2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h in Headers */,
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
@@ -312,9 +320,9 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
0BD906E41EC0C00300C8C18E /* JitsiMeet */ = {
0BD906E41EC0C00300C8C18E /* JitsiMeetSDK */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeetSDK" */;
buildPhases = (
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */,
0BD906E01EC0C00300C8C18E /* Sources */,
@@ -328,9 +336,9 @@
);
dependencies = (
);
name = JitsiMeet;
name = JitsiMeetSDK;
productName = "Jitsi Meet SDK";
productReference = 0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */;
productReference = 0BD906E51EC0C00300C8C18E /* JitsiMeetSDK.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
@@ -362,7 +370,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
0BD906E41EC0C00300C8C18E /* JitsiMeet */,
0BD906E41EC0C00300C8C18E /* JitsiMeetSDK */,
);
};
/* End PBXProject section */
@@ -416,7 +424,7 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-JitsiMeet-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-JitsiMeetSDK-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -429,7 +437,7 @@
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh",
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources.sh",
"${PODS_ROOT}/Amplitude-iOS/Amplitude/api.amplitude.com.der",
"${PODS_ROOT}/Amplitude-iOS/Amplitude/ComodoCaLimitedRsaCertificationAuthority.der",
"${PODS_ROOT}/Amplitude-iOS/Amplitude/ComodoRsaCA.der",
@@ -446,7 +454,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -609,7 +617,7 @@
};
0BD906EE1EC0C00300C8C18E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */;
baseConfigurationReference = 09A78016288AF50ACD28A10D /* Pods-JitsiMeetSDK.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
@@ -636,7 +644,7 @@
};
0BD906EF1EC0C00300C8C18E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */;
baseConfigurationReference = 891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
@@ -672,7 +680,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */ = {
0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeetSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0BD906EE1EC0C00300C8C18E /* Debug */,

View File

@@ -15,8 +15,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
BuildableName = "JitsiMeetSDK.framework"
BlueprintName = "JitsiMeetSDK"
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -44,8 +44,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
BuildableName = "JitsiMeetSDK.framework"
BlueprintName = "JitsiMeetSDK"
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -60,8 +60,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
BuildableName = "JitsiMeetSDK.framework"
BlueprintName = "JitsiMeetSDK"
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.12.0</string>
<string>3.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -1,6 +1,5 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
* Copyright @ 2017-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.
@@ -15,13 +14,10 @@
* limitations under the License.
*/
#import <JitsiMeet/JitsiMeetView.h>
#import <JitsiMeet/JitsiMeetViewDelegate.h>
#import <JitsiMeet/JitsiMeetConferenceOptions.h>
#import <JitsiMeet/JitsiMeetLogger.h>
#import <JitsiMeet/JitsiMeetBaseLogHandler.h>
#import <JitsiMeet/InfoPlistUtil.h>
@import UIKit;
@import Foundation;
#import <JitsiMeetSDK/JitsiMeetConferenceOptions.h>
@interface JitsiMeet : NSObject

View File

@@ -0,0 +1,23 @@
/*
* Copyright @ 2020-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 <JitsiMeetSDK/JitsiMeet.h>
#import <JitsiMeetSDK/JitsiMeetView.h>
#import <JitsiMeetSDK/JitsiMeetViewDelegate.h>
#import <JitsiMeetSDK/JitsiMeetConferenceOptions.h>
#import <JitsiMeetSDK/JitsiMeetLogger.h>
#import <JitsiMeetSDK/JitsiMeetBaseLogHandler.h>
#import <JitsiMeetSDK/InfoPlistUtil.h>

View File

@@ -28,9 +28,9 @@
#import <React/RCTUtils.h>
#import <WebRTC/WebRTC.h>
#import <JitsiMeet/JitsiMeet-Swift.h>
#import <JitsiMeetSDK/JitsiMeetSDK-Swift.h>
#import "LogUtils.h"
#import "../LogUtils.h"
// The events emitted/supported by RNCallKit:

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
import UIKit
final class DragGestureController {
var insets: UIEdgeInsets = UIEdgeInsets.zero

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
import UIKit
public typealias AnimationCompletion = (Bool) -> Void
public protocol PiPViewCoordinatorDelegate: class {

View File

@@ -2,7 +2,7 @@
"addPeople": {
"add": "초대",
"addContacts": "연락처로 초대하세요",
"copyInvite": "의 초대 복사",
"copyInvite": "의 초대 복사",
"copyLink": "회의 링크 복사",
"copyStream": "라이브 스트리밍 링크 복사",
"countryNotSupported": "아직 해당 지역을 지원하지 않습니다.",
@@ -17,7 +17,7 @@
"inviteMorePrompt": "더 많은 사람을 초대하세요",
"linkCopied": "링크가 클립보드에 복사되었습니다.",
"loading": "사람 및 전화번호 검색",
"loadingNumber": "전화번호 확인 중",
"loadingNumber": "전화번호 확인중",
"loadingPeople": "초대할 사람 찾기",
"noResults": "일치하는 검색 결과 없음",
"noValidNumbers": "전화 번호를 입력하십시오.",
@@ -80,21 +80,21 @@
"dontShowAgain": "다시 보지 않기"
},
"connectingOverlay": {
"joiningRoom": "회의에 연결 중 ..."
"joiningRoom": "회의에 연결중 ..."
},
"connection": {
"ATTACHED": "첨부",
"AUTHENTICATING": "인증 중",
"AUTHENTICATING": "인증중",
"AUTHFAIL": "인증 실패",
"CONNECTED": "연결 됨",
"CONNECTING": "연결 중",
"CONNECTED": "연결됨",
"CONNECTING": "연결중",
"CONNFAIL": "연결 실패",
"DISCONNECTED": "연결 끊김",
"DISCONNECTING": "연결 종료 중",
"DISCONNECTING": "연결 종료중",
"ERROR": "에러",
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결 중...",
"GET_SESSION_ID_ERROR": "세션 ID 가져 오기 오류 : {{code}}",
"GOT_SESSION_ID": "세션 ID를 가져 오는 중 ... 완료",
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결중...",
"GET_SESSION_ID_ERROR": "세션 ID 가져오기 오류 : {{code}}",
"GOT_SESSION_ID": "세션 ID를 가져오는중 ... 완료",
"LOW_BANDWIDTH": "대역폭을 절약하기 위해 {{displayName}}의 동영상이 중지되었습니다."
},
"connectionindicator": {
@@ -160,16 +160,16 @@
},
"add": "추가",
"allow": "허락",
"alreadySharedVideoMsg": "다른 참가자가 이미 비디오를 공유하고 있습니다. 이 회의는 한 번에 하나의 공유 비디오 만 허용합니다.",
"alreadySharedVideoTitle": "한 번에 하나의 공유 비디오 만 허용됩니다",
"alreadySharedVideoMsg": "다른 참가자가 이미 비디오를 공유하고 있습니다. 이 회의는 한 번에 하나의 공유 비디오만 허용합니다.",
"alreadySharedVideoTitle": "한 번에 하나의 공유 비디오만 허용됩니다",
"applicationWindow": "응용 프로그램 창",
"Back": "뒤로가기",
"cameraConstraintFailedError": "카메라가 필요한 제약 조건 중 일부를 만족하지 못합니다",
"cameraNotFoundError": "카메라를 찾을 수 없습니다",
"cameraNotSendingData": "카메라에 액세스 할 수 없습니다. 다른 응용 프로그램이 장치를 사용하고 있는지 확인한 후 설정 메뉴에서 다른 장치를 선택하거나 응용 프로그램을 다시로드하십시오.",
"cameraNotSendingData": "카메라에 액세스 할 수 없습니다. 다른 응용 프로그램이 장치를 사용하고 있는지 확인한 후 설정 메뉴에서 다른 장치를 선택하거나 응용 프로그램을 다시 로드하십시오.",
"cameraNotSendingDataTitle": "카메라에 액세스 할 수 없습니다",
"cameraPermissionDeniedError": "카메라 사용 권한을 부여하지 않았습니다. 회의에 계속 참여할 수 있지만 다른 참석자는 귀하를 볼 수 없습니다. 검색 주소창의 카메라 버튼을 사용하여 문제를 해결하십시오.",
"cameraUnknownError": "알 수없는 이유로 카메라를 사용할 수 없습니다",
"cameraUnknownError": "알 수 없는 이유로 카메라를 사용할 수 없습니다",
"cameraUnsupportedResolutionError": "카메라가 필요한 비디오 해상도를 지원하지 않습니다",
"Cancel": "취소",
"close": "닫기",
@@ -182,7 +182,7 @@
"confirmYes": "예",
"connectError": "죄송합니다. 문제가 발생하여 회의에 연결할 수 없습니다",
"connectErrorWithMsg": "죄송합니다. 뭔가 잘못되어 회의에 연결할 수 없습니다: {{msg}}",
"connecting": "연결 중",
"connecting": "연결중",
"contactSupport": "지원 연락처",
"copy": "복사",
"dismiss": "",
@@ -190,7 +190,7 @@
"done": "완료",
"enterDisplayName": "당신의 이름을 입력해주세요.",
"error": "에러",
"externalInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야합니다",
"externalInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야 합니다",
"externalInstallationTitle": "확장 프로그램이 필요합니다",
"goToStore": "웹 스토어로 이동",
"gracefulShutdown": "서비스는 현재 유지 관리를 위해 중단되었습니다. 나중에 다시 시도 해주십시오.",
@@ -227,7 +227,7 @@
"muteParticipantDialog": "",
"muteParticipantTitle": "이 참가자를 음소거 하시겠습니까?",
"Ok": "확인",
"passwordLabel": "잠긴 회의 입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
"passwordLabel": "잠긴 회의입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
"passwordNotSupported": "회의 비밀번호 설정은 지원되지 않습니다",
"passwordNotSupportedTitle": "비밀번호 미지원",
"passwordRequired": "비밀번호 필수",
@@ -285,8 +285,8 @@
"transcribing": "",
"unlockRoom": "회의 비밀번호 제거",
"userPassword": "사용자 비밀번호",
"WaitForHostMsg": "<b>{{room}}</b> 회의가 시작되지 않았습니다. 호스트 인 경우 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitForHostMsgWOk": "<b>{{room}}</b> 회의가 아직 시작되지 않았습니다. 호스트 인 경우 확인을 눌러 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitForHostMsg": "<b>{{room}}</b> 회의가 시작되지 않았습니다. 호스트인 경우 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitForHostMsgWOk": "<b>{{room}}</b> 회의가 아직 시작되지 않았습니다. 호스트인 경우 확인을 눌러 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitingForHost": "호스트를 기다리는 중입니다…",
"Yes": "예",
"yourEntireScreen": "전체 화면"
@@ -371,7 +371,8 @@
"toggleFilmstrip": "동영상 표시 또는 숨기기",
"toggleScreensharing": "카메라와 화면 공유간에 전환",
"toggleShortcuts": "도움말 메뉴 표시 또는 숨기기",
"videoMute": "카메라 시작 또는 중지"
"videoMute": "카메라 시작 또는 중지",
"videoQuality": "비디오 품질"
},
"liveStreaming": {
"busy": "스트리밍 자원을 확보하기 위해 노력하고 있습니다. 몇 분 후에 다시 시도하십시오.",
@@ -382,7 +383,7 @@
"enterStreamKey": "YouTube 실시간 스트리밍 키를 입력하십시오",
"error": "실시간 스트리밍에 실패했습니다. 다시 시도하십시오.",
"errorAPI": "YouTube 방송에 액세스하는 중에 오류가 발생했습니다. 다시 로그인하십시오.",
"errorLiveStreamNotEnabled": "{{email}}에 의해 라이브 스트리밍이 활성화되지 않았습니다. 라이브 스트리밍을 활성화하거나 라이브 스트리밍이 활성화 된 계정으로 로그인하십시오.",
"errorLiveStreamNotEnabled": "{{email}}에 의해 라이브 스트리밍이 활성화되지 않았습니다. 라이브 스트리밍을 활성화하거나 라이브 스트리밍이 활성화된 계정으로 로그인하십시오.",
"expandedOff": "라이브 스트리밍이 중지되었습니다",
"expandedOn": "현재 회의가 YouTube로 스트리밍되고 있습니다.",
"expandedPending": "라이브 스트리밍이 시작됩니다 ...",
@@ -471,17 +472,17 @@
"poweredby": "powered by",
"presenceStatus": {
"busy": "바쁨",
"calling": "전화 거는 중",
"connected": "연결 됨",
"connecting": "연결 중",
"connecting2": "연결 중*",
"calling": "전화 거는중",
"connected": "연결됨",
"connecting": "연결중",
"connecting2": "연결중*",
"disconnected": "연결 끊김",
"expired": "만료 됨",
"ignored": "무시 됨",
"initializingCall": "통화 초기화 중",
"invited": "초대 됨",
"rejected": "거부 됨",
"ringing": "전화 중"
"expired": "만료됨",
"ignored": "무시됨",
"initializingCall": "통화 초기화중",
"invited": "초대됨",
"rejected": "거부됨",
"ringing": "전화중"
},
"profile": {
"setDisplayNameLabel": "표시 이름 설정",
@@ -494,10 +495,10 @@
"availableSpace": "사용 가능한 공간 : {{spaceLeft}}MB (약 {{duration}}분 녹화)",
"beta": "베타",
"busy": "레코딩 자원을 확보하고 있습니다. 몇 분 후에 다시 시도하십시오.",
"busyTitle": "모든 레코더가 현재 사용 중입니다",
"busyTitle": "모든 레코더가 현재 사용중입니다",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedOff": "레코딩이 중지됨",
"expandedOn": "회의가 현재 녹화 중입니다.",
"expandedOn": "회의가 현재 녹화중입니다.",
"expandedPending": "녹화가 시작됩니다 ...",
"failedToStart": "레코딩을 시작하지 못했습니다",
"fileSharingdescription": "회의 참가자와 녹음 공유",
@@ -618,7 +619,7 @@
"audioOnlyOff": "음성전용 모드 끄기",
"audioOnlyOn": "음성전용 모드 끄기",
"audioRoute": "음성 장비 선택하기",
"authenticate": "인증 중",
"authenticate": "인증중",
"callQuality": "품질 설정하기",
"chat": "대화 열기/닫기",
"closeChat": "대화 닫기",
@@ -665,7 +666,7 @@
"transcribing": {
"ccButtonTooltip": "자막 시작/종료",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedLabel": "현재 스크립트 작성 중",
"expandedLabel": "현재 스크립트 작성중",
"failedToStart": "스크립트 작성을 시작하지 못했습니다.",
"labelToolTip": "회의가 기록되고 있습니다.",
"off": "스크립트 작성이 중지되었습니다.",
@@ -698,7 +699,7 @@
},
"videoStatus": {
"audioOnly": "오디오 전용",
"audioOnlyExpanded": "낮은 대역폭 모드에 있습니다. 이 모드에서는 오디오 및 화면 공유 만 수신합니다.",
"audioOnlyExpanded": "낮은 대역폭 모드에 있습니다. 이 모드에서는 오디오 및 화면 공유만 수신합니다.",
"callQuality": "비디오 품질",
"hd": "HD",
"highDefinition": "고해상도",

View File

@@ -7,6 +7,7 @@ import EventEmitter from 'events';
import Logger from 'jitsi-meet-logger';
import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { getLocalParticipant } from '../../react/features/base/participants';
import { toggleChat } from '../../react/features/chat';
import { setDocumentUrl } from '../../react/features/etherpad';
import { setFilmstripVisible } from '../../react/features/filmstrip';
@@ -90,11 +91,29 @@ UI.notifyReservationError = function(code, msg) {
});
};
/**
* Change nickname for the user.
* @param {string} id user id
* @param {string} displayName new nickname
*/
UI.changeDisplayName = function(id, displayName) {
VideoLayout.onDisplayNameChanged(id, displayName);
};
/**
* Initialize conference UI.
*/
UI.initConference = function() {
const { getState } = APP.store;
const { id, name } = getLocalParticipant(getState);
UI.showToolbar();
const displayName = config.displayJids ? id : name;
if (displayName) {
UI.changeDisplayName('localVideoContainer', displayName);
}
};
/**
@@ -219,12 +238,19 @@ UI.getSharedDocumentManager = () => etherpadManager;
* @param {JitsiParticipant} user
*/
UI.addUser = function(user) {
const id = user.getId();
const displayName = user.getDisplayName();
const status = user.getStatus();
if (status) {
// FIXME: move updateUserStatus in participantPresenceChanged action
UI.updateUserStatus(user, status);
}
// set initial display name
if (displayName) {
UI.changeDisplayName(id, displayName);
}
};
/**
@@ -416,6 +442,14 @@ UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
*/
UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
/**
* Hide connection quality statistics from UI.
*/
UI.hideStats = function() {
VideoLayout.hideStats();
};
UI.notifyTokenAuthFailed = function() {
messageHandler.showError({
descriptionKey: 'dialog.tokenAuthFailed',

View File

@@ -1,15 +1,10 @@
/* global $, APP */
/* global $ */
/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import Logger from 'jitsi-meet-logger';
import { i18next } from '../../../react/features/base/i18n';
import Thumbnail from '../../../react/features/filmstrip/components/web/Thumbnail';
import SmallVideo from '../videolayout/SmallVideo';
/* eslint-enable no-unused-vars */
const logger = Logger.getLogger(__filename);
/**
*
@@ -29,12 +24,17 @@ export default class SharedVideoThumb extends SmallVideo {
this.videoSpanId = 'sharedVideoContainer';
this.container = this.createContainer(this.videoSpanId);
this.$container = $(this.container);
this.renderThumbnail();
this._setThumbnailSize();
this.bindHoverHandler();
this.updateDisplayName();
this.container.onclick = this._onContainerClick;
}
/**
*
*/
initializeAvatar() {} // eslint-disable-line no-empty-function
/**
*
* @param {*} spanId
@@ -45,6 +45,18 @@ export default class SharedVideoThumb extends SmallVideo {
container.id = spanId;
container.className = 'videocontainer';
// add the avatar
const avatar = document.createElement('img');
avatar.className = 'sharedVideoAvatar';
avatar.src = `https://img.youtube.com/vi/${this.url}/0.jpg`;
container.appendChild(avatar);
const displayNameContainer = document.createElement('div');
displayNameContainer.className = 'displayNameContainer';
container.appendChild(displayNameContainer);
const remoteVideosContainer
= document.getElementById('filmstripRemoteVideosContainer');
const localVideoContainer
@@ -56,14 +68,21 @@ export default class SharedVideoThumb extends SmallVideo {
}
/**
* Renders the thumbnail.
* Triggers re-rendering of the display name using current instance state.
*
* @returns {void}
*/
renderThumbnail(isHovered = false) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
</I18nextProvider>
</Provider>, this.container);
updateDisplayName() {
if (!this.container) {
logger.warn(`Unable to set displayName - ${this.videoSpanId
} does not exist`);
return;
}
this._renderDisplayName({
elementID: `${this.videoSpanId}_name`,
participantID: this.id
});
}
}

View File

@@ -37,6 +37,7 @@ const Filmstrip = {
*/
resizeThumbnailsForTileView(width, height, forceUpdate = false) {
const thumbs = this._getThumbs(!forceUpdate);
const avatarSize = height / 2;
if (thumbs.localThumb) {
thumbs.localThumb.css({
@@ -57,6 +58,11 @@ const Filmstrip = {
width: `${width}px`
});
}
$('.avatar-container').css({
height: `${avatarSize}px`,
width: `${avatarSize}px`
});
},
/**
@@ -71,6 +77,7 @@ const Filmstrip = {
if (thumbs.localThumb) {
const { height, width } = local;
const avatarSize = height / 2;
thumbs.localThumb.css({
height: `${height}px`,
@@ -78,10 +85,15 @@ const Filmstrip = {
'min-width': `${width}px`,
width: `${width}px`
});
$('#localVideoContainer > .avatar-container').css({
height: `${avatarSize}px`,
width: `${avatarSize}px`
});
}
if (thumbs.remoteThumbs) {
const { height, width } = remote;
const avatarSize = height / 2;
thumbs.remoteThumbs.css({
height: `${height}px`,
@@ -89,6 +101,10 @@ const Filmstrip = {
'min-width': `${width}px`,
width: `${width}px`
});
$('#filmstripRemoteVideosContainer > span > .avatar-container').css({
height: `${avatarSize}px`,
width: `${avatarSize}px`
});
}
},
@@ -110,6 +126,10 @@ const Filmstrip = {
'min-width': '',
'min-height': ''
});
$('#localVideoContainer > .avatar-container').css({
height: '50%',
width: `${heightToWidthPercent / 2}%`
});
}
if (thumbs.remoteThumbs) {
@@ -122,6 +142,10 @@ const Filmstrip = {
'min-width': '',
'min-height': ''
});
$('#filmstripRemoteVideosContainer > span > .avatar-container').css({
height: '50%',
width: `${heightToWidthPercent / 2}%`
});
}
},

View File

@@ -1,23 +1,23 @@
/* global $, config, APP */
/* global $, config, interfaceConfig, APP */
import Logger from 'jitsi-meet-logger';
/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../react/features/base/i18n';
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VideoTrack } from '../../../react/features/base/media';
import { updateSettings } from '../../../react/features/base/settings';
import { getLocalVideoTrack } from '../../../react/features/base/tracks';
import Thumbnail from '../../../react/features/filmstrip/components/web/Thumbnail';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
import SmallVideo from './SmallVideo';
const logger = Logger.getLogger(__filename);
/**
*
*/
@@ -37,7 +37,6 @@ export default class LocalVideo extends SmallVideo {
this.isLocal = true;
this._setThumbnailSize();
this.updateDOMLocation();
this.renderThumbnail();
this.localVideoId = null;
this.bindHoverHandler();
@@ -45,6 +44,7 @@ export default class LocalVideo extends SmallVideo {
this._buildContextMenu();
}
this.emitter = emitter;
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP ? 'left top' : 'top center';
Object.defineProperty(this, 'id', {
get() {
@@ -53,6 +53,18 @@ export default class LocalVideo extends SmallVideo {
});
this.initBrowserSpecificProperties();
// Set default display name.
this.updateDisplayName();
// Initialize the avatar display with an avatar url selected from the redux
// state. Redux stores the local user with a hardcoded participant id of
// 'local' if no id has been assigned yet.
this.initializeAvatar();
this.addAudioLevelIndicator();
this.updateIndicators();
this.updateStatusBar();
this.container.onclick = this._onContainerClick;
}
@@ -65,19 +77,38 @@ export default class LocalVideo extends SmallVideo {
containerSpan.classList.add('videocontainer');
containerSpan.id = this.videoSpanId;
containerSpan.innerHTML = `
<div class = 'videocontainer__background'></div>
<span id = 'localVideoWrapper'></span>
<div class = 'videocontainer__toolbar'></div>
<div class = 'videocontainer__toptoolbar'></div>
<div class = 'videocontainer__hoverOverlay'></div>
<div class = 'displayNameContainer'></div>
<div class = 'avatar-container'></div>`;
return containerSpan;
}
/**
* Renders the thumbnail.
* Triggers re-rendering of the display name using current instance state.
*
* @returns {void}
*/
renderThumbnail(isHovered = false) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
</I18nextProvider>
</Provider>, this.container);
updateDisplayName() {
if (!this.container) {
logger.warn(
`Unable to set displayName - ${this.videoSpanId
} does not exist`);
return;
}
this._renderDisplayName({
allowEditing: !config.disableProfile,
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
elementID: 'localDisplayName',
participantID: this.id
});
}
/**
@@ -85,7 +116,9 @@ export default class LocalVideo extends SmallVideo {
* @param {*} stream
*/
changeVideo(stream) {
this.videoStream = stream;
this.localVideoId = `localVideo_${stream.getId()}`;
this._updateVideoElement();
// eslint-disable-next-line eqeqeq
const isVideo = stream.videoType != 'desktop';
@@ -95,6 +128,17 @@ export default class LocalVideo extends SmallVideo {
this.setFlipX(isVideo ? settings.localFlipX : false);
const endedHandler = () => {
const localVideoContainer
= document.getElementById('localVideoWrapper');
// Only remove if there is no video and not a transition state.
// Previous non-react logic created a new video element with each track
// removal whereas react reuses the video component so it could be the
// stream ended but a new one is being used.
if (localVideoContainer && this.videoStream.isEnded()) {
ReactDOM.unmountComponentAtNode(localVideoContainer);
}
this._notifyOfStreamEnded();
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
};
@@ -210,5 +254,35 @@ export default class LocalVideo extends SmallVideo {
: document.getElementById('filmstripLocalVideoThumbnail');
appendTarget && appendTarget.appendChild(this.container);
this._updateVideoElement();
}
/**
* Renders the React Element for displaying video in {@code LocalVideo}.
*
*/
_updateVideoElement() {
const localVideoContainer = document.getElementById('localVideoWrapper');
const videoTrack
= getLocalVideoTrack(APP.store.getState()['features/base/tracks']);
ReactDOM.render(
<Provider store = { APP.store }>
<VideoTrack
id = 'localVideo_container'
videoTrack = { videoTrack } />
</Provider>,
localVideoContainer
);
// Ensure the video gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case video does not autoplay. Also, set the playsinline attribute on the
// video element so that local video doesn't open in full screen by default
// in Safari browser on iOS.
const video = this.container.querySelector('video');
video && video.setAttribute('playsinline', 'true');
video && !config.testing?.noAutoPlayVideo && video.play();
}
}

View File

@@ -1,4 +1,4 @@
/* global $, APP, config */
/* global $, APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import { AtlasKitThemeProvider } from '@atlaskit/theme';
@@ -15,7 +15,6 @@ import {
import { getParticipantById } from '../../../react/features/base/participants';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
import { Thumbnail, isVideoPlayable } from '../../../react/features/filmstrip';
import { PresenceLabel } from '../../../react/features/presence-status';
import { stopController, requestRemoteControl } from '../../../react/features/remote-control';
import { RemoteVideoMenuTriggerButton } from '../../../react/features/remote-video-menu';
@@ -45,6 +44,16 @@ function createContainer(spanId) {
container.id = spanId;
container.className = 'videocontainer';
container.innerHTML = `
<div class = 'videocontainer__background'></div>
<div class = 'videocontainer__toptoolbar'></div>
<div class = 'videocontainer__toolbar'></div>
<div class = 'videocontainer__hoverOverlay'></div>
<div class = 'displayNameContainer'></div>
<div class = 'avatar-container'></div>
<div class ='presence-label-container'></div>
<span class = 'remotevideomenu'></span>`;
const remoteVideosContainer
= document.getElementById('filmstripRemoteVideosContainer');
const localVideoContainer
@@ -73,7 +82,11 @@ export default class RemoteVideo extends SmallVideo {
this.id = user.getId();
this.videoSpanId = `participant_${this.id}`;
this._audioStreamElement = null;
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP ? 'left bottom' : 'top center';
this.addRemoteVideoContainer();
this.updateIndicators();
this.updateDisplayName();
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
@@ -87,6 +100,11 @@ export default class RemoteVideo extends SmallVideo {
*/
this._canPlayEventReceived = false;
// Bind event handlers so they are only bound once for every instance.
// TODO The event handlers should be turned into actions so changes can be
// handled through reducers and middleware.
this._setAudioVolume = this._setAudioVolume.bind(this);
this.container.onclick = this._onContainerClick;
}
@@ -96,23 +114,76 @@ export default class RemoteVideo extends SmallVideo {
addRemoteVideoContainer() {
this.container = createContainer(this.videoSpanId);
this.$container = $(this.container);
this.renderThumbnail();
this.initializeAvatar();
this._setThumbnailSize();
this.initBrowserSpecificProperties();
this.updateRemoteVideoMenu();
this.updateStatusBar();
this.addAudioLevelIndicator();
this.addPresenceLabel();
return this.container;
}
/**
* Renders the thumbnail.
* Generates the popup menu content.
*
* @returns {Element|*} the constructed element, containing popup menu items
* @private
*/
renderThumbnail(isHovered = false) {
_generatePopupContent() {
const remoteVideoMenuContainer
= this.container.querySelector('.remotevideomenu');
if (!remoteVideoMenuContainer) {
return;
}
const initialVolumeValue = this._audioStreamElement && this._audioStreamElement.volume;
// hide volume when in silent mode
const onVolumeChange
= APP.store.getState()['features/base/config'].startSilent ? undefined : this._setAudioVolume;
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
<AtlasKitThemeProvider mode = 'dark'>
<RemoteVideoMenuTriggerButton
initialVolumeValue = { initialVolumeValue }
onMenuDisplay
= {this._onRemoteVideoMenuDisplay.bind(this)}
onVolumeChange = { onVolumeChange }
participantID = { this.id } />
</AtlasKitThemeProvider>
</I18nextProvider>
</Provider>, this.container);
</Provider>,
remoteVideoMenuContainer);
}
/**
*
*/
_onRemoteVideoMenuDisplay() {
this.updateRemoteVideoMenu();
}
/**
* Change the remote participant's volume level.
*
* @param {int} newVal - The value to set the slider to.
*/
_setAudioVolume(newVal) {
if (this._audioStreamElement) {
this._audioStreamElement.volume = newVal;
}
}
/**
* Updates the remote video menu.
*/
updateRemoteVideoMenu() {
this._generatePopupContent();
}
/**
@@ -128,7 +199,7 @@ export default class RemoteVideo extends SmallVideo {
}
const isVideo = stream.isVideoTrack();
const elementID = `remoteVideo_${stream.getId()}`;
const elementID = SmallVideo.getStreamElementID(stream);
const select = $(`#${elementID}`);
select.remove();
@@ -136,7 +207,11 @@ export default class RemoteVideo extends SmallVideo {
this._canPlayEventReceived = false;
}
logger.info(`Video removed ${this.id}`, select);
logger.info(`${isVideo ? 'Video' : 'Audio'} removed ${this.id}`, select);
if (stream === this.videoStream) {
this.videoStream = null;
}
this.updateView();
}
@@ -148,7 +223,14 @@ export default class RemoteVideo extends SmallVideo {
* @override
*/
isVideoPlayable() {
return isVideoPlayable(APP.store.getState(), this.id) && this._canPlayEventReceived;
const participant = getParticipantById(APP.store.getState(), this.id);
const { connectionStatus } = participant || {};
return (
super.isVideoPlayable()
&& this._canPlayEventReceived
&& connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
);
}
/**
@@ -163,8 +245,9 @@ export default class RemoteVideo extends SmallVideo {
* Removes RemoteVideo from the page.
*/
remove() {
ReactDOM.unmountComponentAtNode(this.container);
super.remove();
this.removePresenceLabel();
this.removeRemoteVideoMenu();
}
/**
@@ -212,16 +295,19 @@ export default class RemoteVideo extends SmallVideo {
const isVideo = stream.isVideoTrack();
if (isVideo) {
this.videoStream = stream;
} else {
this.audioStream = stream;
}
if (!stream.getOriginalStream()) {
logger.debug('Remote video stream has no original stream');
return;
}
let streamElement = document.createElement('video');
streamElement.autoplay = !config.testing?.noAutoPlayVideo;
streamElement.id = `remoteVideo_${stream.getId()}`;
let streamElement = SmallVideo.createStreamElement(stream);
// Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement);
@@ -229,7 +315,14 @@ export default class RemoteVideo extends SmallVideo {
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
if (isVideo && isTestModeEnabled(APP.store.getState())) {
if (!isVideo) {
this._audioStreamElement = streamElement;
// If the remote video menu was created before the audio stream was
// attached we need to update the menu in order to show the volume
// slider.
this.updateRemoteVideoMenu();
} else if (isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
@@ -238,4 +331,72 @@ export default class RemoteVideo extends SmallVideo {
});
}
}
/**
* Triggers re-rendering of the display name using current instance state.
*
* @returns {void}
*/
updateDisplayName() {
if (!this.container) {
logger.warn(`Unable to set displayName - ${this.videoSpanId} does not exist`);
return;
}
this._renderDisplayName({
elementID: `${this.videoSpanId}_name`,
participantID: this.id
});
}
/**
* Removes remote video menu element from video element identified by
* given <tt>videoElementId</tt>.
*
* @param videoElementId the id of local or remote video element.
*/
removeRemoteVideoMenu() {
const menuSpan = this.$container.find('.remotevideomenu');
if (menuSpan.length) {
ReactDOM.unmountComponentAtNode(menuSpan.get(0));
menuSpan.remove();
}
}
/**
* Mounts the {@code PresenceLabel} for displaying the participant's current
* presence status.
*
* @return {void}
*/
addPresenceLabel() {
const presenceLabelContainer = this.container.querySelector('.presence-label-container');
if (presenceLabelContainer) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<PresenceLabel
participantID = { this.id }
className = 'presence-label' />
</I18nextProvider>
</Provider>,
presenceLabelContainer);
}
}
/**
* Unmounts the {@code PresenceLabel} component.
*
* @return {void}
*/
removePresenceLabel() {
const presenceLabelContainer = this.container.querySelector('.presence-label-container');
if (presenceLabelContainer) {
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
}
}
}

View File

@@ -1,4 +1,4 @@
/* global $, APP, interfaceConfig */
/* global $, APP, config, interfaceConfig */
/* eslint-disable no-unused-vars */
import { AtlasKitThemeProvider } from '@atlaskit/theme';
@@ -20,7 +20,6 @@ import {
pinParticipant
} from '../../../react/features/base/participants';
import {
getLocalVideoTrack,
getTrackByMediaTypeAndParticipant,
isLocalTrackMuted,
isRemoteTrackMuted
@@ -29,7 +28,6 @@ import { ConnectionIndicator } from '../../../react/features/connection-indicato
import { DisplayName } from '../../../react/features/display-name';
import {
DominantSpeakerIndicator,
isVideoPlayable,
RaisedHandIndicator,
StatusIndicators
} from '../../../react/features/filmstrip';
@@ -91,10 +89,36 @@ export default class SmallVideo {
* Constructor.
*/
constructor(VideoLayout) {
this.videoStream = null;
this.audioStream = null;
this.VideoLayout = VideoLayout;
this.videoIsHovered = false;
this.videoType = undefined;
/**
* Whether or not the connection indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showConnectionIndicator = !interfaceConfig.CONNECTION_INDICATOR_DISABLED;
/**
* Whether or not the dominant speaker indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showDominantSpeaker = false;
/**
* Whether or not the raised hand indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showRaisedHand = false;
// Bind event handlers so they are only bound once for every instance.
this.updateView = this.updateView.bind(this);
@@ -120,6 +144,33 @@ export default class SmallVideo {
return this.$container.is(':visible');
}
/**
* Creates an audio or video element for a particular MediaStream.
*/
static createStreamElement(stream) {
const isVideo = stream.isVideoTrack();
const element = isVideo ? document.createElement('video') : document.createElement('audio');
if (isVideo) {
element.setAttribute('muted', 'true');
element.setAttribute('playsInline', 'true'); /* for Safari on iOS to work */
} else if (config.startSilent) {
element.muted = true;
}
element.autoplay = !config.testing?.noAutoPlayVideo;
element.id = SmallVideo.getStreamElementID(stream);
return element;
}
/**
* Returns the element id for a particular MediaStream.
*/
static getStreamElementID(stream) {
return (stream.isVideoTrack() ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
}
/**
* Configures hoverIn/hoverOut handlers. Depends on connection indicator.
*/
@@ -128,22 +179,103 @@ export default class SmallVideo {
this.$container.hover(
() => {
this.videoIsHovered = true;
this.renderThumbnail(true);
this.updateView();
this.updateIndicators();
},
() => {
this.videoIsHovered = false;
this.renderThumbnail(false);
this.updateView();
this.updateIndicators();
}
);
}
/**
* Renders the thumbnail.
* Unmounts the ConnectionIndicator component.
* @returns {void}
*/
removeConnectionIndicator() {
this._showConnectionIndicator = false;
this.updateIndicators();
}
/**
* Create or updates the ReactElement for displaying status indicators about
* audio mute, video mute, and moderator status.
*
* @returns {void}
*/
renderThumbnail() {
// Should be implemented by in subclasses.
updateStatusBar() {
const statusBarContainer = this.container.querySelector('.videocontainer__toolbar');
if (!statusBarContainer) {
return;
}
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<StatusIndicators
participantID = { this.id } />
</I18nextProvider>
</Provider>,
statusBarContainer);
}
/**
* Adds the element indicating the audio level of the participant.
*
* @returns {void}
*/
addAudioLevelIndicator() {
let audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) {
return;
}
audioLevelContainer = document.createElement('span');
audioLevelContainer.className = 'audioindicator-container';
this.container.appendChild(audioLevelContainer);
this.updateAudioLevelIndicator();
}
/**
* Removes the element indicating the audio level of the participant.
*
* @returns {void}
*/
removeAudioLevelIndicator() {
const audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) {
ReactDOM.unmountComponentAtNode(audioLevelContainer);
}
}
/**
* Updates the audio level for this small video.
*
* @param lvl the new audio level to set
* @returns {void}
*/
updateAudioLevelIndicator(lvl = 0) {
const audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) {
ReactDOM.render(<AudioLevelIndicator audioLevel = { lvl }/>, audioLevelContainer);
}
}
/**
* Queries the component's DOM for the element that should be the parent to the
* AudioLevelIndicator.
*
* @returns {HTMLElement} The DOM element that holds the AudioLevelIndicator.
*/
_getAudioLevelContainer() {
return this.container.querySelector('.audioindicator-container');
}
/**
@@ -160,6 +292,62 @@ export default class SmallVideo {
return $($(this.container).find('video')[0]);
}
/**
* Selects the HTML image element which displays user's avatar.
*
* @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image
* element which displays the user's avatar.
*/
$avatar() {
return this.$container.find('.avatar-container');
}
/**
* Returns the display name element, which appears on the video thumbnail.
*
* @return {jQuery} a jQuery selector pointing to the display name element of
* the video thumbnail
*/
$displayName() {
return this.$container.find('.displayNameContainer');
}
/**
* Creates or updates the participant's display name that is shown over the
* video preview.
*
* @param {Object} props - The React {@code Component} props to pass into the
* {@code DisplayName} component.
* @returns {void}
*/
_renderDisplayName(props) {
const displayNameContainer = this.container.querySelector('.displayNameContainer');
if (displayNameContainer) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<DisplayName { ...props } />
</I18nextProvider>
</Provider>,
displayNameContainer);
}
}
/**
* Removes the component responsible for showing the participant's display name,
* if its container is present.
*
* @returns {void}
*/
removeDisplayName() {
const displayNameContainer = this.container.querySelector('.displayNameContainer');
if (displayNameContainer) {
ReactDOM.unmountComponentAtNode(displayNameContainer);
}
}
/**
* Enables / disables the css responsible for focusing/pinning a video
* thumbnail.
@@ -203,7 +391,18 @@ export default class SmallVideo {
* or <tt>false</tt> otherwise.
*/
isVideoPlayable() {
return isVideoPlayable(APP.store.getState(), this.id);
const state = APP.store.getState();
const tracks = state['features/base/tracks'];
const participant = this.id ? getParticipantById(state, this.id) : getLocalParticipant(state);
let isVideoMuted = true;
if (participant?.local) {
isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, this.id);
}
return this.videoStream && !isVideoMuted && !APP.conference.isAudioOnly();
}
/**
@@ -236,15 +435,13 @@ export default class SmallVideo {
let isScreenSharing = false;
let connectionStatus;
const state = APP.store.getState();
const id = this.id;
const participant = getParticipantById(state, id);
const isLocal = participant?.local ?? true;
const tracks = state['features/base/tracks'];
const videoTrack
= isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
const participant = getParticipantById(state, this.id);
if (typeof participant !== 'undefined' && !participant.isFakeParticipant && !participant.local) {
isScreenSharing = typeof track !== 'undefined' && videoTrack?.videoType === 'desktop';
const tracks = state['features/base/tracks'];
const track = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, this.id);
isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop';
connectionStatus = participant.connectionStatus;
}
@@ -257,9 +454,9 @@ export default class SmallVideo {
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus,
canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(videoTrack),
videoStream: Boolean(this.videoStream),
isScreenSharing,
videoStreamMuted: videoTrack ? videoTrack.muted : 'no stream'
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
};
}
@@ -317,6 +514,43 @@ export default class SmallVideo {
}
}
/**
* Updates the react component displaying the avatar with the passed in avatar
* url.
*
* @returns {void}
*/
initializeAvatar() {
const thumbnail = this.$avatar().get(0);
if (thumbnail) {
// Maybe add a special case for local participant, as on init of
// LocalVideo.js the id is set to "local" but will get updated later.
ReactDOM.render(
<Provider store = { APP.store }>
<AvatarDisplay
className = 'userAvatar'
participantId = { this.id } />
</Provider>,
thumbnail
);
}
}
/**
* Unmounts any attached react components (particular the avatar image) from
* the avatar container.
*
* @returns {void}
*/
removeAvatar() {
const thumbnail = this.$avatar().get(0);
if (thumbnail) {
ReactDOM.unmountComponentAtNode(thumbnail);
}
}
/**
* Shows or hides the dominant speaker indicator.
* @param show whether to show or hide.
@@ -333,8 +567,30 @@ export default class SmallVideo {
return;
}
if (this._showDominantSpeaker === show) {
return;
}
this.$container.toggleClass('active-speaker', show);
this._showDominantSpeaker = show;
this.$container.toggleClass('active-speaker', this._showDominantSpeaker);
this.updateIndicators();
this.updateView();
}
/**
* Shows or hides the raised hand indicator.
* @param show whether to show or hide.
*/
showRaisedHandIndicator(show) {
if (!this.container) {
logger.warn(`Unable to raised hand indication - ${
this.videoSpanId} does not exist`);
return;
}
this._showRaisedHand = show;
this.updateIndicators();
}
/**
@@ -365,7 +621,19 @@ export default class SmallVideo {
*/
remove() {
logger.log('Remove thumbnail', this.id);
this._unmountThumbnail();
this.removeAudioLevelIndicator();
const toolbarContainer
= this.container.querySelector('.videocontainer__toolbar');
if (toolbarContainer) {
ReactDOM.unmountComponentAtNode(toolbarContainer);
}
this.removeConnectionIndicator();
this.removeDisplayName();
this.removeAvatar();
this._unmountIndicators();
// Remove whole container
if (this.container.parentNode) {
@@ -380,9 +648,76 @@ export default class SmallVideo {
* @returns {void}
*/
rerender() {
this.updateIndicators();
this.updateStatusBar();
this.updateView();
}
/**
* Updates the React element responsible for showing connection status, dominant
* speaker, and raised hand icons. Uses instance variables to get the necessary
* state to display. Will create the React element if not already created.
*
* @private
* @returns {void}
*/
updateIndicators() {
const indicatorToolbar = this.container.querySelector('.videocontainer__toptoolbar');
if (!indicatorToolbar) {
return;
}
const { NORMAL = 8 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
const iconSize = NORMAL;
const showConnectionIndicator = this.videoIsHovered || !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
const state = APP.store.getState();
const currentLayout = getCurrentLayout(state);
const participantCount = getParticipantCount(state);
let statsPopoverPosition, tooltipPosition;
if (currentLayout === LAYOUTS.TILE_VIEW) {
statsPopoverPosition = 'right top';
tooltipPosition = 'right';
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
statsPopoverPosition = this.statsPopoverLocation;
tooltipPosition = 'left';
} else {
statsPopoverPosition = this.statsPopoverLocation;
tooltipPosition = 'top';
}
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<div>
<AtlasKitThemeProvider mode = 'dark'>
{ this._showConnectionIndicator
? <ConnectionIndicator
alwaysVisible = { showConnectionIndicator }
iconSize = { iconSize }
isLocalVideo = { this.isLocal }
enableStatsDisplay = { true }
participantId = { this.id }
statsPopoverPosition = { statsPopoverPosition } />
: null }
<RaisedHandIndicator
iconSize = { iconSize }
participantId = { this.id }
tooltipPosition = { tooltipPosition } />
{ this._showDominantSpeaker && participantCount > 2
? <DominantSpeakerIndicator
iconSize = { iconSize }
tooltipPosition = { tooltipPosition } />
: null }
</AtlasKitThemeProvider>
</div>
</I18nextProvider>
</Provider>,
indicatorToolbar
);
}
/**
* Callback invoked when the thumbnail is clicked and potentially trigger
* pinning of the participant.
@@ -440,10 +775,18 @@ export default class SmallVideo {
}
/**
* Unmounts the thumbnail.
* Removes the React element responsible for showing connection status, dominant
* speaker, and raised hand icons.
*
* @private
* @returns {void}
*/
_unmountThumbnail() {
ReactDOM.unmountComponentAtNode(this.container);
_unmountIndicators() {
const indicatorToolbar = this.container.querySelector('.videocontainer__toptoolbar');
if (indicatorToolbar) {
ReactDOM.unmountComponentAtNode(indicatorToolbar);
}
}
/**
@@ -457,6 +800,10 @@ export default class SmallVideo {
switch (layout) {
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
this.$container.css('padding-top', `${heightToWidthPercent}%`);
this.$avatar().css({
height: '50%',
width: `${heightToWidthPercent / 2}%`
});
break;
}
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
@@ -466,6 +813,7 @@ export default class SmallVideo {
if (typeof size !== 'undefined') {
const { height, width } = size;
const avatarSize = height / 2;
this.$container.css({
height: `${height}px`,
@@ -473,6 +821,10 @@ export default class SmallVideo {
'min-width': `${width}px`,
width: `${width}px`
});
this.$avatar().css({
height: `${avatarSize}px`,
width: `${avatarSize}px`
});
}
break;
}
@@ -482,6 +834,7 @@ export default class SmallVideo {
if (typeof thumbnailSize !== 'undefined') {
const { height, width } = thumbnailSize;
const avatarSize = height / 2;
this.$container.css({
height: `${height}px`,
@@ -489,6 +842,10 @@ export default class SmallVideo {
'min-width': `${width}px`,
width: `${width}px`
});
this.$avatar().css({
height: `${avatarSize}px`,
width: `${avatarSize}px`
});
}
break;
}

View File

@@ -116,6 +116,12 @@ const VideoLayout = {
* @param lvl the new audio level to update to
*/
setAudioLevel(id, lvl) {
const smallVideo = this.getSmallVideo(id);
if (smallVideo) {
smallVideo.updateAudioLevelIndicator(lvl);
}
if (largeVideo && id === largeVideo.id) {
largeVideo.updateLargeVideoAudioLevel(lvl);
}
@@ -131,6 +137,19 @@ const VideoLayout = {
this._updateLargeVideoIfDisplayed(localId);
},
/**
* Get's the localID of the conference and set it to the local video
* (small one). This needs to be called as early as possible, when muc is
* actually joined. Otherwise events can come with information like email
* and setting them assume the id is already set.
*/
mucJoined() {
// FIXME: replace this call with a generic update call once SmallVideo
// only contains a ReactElement. Then remove this call once the
// Filmstrip is fully in React.
localVideoThumbnail.updateIndicators();
},
/**
* Shows/hides local video.
* @param {boolean} true to make the local video visible, false - otherwise
@@ -153,8 +172,11 @@ const VideoLayout = {
remoteVideo.addRemoteStreamElement(stream);
this.onVideoMute(id);
remoteVideo.updateView();
// Make sure track's muted state is reflected
if (stream.getType() !== 'audio') {
this.onVideoMute(id);
remoteVideo.updateView();
}
},
onRemoteStreamRemoved(stream) {
@@ -162,12 +184,13 @@ const VideoLayout = {
const remoteVideo = remoteVideos[id];
// Remote stream may be removed after participant left the conference.
if (remoteVideo) {
remoteVideo.removeRemoteStreamElement(stream);
remoteVideo.updateView();
}
this.updateVideoMutedForNoTracks(id);
this.updateMutedForNoTracks(id, stream.getType());
},
/**
@@ -176,12 +199,19 @@ const VideoLayout = {
*
* If participant has no tracks will make the UI display muted status.
* @param {string} participantId
* @param {string} mediaType 'audio' or 'video'
*/
updateVideoMutedForNoTracks(participantId) {
updateMutedForNoTracks(participantId, mediaType) {
const participant = APP.conference.getParticipantById(participantId);
if (participant && !participant.getTracksByMediaType('video').length) {
APP.UI.setVideoMuted(participantId);
if (participant && !participant.getTracksByMediaType(mediaType).length) {
if (mediaType === 'audio') {
APP.UI.setAudioMuted(participantId, true);
} else if (mediaType === 'video') {
APP.UI.setVideoMuted(participantId);
} else {
logger.error(`Unsupported media type: ${mediaType}`);
}
}
},
@@ -264,7 +294,9 @@ const VideoLayout = {
const remoteVideo = new RemoteVideo(jitsiParticipant, VideoLayout);
this.addRemoteVideoContainer(id, remoteVideo);
this.updateVideoMutedForNoTracks(id);
this.updateMutedForNoTracks(id, 'audio');
this.updateMutedForNoTracks(id, 'video');
},
/**
@@ -299,6 +331,22 @@ const VideoLayout = {
this._updateLargeVideoIfDisplayed(id, true);
},
/**
* Display name changed.
*/
onDisplayNameChanged(id) {
if (id === 'localVideoContainer'
|| APP.conference.isLocalId(id)) {
localVideoThumbnail.updateDisplayName();
} else {
const remoteVideo = remoteVideos[id];
if (remoteVideo) {
remoteVideo.updateDisplayName();
}
}
},
/**
* On dominant speaker changed event.
*
@@ -365,6 +413,20 @@ const VideoLayout = {
}
},
/**
* Hides all the indicators
*/
hideStats() {
for (const video in remoteVideos) { // eslint-disable-line guard-for-in
const remoteVideo = remoteVideos[video];
if (remoteVideo) {
remoteVideo.removeConnectionIndicator();
}
}
localVideoThumbnail.removeConnectionIndicator();
},
removeParticipantContainer(id) {
// Unlock large video
if (this.getPinnedId() === id) {
@@ -415,6 +477,15 @@ const VideoLayout = {
},
changeUserAvatar(id, avatarUrl) {
const smallVideo = VideoLayout.getSmallVideo(id);
if (smallVideo) {
smallVideo.initializeAvatar();
} else {
logger.warn(
`Missed avatar update - no small video yet for ${id}`
);
}
if (this.isCurrentlyOnLarge(id)) {
largeVideo.updateAvatar(avatarUrl);
}

8
package-lock.json generated
View File

@@ -10776,8 +10776,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"from": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"version": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
"from": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -10806,10 +10806,6 @@
"js-md5": "0.7.3"
}
},
"jitsi-meet-logger": {
"version": "github:jitsi/jitsi-meet-logger#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"from": "github:jitsi/jitsi-meet-logger#v1.0.0"
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",

View File

@@ -56,7 +56,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",

View File

@@ -128,6 +128,9 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
titleKey: 'dialog.sessTerminated'
}));
if (typeof APP !== 'undefined') {
APP.UI.hideStats();
}
break;
}
case JitsiConferenceErrors.CONNECTION_ERROR: {

View File

@@ -91,6 +91,7 @@ export default [
'disableRtx',
'disableSimulcast',
'disableThirdPartyRequests',
'displayJids',
'doNotStoreRoom',
'e2eping',
'enableDisplayNameInStats',

View File

@@ -1,211 +0,0 @@
// @flow
import React, { Component } from 'react';
/**
* The type of the React {@code Component} props of {@link Video}.
*/
type Props = {
/**
* The value of the id attribute of the video. Used by the torture tests to
* locate video elements.
*/
id: string,
/**
* The audio track.
*/
audioTrack: ?Object,
/**
* Used to determine the value of the autoplay attribute of the underlying
* audio element.
*/
autoPlay: boolean,
muted: ?Boolean,
volume: ?number,
onAudioElementReferenceChanged: Function
};
/**
* The React/Web {@link Component} which is similar to and wraps around
* {@code HTMLAudioElement} in order to facilitate cross-platform source code.
*/
export default class AudioTrack extends Component<Props> {
/**
* Reference to the HTML audio element, stored until the file is ready.
*/
_ref: ?HTMLAudioElement;
/**
* Default values for {@code AudioTrack} component's properties.
*
* @static
*/
static defaultProps = {
autoPlay: true,
id: ''
};
/**
* Creates new <code>Audio</code> element instance with given props.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._setRef = this._setRef.bind(this);
}
/**
* Invokes the library for rendering the video on initial display. Sets the
* volume level to zero to ensure no sound plays.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
this._attachTrack(this.props.audioTrack);
if (this._ref) {
const { autoPlay, muted, volume } = this.props;
if (autoPlay) {
// Ensure the audio gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case video does not autoplay.
this._ref.play();
}
if (typeof volume === 'number') {
this._ref.volume = volume;
}
if (typeof muted === 'boolean') {
this._ref.muted = muted;
}
}
}
/**
* Remove any existing associations between the current video track and the
* component's video element.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
this._detachTrack(this.props.audioTrack);
}
/**
* Updates the video display only if a new track is added. This component's
* updating is blackboxed from React to prevent re-rendering of video
* element, as the lib uses {@code track.attach(videoElement)} instead.
*
* @inheritdoc
* @returns {boolean} - False is always returned to blackbox this component
* from React.
*/
shouldComponentUpdate(nextProps: Props) {
const currentJitsiTrack = this.props.audioTrack && this.props.audioTrack.jitsiTrack;
const nextJitsiTrack = nextProps.audioTrack && nextProps.audioTrack.jitsiTrack;
if (currentJitsiTrack !== nextJitsiTrack) {
this._detachTrack(this.props.audioTrack);
this._attachTrack(nextProps.audioTrack);
}
if (this._ref) {
const currentVolume = this._ref.volume;
const nextVolume = nextProps.volume;
if (typeof nextVolume === 'number' && currentVolume !== nextVolume) {
this._ref.volume = nextVolume;
}
const currentMuted = this._ref.muted;
const nextMuted = nextProps.muted;
if (typeof nextMuted === 'boolean' && currentMuted !== nextVolume) {
this._ref.muted = nextMuted;
}
}
return false;
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { autoPlay, id } = this.props;
return (
<audio
autoPlay = { autoPlay }
id = { id }
ref = { this._setRef } />
);
}
/**
* Calls into the passed in track to associate the track with the component's audio element.
*
* @param {Object} track - The redux representation of the {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_attachTrack(track) {
if (!track || !track.jitsiTrack) {
return;
}
track.jitsiTrack.attach(this._ref);
}
/**
* Removes the association to the component's audio element from the passed
* in redux representation of jitsi audio track.
*
* @param {Object} track - The redux representation of the {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_detachTrack(track) {
if (this._ref && track && track.jitsiTrack) {
track.jitsiTrack.detach(this._ref);
}
}
_setRef: (?HTMLAudioElement) => void;
/**
* Sets the reference to the HTML audio element.
*
* @param {HTMLAudioElement} audioElement - The HTML audio element instance.
* @private
* @returns {void}
*/
_setRef(audioElement: ?HTMLAudioElement) {
this._ref = audioElement;
const { onAudioElementReferenceChanged } = this.props;
if (this._ref && onAudioElementReferenceChanged) {
onAudioElementReferenceChanged({ volume: this._ref.volume });
}
}
}

View File

@@ -99,13 +99,6 @@ class Video extends Component<Props> {
}
this._attachTrack(this.props.videoTrack);
if (this._videoElement && this.props.autoPlay) {
// Ensure the video gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case video does not autoplay.
this._videoElement.play();
}
}
/**
@@ -149,8 +142,6 @@ class Video extends Component<Props> {
* @returns {ReactElement}
*/
render() {
// NOTE: Maybe we should render null if we don't have video track or if the video track has ended.
return (
<video
autoPlay = { this.props.autoPlay }

View File

@@ -1,612 +0,0 @@
// @flow
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React, { Component } from 'react';
import { AudioLevelIndicator } from '../../../audio-level-indicator';
import { Avatar } from '../../../base/avatar';
import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
import AudioTrack from '../../../base/media/components/web/AudioTrack';
import {
getLocalParticipant,
getParticipantById,
getParticipantCount
} from '../../../base/participants';
import { connect } from '../../../base/redux';
import { getLocalAudioTrack, getLocalVideoTrack, getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import { ConnectionIndicator } from '../../../connection-indicator';
import { DisplayName } from '../../../display-name';
import { StatusIndicators, RaisedHandIndicator, DominantSpeakerIndicator } from '../../../filmstrip';
import { PresenceLabel } from '../../../presence-status';
import { RemoteVideoMenuTriggerButton } from '../../../remote-video-menu';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
const JitsiTrackEvents = JitsiMeetJS.events.track;
declare var interfaceConfig: Object;
type State = {
audioLevel: number,
volume: ?number
};
/**
* The type of the React {@code Component} props of {@link Thumbnail}.
*/
type Props = {
/**
* The current layout of the filmstrip.
*/
_currentLayout: string,
_height: number,
_heightToWidthPercent: number,
/**
* The video track that will be displayed in the thumbnail.
*/
_videoTrack: ?Object,
/**
* The audio track related to the participant.
*/
_audioTrack: ?Object,
_width: number,
/**
* The ID of the participant related to the thumbnaul.
*/
_participant: Object,
_defaultLocalDisplayName: string,
_disableProfile: boolean,
_participantCount: number,
_isFilmstripOnly: boolean,
_connectionIndicatorDisabled: boolean,
_connectionIndicatorAutoHideEnabled: boolean,
_isDominantSpeakerDisabled: boolean,
_startSilent: Boolean,
participantID: ?string,
isHovered: ?boolean,
dispatch: Function
};
/**
* TODO.
*
* @returns {number}
*/
function _getIndicatorsIconSize() {
const { NORMAL = 8 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
return NORMAL;
}
/**
* Implements a thumbnail.
*
* @extends Component
*/
class Thumbnail extends Component<Props, State> {
/**
* Initializes a new Thumbnail instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
*/
constructor(props: Props) {
super(props);
this.state = {
audioLevel: 0,
volume: undefined
};
this._updateAudioLevel = this._updateAudioLevel.bind(this);
this._onVolumeChange = this._onVolumeChange.bind(this);
this._onAudioElementReferenceChanged = this._onAudioElementReferenceChanged.bind(this);
}
/**
* Starts listening for audio level updates after the initial render.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
this._listenForAudioUpdates();
}
/**
* Stops listening for audio level updates on the old track and starts
* listening instead on the new track.
*
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps: Props) {
if (prevProps._audioTrack !== this.props._audioTrack) {
this._stopListeningForAudioUpdates(prevProps._audioTrack);
this._listenForAudioUpdates();
this._updateAudioLevel(0);
}
}
/**
* Unsubscribe from audio level updates.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
this._stopListeningForAudioUpdates(this.props._audioTrack);
}
/**
* Starts listening for audio level updates from the library.
*
* @private
* @returns {void}
*/
_listenForAudioUpdates() {
const { _audioTrack } = this.props;
if (_audioTrack) {
const { jitsiTrack } = _audioTrack;
jitsiTrack && jitsiTrack.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateAudioLevel);
}
}
/**
* Stops listening to further updates from the passed track.
*
* @param {Object} audioTrack - The track.
* @private
* @returns {void}
*/
_stopListeningForAudioUpdates(audioTrack) {
if (audioTrack) {
const { jitsiTrack } = audioTrack;
jitsiTrack && jitsiTrack.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateAudioLevel);
}
}
_updateAudioLevel: (number) => void;
/**
* Updates the internal state of the last know audio level. The level should
* be between 0 and 1, as the level will be used as a percentage out of 1.
*
* @param {number} audioLevel - The new audio level for the track.
* @private
* @returns {void}
*/
_updateAudioLevel(audioLevel) {
this.setState({
audioLevel
});
}
/**
* Returns an object with the styles for the video container and the avatar container.
*
* @returns {Object} - The styles for the video container and the avatar container.
*/
_getStyles(): Object {
const { _height, _heightToWidthPercent, _currentLayout } = this.props;
let styles;
switch (_currentLayout) {
case LAYOUTS.TILE_VIEW:
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
const avatarSize = _height / 2;
styles = {
avatarContainer: {
height: `${avatarSize}px`,
width: `${avatarSize}px`
}
};
break;
}
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
styles = {
avatarContainer: {
height: '50%',
width: `${_heightToWidthPercent / 2}%`
}
};
break;
}
}
return styles;
}
/**
* Renders a fake participant (youtube video) thumbnail.
*
* @param {string} id - The id of the participant.
* @returns {ReactElement}
*/
_renderFakeParticipant(id) {
return (
<>
<img
className = 'sharedVideoAvatar'
src = { `https://img.youtube.com/vi/${id}/0.jpg` } />
<div className = 'displayNameContainer'>
<DisplayName
elementID = 'sharedVideoContainer_name'
participantID = { id } />
</div>
</>
);
}
/**
* Renders the local participant's thumbnail.
*
* @param {string} id - The ID of the participant.
* @returns {ReactElement}
*/
_renderLocalParticipant(id) {
const styles = this._getStyles();
const {
_participant,
_participantCount,
_videoTrack,
_defaultLocalDisplayName,
_disableProfile,
_isFilmstripOnly,
_isDominantSpeakerDisabled,
_connectionIndicatorDisabled,
_connectionIndicatorAutoHideEnabled,
_currentLayout
} = this.props;
const { audioLevel = 0 } = this.state;
const iconSize = _getIndicatorsIconSize();
const showConnectionIndicator = this.props.isHovered || !_connectionIndicatorAutoHideEnabled;
const { dominantSpeaker = false } = _participant;
const showDominantSpeaker = !_isDominantSpeakerDisabled && dominantSpeaker;
let statsPopoverPosition, tooltipPosition;
switch (_currentLayout) {
case LAYOUTS.TILE_VIEW:
statsPopoverPosition = 'right top';
tooltipPosition = 'right';
break;
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
statsPopoverPosition = 'left top';
tooltipPosition = 'left';
break;
default:
statsPopoverPosition = 'top center';
tooltipPosition = 'top';
}
return (
<>
<div className = 'videocontainer__background' />
<span id = 'localVideoWrapper'>
<VideoTrack
id = 'localVideo_container'
videoTrack = { _videoTrack } />
</span>
<div className = 'videocontainer__toolbar'>
<StatusIndicators participantID = { id } />
</div>
<div className = 'videocontainer__toptoolbar'>
<div>
<AtlasKitThemeProvider mode = 'dark'>
{ _connectionIndicatorDisabled
? null
: <ConnectionIndicator
alwaysVisible = { showConnectionIndicator }
enableStatsDisplay = { !_isFilmstripOnly }
iconSize = { iconSize }
isLocalVideo = { true }
participantId = { id }
statsPopoverPosition = { statsPopoverPosition } />
}
<RaisedHandIndicator
iconSize = { iconSize }
participantId = { id }
tooltipPosition = { tooltipPosition } />
{ showDominantSpeaker && _participantCount > 2
? <DominantSpeakerIndicator
iconSize = { iconSize }
tooltipPosition = { tooltipPosition } />
: null }
</AtlasKitThemeProvider>
</div>
</div>
<div className = 'videocontainer__hoverOverlay' />
<div className = 'displayNameContainer'>
<DisplayName
allowEditing = { !_disableProfile }
displayNameSuffix = { _defaultLocalDisplayName }
elementID = 'localDisplayName'
participantID = { _participant?.id } />
</div>
<div
className = 'avatar-container'
style = { styles.avatarContainer }>
<Avatar
className = 'userAvatar'
participantId = { id } />
</div>
<span className = 'audioindicator-container'>
<AudioLevelIndicator audioLevel = { audioLevel } />
</span>
</>
);
}
/**
* Renders a remote participant's 'thumbnail.
*
* @param {string} id - The id of the participant.
* @returns {ReactElement}
*/
_renderRemoteParticipant(id) {
const styles = this._getStyles();
const {
_audioTrack,
_participant,
_participantCount,
_isFilmstripOnly,
_currentLayout,
_connectionIndicatorDisabled,
_connectionIndicatorAutoHideEnabled,
_isDominantSpeakerDisabled,
_startSilent
} = this.props;
const { audioLevel = 0, volume = 1 } = this.state;
const showConnectionIndicator = this.props.isHovered || !_connectionIndicatorAutoHideEnabled;
const { dominantSpeaker = false } = _participant;
const showDominantSpeaker = !_isDominantSpeakerDisabled && dominantSpeaker;
const iconSize = _getIndicatorsIconSize();
let statsPopoverPosition, tooltipPosition;
// hide volume when in silent mode
const onVolumeChange = _startSilent ? undefined : this._onVolumeChange;
const { jitsiTrack } = _audioTrack ?? {};
const audioTrackId = jitsiTrack && jitsiTrack.getId();
switch (_currentLayout) {
case LAYOUTS.TILE_VIEW:
statsPopoverPosition = 'right top';
tooltipPosition = 'right';
break;
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
statsPopoverPosition = 'left bottom';
tooltipPosition = 'left';
break;
default:
statsPopoverPosition = 'top center';
tooltipPosition = 'top';
}
return (
<>
{
_audioTrack
? <AudioTrack
audioTrack = { _audioTrack }
id = { `remoteAudio_${audioTrackId}` }
muted = { _startSilent }
onAudioElementReferenceChanged = { this._onAudioElementReferenceChanged }
volume = { this.state.volume } />
: null
}
<div className = 'videocontainer__background' />
<div className = 'videocontainer__toptoolbar'>
<div>
<AtlasKitThemeProvider mode = 'dark'>
{ _connectionIndicatorDisabled
? null
: <ConnectionIndicator
alwaysVisible = { showConnectionIndicator }
enableStatsDisplay = { !_isFilmstripOnly }
iconSize = { iconSize }
isLocalVideo = { false }
participantId = { id }
statsPopoverPosition = { statsPopoverPosition } />
}
<RaisedHandIndicator
iconSize = { iconSize }
participantId = { id }
tooltipPosition = { tooltipPosition } />
{ showDominantSpeaker && _participantCount > 2
? <DominantSpeakerIndicator
iconSize = { iconSize }
tooltipPosition = { tooltipPosition } />
: null }
</AtlasKitThemeProvider>
</div>
</div>
<div className = 'videocontainer__toolbar'>
<StatusIndicators participantID = { id } />
</div>
<div className = 'videocontainer__hoverOverlay' />
<div className = 'displayNameContainer'>
<DisplayName
elementID = { `participant_${id}_name` }
participantID = { id } />
</div>
<div
className = 'avatar-container'
style = { styles.avatarContainer }>
<Avatar
className = 'userAvatar'
participantId = { id } />
</div>
<div className = 'presence-label-container'>
<PresenceLabel
className = 'presence-label'
participantID = { id } />
</div>
<span className = 'remotevideomenu'>
<AtlasKitThemeProvider mode = 'dark'>
<RemoteVideoMenuTriggerButton
initialVolumeValue = { volume }
onVolumeChange = { onVolumeChange }
participantID = { id } />
</AtlasKitThemeProvider>
</span>
<span className = 'audioindicator-container'>
<AudioLevelIndicator audioLevel = { audioLevel } />
</span>
</>
);
}
_onAudioElementReferenceChanged: Object => void;
/**
* Handles audio element references changes by receiving some properties from the audio element.
*
* @param {Obejct} audioElementProps - Properties of the audio element.
* @returns {void}
*/
_onAudioElementReferenceChanged({ volume }) {
if (this.state.volume !== volume) {
this.setState({ volume });
}
}
_onVolumeChange: number => void;
/**
* Handles volume changes.
*
* @param {number} value - The new value for the volume.
* @returns {void}
*/
_onVolumeChange(value) {
this.setState({ volume: value });
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _participant } = this.props;
if (!_participant) {
return null;
}
const { id, isFakeParticipant, local = false } = _participant;
if (local) {
return this._renderLocalParticipant(id);
}
if (isFakeParticipant) {
return this._renderFakeParticipant(id);
}
return this._renderRemoteParticipant(id);
}
}
/**
* Maps (parts of) the redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The own props of the component.
* @private
* @returns {{
* _videoTrack: Object
* }}
*/
function _mapStateToProps(state, ownProps): Object {
const { participantID } = ownProps;
// Only the local participant won't have id for the time when the conference is not yet joined.
const participant = participantID ? getParticipantById(state, participantID) : getLocalParticipant(state);
const isLocal = participant?.local ?? true;
const _videoTrack = isLocal
? getLocalVideoTrack(state['features/base/tracks'])
: getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantID);
const _audioTrack = isLocal
? getLocalAudioTrack(state['features/base/tracks'])
: getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.AUDIO, participantID);
const _currentLayout = getCurrentLayout(state);
let size = {};
const { startSilent, disableProfile = false } = state['features/base/config'];
switch (_currentLayout) {
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
const {
horizontalViewDimensions = {
local: {},
remote: {}
}
} = state['features/filmstrip'];
const { local, remote } = horizontalViewDimensions;
const { width, height } = isLocal ? local : remote;
size = {
_width: width,
_height: height
};
break;
}
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
size = {
_heightToWidthPercent: isLocal
? 100 / interfaceConfig.LOCAL_THUMBNAIL_RATIO
: 100 / interfaceConfig.REMOTE_THUMBNAIL_RATIO
};
break;
case LAYOUTS.TILE_VIEW: {
const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
size = {
_width: width,
_height: height
};
break;
}
}
return {
_videoTrack,
_audioTrack,
_currentLayout,
_participant: participant,
_participantCount: getParticipantCount(state),
_isFilmstripOnly: interfaceConfig.filmStripOnly,
_defaultLocalDisplayName: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
_disableProfile: disableProfile,
_connectionIndicatorDisabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
_connectionIndicatorAutoHideEnabled: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
_startSilent: Boolean(startSilent),
...size
};
}
export default connect(_mapStateToProps)(Thumbnail);

View File

@@ -7,4 +7,3 @@ export { default as ModeratorIndicator } from './ModeratorIndicator';
export { default as RaisedHandIndicator } from './RaisedHandIndicator';
export { default as StatusIndicators } from './StatusIndicators';
export { default as VideoMutedIndicator } from './VideoMutedIndicator';
export { default as Thumbnail } from './Thumbnail';

View File

@@ -1,20 +1,10 @@
// @flow
import { JitsiParticipantConnectionStatus } from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media';
import {
getLocalParticipant,
getParticipantById,
getParticipantCountWithFake,
getPinnedParticipant
} from '../base/participants';
import { toState } from '../base/redux';
import {
getLocalVideoTrack,
getTrackByMediaTypeAndParticipant,
isLocalTrackMuted,
isRemoteTrackMuted
} from '../base/tracks';
import { TILE_ASPECT_RATIO } from './constants';
@@ -69,40 +59,6 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|| state['features/base/config'].disable1On1Mode);
}
/**
* Checks whether there is a playable video stream available for the user
* associated with this <tt>SmallVideo</tt>.
*
* @param {Object | Function} stateful - The Object or Function that can be
* resolved to a Redux state object with the toState function.
* @param {string} id - The id of the participant.
* @returns {boolean} <tt>true</tt> if there is a playable video stream available
* or <tt>false</tt> otherwise.
*/
export function isVideoPlayable(stateful: Object | Function, id: String) {
const state = toState(stateful);
const tracks = state['features/base/tracks'];
const participant = id ? getParticipantById(state, id) : getLocalParticipant(state);
let isVideoMuted = true;
const isLocal = participant?.local ?? true;
const { connectionStatus } = participant || {};
const videoTrack
= isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
const isAudioOnly = Boolean(state['features/base/audio-only'].enabled);
let isPlayable = false;
if (participant?.local) {
isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly;
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, id);
isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
&& connectionStatus === JitsiParticipantConnectionStatus.ACTIVE;
}
return isPlayable;
}
/**
* Calculates the size for thumbnails when in horizontal view layout.
*

View File

@@ -77,6 +77,11 @@ type Props = {
*/
initialVolumeValue: number,
/**
* Callback to invoke when the popover has been displayed.
*/
onMenuDisplay: Function,
/**
* Callback to invoke when changing the level of the participant's
* audio element.
@@ -106,6 +111,19 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
*/
_rootElement = null;
/**
* Initializes a new {#@code RemoteVideoMenuTriggerButton} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Object) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onShowRemoteMenu = this._onShowRemoteMenu.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
@@ -122,6 +140,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
return (
<Popover
content = { content }
onPopoverOpen = { this._onShowRemoteMenu }
position = { this.props._menuPosition }>
<span
className = 'popover-trigger remote-video-menu-trigger'>
@@ -134,6 +153,18 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
);
}
_onShowRemoteMenu: () => void;
/**
* Opens the {@code RemoteVideoMenu}.
*
* @private
* @returns {void}
*/
_onShowRemoteMenu() {
this.props.onMenuDisplay();
}
/**
* Creates a new {@code RemoteVideoMenu} with buttons for interacting with
* the remote participant.

View File

@@ -1,8 +1,7 @@
// @flow
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js';
import { CONFERENCE_WILL_LEAVE } from '../base/conference';
import { MEDIA_TYPE } from '../base/media/index.js';
import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../base/conference';
import {
DOMINANT_SPEAKER_CHANGED,
PARTICIPANT_JOINED,
@@ -34,6 +33,10 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case CONFERENCE_JOINED:
VideoLayout.mucJoined();
break;
case CONFERENCE_WILL_LEAVE:
VideoLayout.reset();
break;
@@ -74,13 +77,13 @@ MiddlewareRegistry.register(store => next => action => {
break;
case TRACK_ADDED:
if (!action.track.local && action.track.mediaType !== MEDIA_TYPE.AUDIO) {
if (!action.track.local) {
VideoLayout.onRemoteStreamAdded(action.track.jitsiTrack);
}
break;
case TRACK_REMOVED:
if (!action.track.local && action.track.mediaType !== MEDIA_TYPE.AUDIO) {
if (!action.track.local) {
VideoLayout.onRemoteStreamRemoved(action.track.jitsiTrack);
}

View File

@@ -2,15 +2,49 @@
set -e -u
if [[ ! -z $(git status -s --untracked-files=no) ]]; then
echo "Git tree is not clean, aborting!"
exit 1
fi
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" != "master" ]]; then
echo "Not on master, aborting!";
exit 1;
fi
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
LATEST_LJM_COMMIT=$(git ls-remote https://github.com/jitsi/lib-jitsi-meet.git HEAD | awk '{ print $1 }')
PID=$$
LJM_TMP="${TMPDIR:-/tmp}/ljm-${PID}"
pushd ${THIS_DIR}/..
npm install github:jitsi/lib-jitsi-meet#${LATEST_LJM_COMMIT}
git add package.json package-lock.json
git commit -m "chore(deps) lib-jitsi-meet@latest"
CURRENT_LJM_COMMIT=$(jq -r '.dependencies."lib-jitsi-meet"' package.json | cut -d "#" -f2)
popd
echo "Done! Now push your branch to GH and open a PR!"
git clone --branch master --single-branch --bare https://github.com/jitsi/lib-jitsi-meet ${LJM_TMP}
pushd ${LJM_TMP}
LATEST_LJM_COMMIT=$(git rev-parse HEAD)
LJM_COMMITS=$(git log --oneline --no-decorate --no-merges ${CURRENT_LJM_COMMIT}..HEAD --pretty=format:"%x2a%x20%s")
popd
if [[ "${CURRENT_LJM_COMMIT}" == "${LATEST_LJM_COMMIT}" ]]; then
echo "No need to update, already on the latest commit!"
rm -rf ${LJM_TMP}
exit 1
fi
GH_LINK="https://github.com/jitsi/lib-jitsi-meet/compare/${CURRENT_LJM_COMMIT}...${LATEST_LJM_COMMIT}"
pushd ${THIS_DIR}/..
EPOCH=$(date +%s)
NEW_BRANCH="update-ljm-${EPOCH}"
git checkout -b ${NEW_BRANCH}
npm install github:jitsi/lib-jitsi-meet#${LATEST_LJM_COMMIT}
git add package.json package-lock.json
git commit -m "chore(deps) lib-jitsi-meet@latest" -m "${LJM_COMMITS}" -m "${GH_LINK}"
git push origin ${NEW_BRANCH}
gh pr create --repo jitsi/jitsi-meet --fill
popd
rm -rf ${LJM_TMP}