feat(iOS): screensharing support

The Jitsi team would like to thank @AliKarpuzoglu, @linuxpi and The Hopp Foundation for the initial effort and help throughout.
This commit is contained in:
Alex Bumbu 2021-03-05 17:33:53 +02:00 committed by GitHub
parent dcda89012e
commit 508f1e0da9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1201 additions and 82 deletions

View File

@ -0,0 +1,31 @@
/*
* Copyright @ 2021-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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern NSNotificationName const kBroadcastStartedNotification;
extern NSNotificationName const kBroadcastStoppedNotification;
@interface DarwinNotificationCenter: NSObject
+ (instancetype)sharedInstance;
- (void)postNotificationWithName:(NSNotificationName)name;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,50 @@
/*
* Copyright @ 2021-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 "DarwinNotificationCenter.h"
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
@implementation DarwinNotificationCenter {
CFNotificationCenterRef _notificationCenter;
}
+ (instancetype)sharedInstance {
static DarwinNotificationCenter *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
}
return self;
}
- (void)postNotificationWithName:(NSString*)name {
CFNotificationCenterPostNotification(_notificationCenter, (__bridge CFStringRef)name, NULL, NULL, true);
}
@end

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>JitsiMeetBroadcast Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.broadcast-services-upload</string>
<key>NSExtensionPrincipalClass</key>
<string>SampleHandler</string>
<key>RPBroadcastProcessMode</key>
<string>RPBroadcastProcessModeSampleBuffer</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.jitsi.meet.appgroup</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,21 @@
/*
* Copyright @ 2021-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 <ReplayKit/ReplayKit.h>
@interface SampleHandler : RPBroadcastSampleHandler
@end

View File

@ -0,0 +1,123 @@
/*
* Copyright @ 2021-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 "SampleHandler.h"
#import "SocketConnection.h"
#import "SampleUploader.h"
#import "DarwinNotificationCenter.h"
@interface SampleHandler ()
@property (nonatomic, retain) SocketConnection *clientConnection;
@property (nonatomic, retain) SampleUploader *uploader;
@end
@implementation SampleHandler
- (instancetype)init {
self = [super init];
if (self) {
self.clientConnection = [[SocketConnection alloc] initWithFilePath:self.socketFilePath];
[self setupConnection];
self.uploader = [[SampleUploader alloc] initWithConnection:self.clientConnection];
}
return self;
}
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
NSLog(@"broadcast started");
[[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStartedNotification];
[self openConnection];
}
- (void)broadcastPaused {
// User has requested to pause the broadcast. Samples will stop being delivered.
}
- (void)broadcastResumed {
// User has requested to resume the broadcast. Samples delivery will resume.
}
- (void)broadcastFinished {
// User has requested to finish the broadcast.
[[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStoppedNotification];
[self.clientConnection close];
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
static NSUInteger frameCount = 0;
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
// adjust frame rate by using every third frame
if (++frameCount%3 == 0 && self.uploader.isReady) {
[self.uploader sendSample:sampleBuffer];
}
break;
default:
break;
}
}
// MARK: Private Methods
- (NSString *)socketFilePath {
// the appGroupIdentifier must match the value provided in the app's info.plist for the RTCAppGroupIdentifier key
NSString *appGroupIdentifier = @"group.org.jitsi.meet.appgroup";
NSURL *sharedContainer = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupIdentifier];
NSString *socketFilePath = [[sharedContainer URLByAppendingPathComponent:@"rtc_SSFD"] path];
return socketFilePath;
}
- (void)setupConnection {
__weak __typeof(self) weakSelf = self;
self.clientConnection.didClose = ^(NSError *error) {
NSLog(@"client connection did close: %@", error);
if (error) {
[weakSelf finishBroadcastWithError:error];
}
else {
NSInteger JMScreenSharingStopped = 10001;
NSError *customError = [NSError errorWithDomain:RPRecordingErrorDomain
code:JMScreenSharingStopped
userInfo:@{NSLocalizedDescriptionKey: @"Screen sharing stopped"}];
[weakSelf finishBroadcastWithError:customError];
}
};
}
- (void)openConnection {
dispatch_queue_t queue = dispatch_queue_create("org.jitsi.meet.broadcast.connectTimer", 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
BOOL success = [self.clientConnection open];
if (success) {
dispatch_source_cancel(timer);
}
});
dispatch_resume(timer);
}
@end

View File

@ -0,0 +1,33 @@
/*
* Copyright @ 2021-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 <Foundation/Foundation.h>
#import <ReplayKit/ReplayKit.h>
NS_ASSUME_NONNULL_BEGIN
@class SocketConnection;
@interface SampleUploader : NSObject
@property (nonatomic, assign, readonly) BOOL isReady;
- (instancetype)initWithConnection:(SocketConnection *)connection;
- (void)sendSample:(CMSampleBufferRef)sampleBuffer;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,155 @@
/*
* Copyright @ 2021-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 <MessageUI/MessageUI.h>
#import <ReplayKit/ReplayKit.h>
#import "SampleUploader.h"
#import "SocketConnection.h"
static const NSInteger kBufferMaxLenght = 10 * 1024;
@interface SampleUploader ()
@property (nonatomic, assign) BOOL isReady;
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@property (nonatomic, strong) SocketConnection *connection;
@property (nonatomic, strong) CIContext *imageContext;
@property (nonatomic, strong) NSData *dataToSend;
@property (nonatomic, assign) NSUInteger byteIndex;
@end
@implementation SampleUploader
- (instancetype)initWithConnection:(SocketConnection *)connection {
self = [super init];
if (self) {
self.serialQueue = dispatch_queue_create("org.jitsi.meet.broadcast.sampleUploader", DISPATCH_QUEUE_SERIAL);
self.connection = connection;
[self setupConnection];
self.imageContext = [[CIContext alloc] initWithOptions:nil];
self.isReady = false;
}
return self;
}
- (void)sendSample:(CMSampleBufferRef)sampleBuffer {
self.isReady = false;
self.dataToSend = [self prepareSample:sampleBuffer];
self.byteIndex = 0;
dispatch_async(self.serialQueue, ^{
[self sendData];
});
}
// MARK: Private Methods
- (void)setupConnection {
__weak __typeof(self) weakSelf = self;
self.connection.didOpen = ^{
weakSelf.isReady = true;
};
self.connection.streamHasSpaceAvailable = ^{
dispatch_async(weakSelf.serialQueue, ^{
weakSelf.isReady = ![weakSelf sendData];
});
};
}
/**
This function downscales and converts to jpeg the provided sample buffer, then wraps the resulted image data into a CFHTTPMessageRef. Returns the serialized CFHTTPMessageRef.
*/
- (NSData *)prepareSample:(CMSampleBufferRef)sampleBuffer {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
CGFloat scaleFactor = 2;
size_t width = CVPixelBufferGetWidth(imageBuffer)/scaleFactor;
size_t height = CVPixelBufferGetHeight(imageBuffer)/scaleFactor;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1/scaleFactor, 1/scaleFactor);
NSData *bufferData = [self jpegDataFromPixelBuffer:imageBuffer withScaling:scaleTransform];
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
if (bufferData) {
CFHTTPMessageRef httpResponse = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", bufferData.length]);
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Width", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", width]);
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Height", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", height]);
CFHTTPMessageSetBody(httpResponse, (__bridge CFDataRef)bufferData);
CFDataRef serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse);
CFRelease(httpResponse);
return CFBridgingRelease(serializedMessage);
}
return nil;
}
- (BOOL)sendData {
if (!self.dataToSend) {
NSLog(@"no data to send");
return false;
}
NSUInteger bytesLeft = self.dataToSend.length - self.byteIndex;
NSInteger length = bytesLeft > kBufferMaxLenght ? kBufferMaxLenght : bytesLeft;
uint8_t buffer[length];
[self.dataToSend getBytes:&buffer range:NSMakeRange(self.byteIndex, length)];
length = [self.connection writeBufferToStream:buffer maxLength:length];
if (length > 0) {
self.byteIndex += length;
bytesLeft -= length;
if (bytesLeft == 0) {
NSLog(@"video sample processed successfully");
self.dataToSend = nil;
self.byteIndex = 0;
}
}
else {
NSLog(@"writeBufferToStream failure");
}
return true;
}
- (NSData *)jpegDataFromPixelBuffer:(CVPixelBufferRef)pixelBuffer withScaling:(CGAffineTransform)scaleTransform {
CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
image = [image imageByApplyingTransform:scaleTransform];
NSDictionary *options = @{(NSString *)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithFloat:1.0]};
NSData *imageData = [self.imageContext JPEGRepresentationOfImage:image
colorSpace:image.colorSpace
options:options];
return imageData;
}
@end

