mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-10 16:50:21 +00:00
Compare commits
6 Commits
react-thum
...
4602
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27d41604df | ||
|
|
99ac60ed74 | ||
|
|
4f52fd5e01 | ||
|
|
edf415a7da | ||
|
|
5637b37fd2 | ||
|
|
dc0c1f0d93 |
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -582,6 +582,6 @@ SPEC CHECKSUMS:
|
||||
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
|
||||
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
|
||||
|
||||
PODFILE CHECKSUM: f6626cd705333112182cedbe175ae2f9006e8874
|
||||
PODFILE CHECKSUM: e77f7134cda53f089046f29cb1f32b08c00c8a2e
|
||||
|
||||
COCOAPODS: 1.10.0
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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>
|
||||
@@ -21,7 +21,7 @@
|
||||
#import "ViewController.h"
|
||||
|
||||
@import Firebase;
|
||||
@import JitsiMeet;
|
||||
@import JitsiMeetSDK;
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#import "FIRUtilities.h"
|
||||
|
||||
@import JitsiMeet;
|
||||
@import JitsiMeetSDK;
|
||||
|
||||
@implementation FIRUtilities
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
@import UIKit;
|
||||
@import JitsiMeetSDK;
|
||||
|
||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
@import MobileCoreServices;
|
||||
@import Intents; // Needed for NSUserActivity suggestedInvocationPhrase
|
||||
|
||||
@import JitsiMeet;
|
||||
@import JitsiMeetSDK;
|
||||
|
||||
#import "Types.h"
|
||||
#import "ViewController.h"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
23
ios/sdk/src/JitsiMeetSDK.h
Normal file
23
ios/sdk/src/JitsiMeetSDK.h
Normal 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>
|
||||
@@ -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:
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
final class DragGestureController {
|
||||
|
||||
var insets: UIEdgeInsets = UIEdgeInsets.zero
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
public typealias AnimationCompletion = (Bool) -> Void
|
||||
|
||||
public protocol PiPViewCoordinatorDelegate: class {
|
||||
|
||||
@@ -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": "고해상도",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}%`
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -91,6 +91,7 @@ export default [
|
||||
'disableRtx',
|
||||
'disableSimulcast',
|
||||
'disableThirdPartyRequests',
|
||||
'displayJids',
|
||||
'doNotStoreRoom',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user