View File

@ -0,0 +1,34 @@
/*
* Copyright @ 2021-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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SocketConnection : NSObject
@property (nonatomic, copy, nullable) void (^didOpen)(void);
@property (nonatomic, copy, nullable) void (^didClose)(NSError*);
@property (nonatomic, copy, nullable) void (^streamHasSpaceAvailable)(void);
- (instancetype)initWithFilePath:(nonnull NSString *)filePath;
- (BOOL)open;
- (void)close;
- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,189 @@
/*
* Copyright @ 2021-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.
*/
#include <sys/socket.h>
#include <sys/un.h>
#import "SocketConnection.h"
@interface SocketConnection () <NSStreamDelegate>
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, strong) NSInputStream *inputStream;
@property (nonatomic, strong) NSOutputStream *outputStream;
@property (nonatomic, strong) NSThread *networkThread;
@end
@implementation SocketConnection {
int _socket;
struct sockaddr_un _socketAddr;
}
- (instancetype)initWithFilePath:(NSString *)path {
self = [super init];
if (self) {
self.filePath = path;
[self setupSocketWithFilePath:path];
[self setupNetworkThread];
}
return self;
}
- (BOOL)open {
NSLog(@"Open socket connection");
if (![[NSFileManager defaultManager] fileExistsAtPath:self.filePath]) {
NSLog(@"failure: socket file missing");
return false;
}
int status = connect(_socket, (struct sockaddr *)&_socketAddr, sizeof(_socketAddr));
if (status < 0) {
NSLog(@"failure: socket connect (%d)", status);
return false;
}
[self.networkThread start];
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, _socket, &readStream, &writeStream);
self.inputStream = (__bridge_transfer NSInputStream *)readStream;
self.inputStream.delegate = self;
[self.inputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
self.outputStream.delegate = self;
[self.outputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
[self performSelector:@selector(scheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
[self.inputStream open];
[self.outputStream open];
NSLog(@"read stream status: %ld", CFReadStreamGetStatus(readStream));
NSLog(@"write stream status: %ld", CFWriteStreamGetStatus(writeStream));
return true;
}
- (void)close {
[self performSelector:@selector(unscheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
self.inputStream.delegate = nil;
self.outputStream.delegate = nil;
[self.inputStream close];
[self.outputStream close];
[self.networkThread cancel];
}
- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length {
return [self.outputStream write:buffer maxLength:length];
}
// MARK: Private Methods
- (BOOL)isOpen {
return self.inputStream.streamStatus == NSStreamStatusOpen && self.outputStream.streamStatus == NSStreamStatusOpen;
}
- (void)setupSocketWithFilePath:(NSString*)path {
_socket = socket(AF_UNIX, SOCK_STREAM, 0);
memset(&_socketAddr, 0, sizeof(_socketAddr));
_socketAddr.sun_family = AF_UNIX;
strncpy(_socketAddr.sun_path, path.UTF8String, sizeof(_socketAddr.sun_path) - 1);
}
- (void)setupNetworkThread {
self.networkThread = [[NSThread alloc] initWithBlock:^{
do {
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
} while (![NSThread currentThread].isCancelled);
}];
self.networkThread.qualityOfService = NSQualityOfServiceUserInitiated;
}
- (void)scheduleStreams {
[self.inputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
[self.outputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
}
- (void)unscheduleStreams {
[self.inputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
[self.outputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
}
- (void)notifyDidClose:(NSError *)error {
if (self.didClose) {
self.didClose(error);
}
}
@end
#pragma mark - NSStreamDelegate
@implementation SocketConnection (NSStreamDelegate)
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(@"client stream open completed");
if (aStream == self.outputStream && self.didOpen) {
self.didOpen();
}
break;
case NSStreamEventHasBytesAvailable:
if (aStream == self.inputStream) {
uint8_t buffer;
NSInteger numberOfBytesRead = [(NSInputStream *)aStream read:&buffer maxLength:sizeof(buffer)];
if (!numberOfBytesRead && aStream.streamStatus == NSStreamStatusAtEnd) {
NSLog(@"server socket closed");
[self close];
[self notifyDidClose:nil];
}
}
break;
case NSStreamEventHasSpaceAvailable:
if (aStream == self.outputStream && self.streamHasSpaceAvailable) {
NSLog(@"client stream has space available");
self.streamHasSpaceAvailable();
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"client stream error occurred: %@", aStream.streamError);
[self close];
[self notifyDidClose:aStream.streamError];
break;
default:
break;
}
}
@end

View File

@ -8,6 +8,10 @@
<string>applinks:beta.meet.jit.si</string>
<string>applinks:meet.jit.si</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.jitsi.meet.appgroup</string>
</array>
<key>com.apple.developer.siri</key>
<true/>
</dict>

View File

@ -23,6 +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 */; };
4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */; };
4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EC49B8625BED71300E76218 /* ReplayKit.framework */; };
4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BBA25BEDAC100E76218 /* SampleHandler.m */; };
4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BCA25BEDB6400E76218 /* SocketConnection.m */; };
4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BD025BF19CF00E76218 /* SampleUploader.m */; };
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, ); }; };
@ -48,6 +54,13 @@
remoteGlobalIDString = 0BEA5C241F7B8F73000D0AB4;
remoteInfo = JitsiMeetCompanion;
};
4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4EC49BB525BEDAC100E76218;
remoteInfo = "JitsiMeetBroadcast Extension";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -85,6 +98,17 @@
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
4EC49B9025BED71300E76218 /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@ -115,6 +139,18 @@
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>"; };
4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinNotificationCenter.h; sourceTree = "<group>"; };
4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DarwinNotificationCenter.m; sourceTree = "<group>"; };
4EC49B8625BED71300E76218 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "JitsiMeetBroadcast Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4EC49BB925BEDAC100E76218 /* SampleHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleHandler.h; sourceTree = "<group>"; };
4EC49BBA25BEDAC100E76218 /* SampleHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleHandler.m; sourceTree = "<group>"; };
4EC49BBC25BEDAC100E76218 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4EC49BC925BEDB6400E76218 /* SocketConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SocketConnection.h; sourceTree = "<group>"; };
4EC49BCA25BEDB6400E76218 /* SocketConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SocketConnection.m; sourceTree = "<group>"; };
4EC49BCF25BF19CF00E76218 /* SampleUploader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleUploader.h; sourceTree = "<group>"; };
4EC49BD025BF19CF00E76218 /* SampleUploader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleUploader.m; sourceTree = "<group>"; };
4EC49BDB25BF280A00E76218 /* JitsiMeetBroadcast Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "JitsiMeetBroadcast Extension.entitlements"; sourceTree = "<group>"; };
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; };
@ -153,6 +189,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4EC49BB325BEDAC100E76218 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -165,6 +209,7 @@
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */,
4EC49B8625BED71300E76218 /* ReplayKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -216,6 +261,23 @@
path = src;
sourceTree = "<group>";
};
4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
isa = PBXGroup;
children = (
4EC49BDB25BF280A00E76218 /* JitsiMeetBroadcast Extension.entitlements */,
4EC49BB925BEDAC100E76218 /* SampleHandler.h */,
4EC49BBA25BEDAC100E76218 /* SampleHandler.m */,
4EC49BC925BEDB6400E76218 /* SocketConnection.h */,
4EC49BCA25BEDB6400E76218 /* SocketConnection.m */,
4EC49BCF25BF19CF00E76218 /* SampleUploader.h */,
4EC49BD025BF19CF00E76218 /* SampleUploader.m */,
4EC49BBC25BEDAC100E76218 /* Info.plist */,
4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */,
4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */,
);
path = "JitsiMeetBroadcast Extension";
sourceTree = "<group>";
};
5E96ADD5E49F3B3822EF9A52 /* Pods */ = {
isa = PBXGroup;
children = (
@ -236,6 +298,7 @@
13B07FAE1A68108700A75B9A /* src */,
5E96ADD5E49F3B3822EF9A52 /* Pods */,
0BEA5C261F7B8F73000D0AB4 /* Watch app */,
4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
);
indentWidth = 2;
@ -248,6 +311,7 @@
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */,
);
name = Products;
sourceTree = "<group>";
@ -305,17 +369,36 @@
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
4EC49B9025BED71300E76218 /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */,
);
name = JitsiMeet;
productName = "Jitsi Meet";
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
productType = "com.apple.product-type.application";
};
4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */;
buildPhases = (
4EC49BB225BEDAC100E76218 /* Sources */,
4EC49BB325BEDAC100E76218 /* Frameworks */,
4EC49BB425BEDAC100E76218 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "JitsiMeetBroadcast Extension";
productName = "JitsiMeetBroadcast Extension";
productReference = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -347,6 +430,9 @@
};
};
};
4EC49BB525BEDAC100E76218 = {
CreatedOnToolsVersion = 12.2;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
@ -365,6 +451,7 @@
13B07F861A680F5B00A75B9A /* JitsiMeet */,
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
);
};
/* End PBXProject section */
@ -397,6 +484,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4EC49BB425BEDAC100E76218 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -532,6 +626,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4EC49BB225BEDAC100E76218 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */,
4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */,
4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */,
4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -545,6 +650,11 @@
target = 0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */;
targetProxy = 0BEA5C3F1F7B8F73000D0AB4 /* PBXContainerItemProxy */;
};
4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */;
targetProxy = 4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -770,6 +880,70 @@
};
name = Release;
};
4EC49BC125BEDAC100E76218 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "JitsiMeetBroadcast Extension/JitsiMeetBroadcast Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "JitsiMeetBroadcast Extension/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4EC49BC225BEDAC100E76218 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "JitsiMeetBroadcast Extension/JitsiMeetBroadcast Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "JitsiMeetBroadcast Extension/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -914,6 +1088,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4EC49BC125BEDAC100E76218 /* Debug */,
4EC49BC225BEDAC100E76218 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -45,6 +45,8 @@
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>FirebaseCrashlyticsCollectionEnabled</key>
<string>false</string>
<key>FirebaseScreenReportingEnabled</key>
<false/>
<key>ITSAppUsesNonExemptEncryption</key>
@ -66,14 +68,18 @@
<string>See your scheduled meetings in the app.</string>
<key>NSCameraUsageDescription</key>
<string>Participate in meetings with video.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Participate in meetings with voice.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Local network is used for establishing Peer-to-Peer connections.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Participate in meetings with voice.</string>
<key>NSUserActivityTypes</key>
<array>
<string>org.jitsi.JitsiMeet.ios.conference</string>
</array>
<key>RTCAppGroupIdentifier</key>
<string>group.org.jitsi.meet.appgroup</string>
<key>RTCScreenSharingExtension</key>
<string>org.jitsi.meet.broadcast.extension</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
@ -99,7 +105,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>FirebaseCrashlyticsCollectionEnabled</key>
<string>false</string>
</dict>
</plist>

View File

@ -24,6 +24,8 @@
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, ); }; };
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
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 */; };
@ -85,6 +87,8 @@
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>"; };
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
6C31EDC720C06D490089C899 /* recordingOn.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOn.mp3; path = ../../sounds/recordingOn.mp3; sourceTree = "<group>"; };
6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
@ -231,6 +235,8 @@
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */,
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */,
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */,
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */,
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */,
);
path = src;
sourceTree = "<group>";
@ -298,6 +304,7 @@
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
DE81A2DE2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h in Headers */,
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
@ -466,6 +473,7 @@
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,

View File

@ -23,6 +23,7 @@
#import "RCTBridgeWrapper.h"
#import "ReactUtils.h"
#import "RNSplashScreen.h"
#import "ScheenshareEventEmiter.h"
#import <RNGoogleSignin/RNGoogleSignin.h>
#import <WebRTC/RTCLogging.h>
@ -31,6 +32,7 @@
@implementation JitsiMeet {
RCTBridgeWrapper *_bridgeWrapper;
NSDictionary *_launchOptions;
ScheenshareEventEmiter *_screenshareEventEmiter;
}
#pragma mak - This class is a singleton
@ -50,6 +52,9 @@
if (self = [super init]) {
// Initialize the on and only bridge for interfacing with React Native.
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
// Initialize the listener for handling start/stop screensharing notifications.
_screenshareEventEmiter = [[ScheenshareEventEmiter alloc] init];
// Register a fatal error handler for React.
registerReactFatalErrorHandler();

View File

@ -0,0 +1,25 @@
/*
* Copyright @ 2021-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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ScheenshareEventEmiter : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,63 @@
/*
* Copyright @ 2021-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 "ScheenshareEventEmiter.h"
#import "JitsiMeet+Private.h"
#import "ExternalAPI.h"
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
@implementation ScheenshareEventEmiter {
CFNotificationCenterRef _notificationCenter;
}
- (instancetype)init {
self = [super init];
if (self) {
_notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
[self setupObserver];
}
return self;
}
- (void)dealloc {
[self clearObserver];
}
// MARK: Private Methods
- (void)setupObserver {
CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStartedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStoppedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
}
- (void)clearObserver {
CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStartedNotification, NULL);
CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStoppedNotification, NULL);
}
void broadcastToggleNotificationCallback(CFNotificationCenterRef center,
void *observer,
CFStringRef name,
const void *object,
CFDictionaryRef userInfo) {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI toggleScreenShare];
}
@end

5
package-lock.json generated
View File

@ -14071,9 +14071,8 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
"version": "1.87.3",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.3.tgz",
"integrity": "sha512-fWnaEHFCFD7YnPR95aaUqLQ5b4dY4av0qHjmwHXeLHGvGrVeWF1je9PNhet7PDHUIJa4GIYKB/8+co51SXm5dA==",
"version": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"from": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"requires": {
"base64-js": "^1.1.2",
"cross-os": "^1.3.0",

View File

@ -84,7 +84,7 @@
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "1.87.3",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"react-native-webview": "11.0.2",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",

View File

@ -11,5 +11,7 @@ import { NativeModules } from 'react-native';
export function setPictureInPictureDisabled(disabled: boolean) {
const { PictureInPicture } = NativeModules;
PictureInPicture.setPictureInPictureDisabled(disabled);
if (PictureInPicture) {
PictureInPicture.setPictureInPictureDisabled(disabled);
}
}

View File

@ -0,0 +1,74 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconShareDesktop } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
/**
* The type of the React {@code Component} props of {@link ScreenSharingAndroidButton}.
*/
type Props = AbstractButtonProps & {
/**
* Whether video is currently muted or not.
*/
_screensharing: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
/**
* An implementation of a button for toggling screen sharing.
*/
class ScreenSharingAndroidButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
icon = IconShareDesktop;
label = 'toolbar.startScreenSharing';
toggledLabel = 'toolbar.stopScreenSharing';
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_handleClick() {
this.props.dispatch(toggleScreensharing());
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._screensharing;
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code ToggleCameraButton} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _disabled: boolean,
* _screensharing: boolean
* }}
*/
function _mapStateToProps(state): Object {
return {
_screensharing: isLocalVideoTrackDesktop(state)
};
}
export default translate(connect(_mapStateToProps)(ScreenSharingAndroidButton));

View File

@ -1,77 +1,18 @@
// @flow
import React from 'react';
import { Platform } from 'react-native';
import { translate } from '../../../base/i18n';
import { IconShareDesktop } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
import ScreenSharingAndroidButton from './ScreenSharingAndroidButton.js';
import ScreenSharingIosButton from './ScreenSharingIosButton.js';
/**
* The type of the React {@code Component} props of {@link ScreenSharingButton}.
*/
type Props = AbstractButtonProps & {
const ScreenSharingButton = props => (
<>
{Platform.OS === 'android'
&& <ScreenSharingAndroidButton { ...props } />
}
{Platform.OS === 'ios'
&& <ScreenSharingIosButton { ...props } />
}
</>
);
/**
* Whether video is currently muted or not.
*/
_screensharing: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
/**
* An implementation of a button for toggling screen sharing.
*/
class ScreenSharingButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
icon = IconShareDesktop;
label = 'toolbar.startScreenSharing';
toggledLabel = 'toolbar.stopScreenSharing';
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_handleClick() {
this.props.dispatch(toggleScreensharing());
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._screensharing;
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code ToggleCameraButton} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _disabled: boolean,
* _screensharing: boolean
* }}
*/
function _mapStateToProps(state): Object {
return {
_screensharing: isLocalVideoTrackDesktop(state),
visible: Platform.OS === 'android'
};
}
export default translate(connect(_mapStateToProps)(ScreenSharingButton));
export default ScreenSharingButton;

View File

@ -0,0 +1,132 @@
// @flow
import React from 'react';
import { findNodeHandle, NativeModules, Platform } from 'react-native';
import { ScreenCapturePickerView } from 'react-native-webrtc';
import { translate } from '../../../base/i18n';
import { IconShareDesktop } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { isLocalVideoTrackDesktop } from '../../../base/tracks';
/**
* The type of the React {@code Component} props of {@link ScreenSharingIosButton}.
*/
type Props = AbstractButtonProps & {
/**
* Whether video is currently muted or not.
*/
_screensharing: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
const styles = {
screenCapturePickerView: {
display: 'none'
}
};
/**
* An implementation of a button for toggling screen sharing on iOS.
*/
class ScreenSharingIosButton extends AbstractButton<Props, *> {
_nativeComponent: ?Object;
_setNativeComponent: Function;
accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
icon = IconShareDesktop;
label = 'toolbar.startScreenSharing';
toggledLabel = 'toolbar.stopScreenSharing';
/**
* Initializes a new {@code ScreenSharingIosButton} instance.
*
* @param {Object} props - The React {@code Component} props to initialize
* the new {@code ScreenSharingIosButton} instance with.
*/
constructor(props) {
super(props);
this._nativeComponent = null;
// Bind event handlers so they are only bound once per instance.
this._setNativeComponent = this._setNativeComponent.bind(this);
}
/**
* Sets the internal reference to the React Component wrapping the
* {@code RPSystemBroadcastPickerView} component.
*
* @param {ReactComponent} component - React Component.
* @returns {void}
*/
_setNativeComponent(component) {
this._nativeComponent = component;
}
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_handleClick() {
const handle = findNodeHandle(this._nativeComponent);
NativeModules.ScreenCapturePickerViewManager.show(handle);
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._screensharing;
}
/**
* Helper function to be implemented by subclasses, which may return a
* new React Element to be appended at the end of the button.
*
* @protected
* @returns {ReactElement|null}
*/
_getElementAfter() {
return (
<ScreenCapturePickerView
ref = { this._setNativeComponent }
style = { styles.screenCapturePickerView } />
);
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code ScreenSharingIosButton} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _disabled: boolean,
* }}
*/
function _mapStateToProps(state): Object {
return {
_screensharing: isLocalVideoTrackDesktop(state),
// TODO: this should work on iOS 12 too, but our trick to show the picker doesn't work.
visible: Platform.OS === 'ios' && Platform.Version.split('.')[0] >= 14
};
}
export default translate(connect(_mapStateToProps)(ScreenSharingIosButton));