Преглед изворни кода

feat(iOS): screensharing support

The Jitsi team would like to thank @AliKarpuzoglu, @linuxpi and The Hopp Foundation for the initial effort and help throughout.
factor2
Alex Bumbu пре 4 година
родитељ
комит
508f1e0da9
No account linked to committer's email address

+ 31
- 0
ios/app/JitsiMeetBroadcast Extension/DarwinNotificationCenter.h Прегледај датотеку

@@ -0,0 +1,31 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+NS_ASSUME_NONNULL_BEGIN
20
+
21
+extern NSNotificationName const kBroadcastStartedNotification;
22
+extern NSNotificationName const kBroadcastStoppedNotification;
23
+
24
+@interface DarwinNotificationCenter: NSObject
25
+
26
++ (instancetype)sharedInstance;
27
+- (void)postNotificationWithName:(NSNotificationName)name;
28
+
29
+@end
30
+
31
+NS_ASSUME_NONNULL_END

+ 50
- 0
ios/app/JitsiMeetBroadcast Extension/DarwinNotificationCenter.m Прегледај датотеку

@@ -0,0 +1,50 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "DarwinNotificationCenter.h"
18
+
19
+NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
20
+NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
21
+
22
+@implementation DarwinNotificationCenter {
23
+  CFNotificationCenterRef _notificationCenter;
24
+}
25
+
26
++ (instancetype)sharedInstance {
27
+  static DarwinNotificationCenter *sharedInstance = nil;
28
+  static dispatch_once_t onceToken;
29
+  dispatch_once(&onceToken, ^{
30
+      sharedInstance = [[self alloc] init];
31
+  });
32
+  
33
+  return sharedInstance;
34
+}
35
+
36
+- (instancetype)init {
37
+  self = [super init];
38
+  if (self) {
39
+    _notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
40
+  }
41
+  
42
+  return self;
43
+}
44
+
45
+- (void)postNotificationWithName:(NSString*)name {
46
+  CFNotificationCenterPostNotification(_notificationCenter, (__bridge CFStringRef)name, NULL, NULL, true);
47
+}
48
+
49
+@end
50
+

+ 33
- 0
ios/app/JitsiMeetBroadcast Extension/Info.plist Прегледај датотеку

@@ -0,0 +1,33 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
7
+	<key>CFBundleDisplayName</key>
8
+	<string>JitsiMeetBroadcast Extension</string>
9
+	<key>CFBundleExecutable</key>
10
+	<string>$(EXECUTABLE_NAME)</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>1.0</string>
21
+	<key>CFBundleVersion</key>
22
+	<string>1</string>
23
+	<key>NSExtension</key>
24
+	<dict>
25
+		<key>NSExtensionPointIdentifier</key>
26
+		<string>com.apple.broadcast-services-upload</string>
27
+		<key>NSExtensionPrincipalClass</key>
28
+		<string>SampleHandler</string>
29
+		<key>RPBroadcastProcessMode</key>
30
+		<string>RPBroadcastProcessModeSampleBuffer</string>
31
+	</dict>
32
+</dict>
33
+</plist>

+ 10
- 0
ios/app/JitsiMeetBroadcast Extension/JitsiMeetBroadcast Extension.entitlements Прегледај датотеку

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

+ 21
- 0
ios/app/JitsiMeetBroadcast Extension/SampleHandler.h Прегледај датотеку

@@ -0,0 +1,21 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <ReplayKit/ReplayKit.h>
18
+
19
+@interface SampleHandler : RPBroadcastSampleHandler
20
+
21
+@end

+ 123
- 0
ios/app/JitsiMeetBroadcast Extension/SampleHandler.m Прегледај датотеку

@@ -0,0 +1,123 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "SampleHandler.h"
18
+#import "SocketConnection.h"
19
+#import "SampleUploader.h"
20
+#import "DarwinNotificationCenter.h"
21
+
22
+@interface SampleHandler ()
23
+
24
+@property (nonatomic, retain) SocketConnection *clientConnection;
25
+@property (nonatomic, retain) SampleUploader *uploader;
26
+
27
+@end
28
+
29
+@implementation SampleHandler
30
+
31
+- (instancetype)init {
32
+  self = [super init];
33
+  if (self) {
34
+    self.clientConnection = [[SocketConnection alloc] initWithFilePath:self.socketFilePath];
35
+    [self setupConnection];
36
+    
37
+    self.uploader = [[SampleUploader alloc] initWithConnection:self.clientConnection];    
38
+  }
39
+  
40
+  return self;
41
+}
42
+
43
+- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
44
+  // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
45
+  NSLog(@"broadcast started");
46
+  
47
+  [[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStartedNotification];
48
+  [self openConnection];
49
+}
50
+
51
+- (void)broadcastPaused {
52
+    // User has requested to pause the broadcast. Samples will stop being delivered.
53
+}
54
+
55
+- (void)broadcastResumed {
56
+    // User has requested to resume the broadcast. Samples delivery will resume.
57
+}
58
+
59
+- (void)broadcastFinished {
60
+    // User has requested to finish the broadcast.
61
+  [[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStoppedNotification];
62
+  [self.clientConnection close];
63
+}
64
+
65
+- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
66
+  static NSUInteger frameCount = 0;
67
+  switch (sampleBufferType) {
68
+    case RPSampleBufferTypeVideo:
69
+      // adjust frame rate by using every third frame
70
+      if (++frameCount%3 == 0 && self.uploader.isReady) {
71
+        [self.uploader sendSample:sampleBuffer];
72
+      }
73
+      break;
74
+
75
+    default:
76
+      break;
77
+  }
78
+}
79
+
80
+// MARK: Private Methods
81
+
82
+- (NSString *)socketFilePath {
83
+    // the appGroupIdentifier must match the value provided in the app's info.plist for the RTCAppGroupIdentifier key
84
+    NSString *appGroupIdentifier = @"group.org.jitsi.meet.appgroup";
85
+    NSURL *sharedContainer = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupIdentifier];
86
+    NSString *socketFilePath = [[sharedContainer URLByAppendingPathComponent:@"rtc_SSFD"] path];
87
+      
88
+    return socketFilePath;
89
+}
90
+
91
+- (void)setupConnection {
92
+  __weak __typeof(self) weakSelf = self;
93
+  self.clientConnection.didClose = ^(NSError *error) {
94
+    NSLog(@"client connection did close: %@", error);
95
+    if (error) {
96
+      [weakSelf finishBroadcastWithError:error];
97
+    }
98
+    else {
99
+      NSInteger JMScreenSharingStopped = 10001;
100
+      NSError *customError = [NSError errorWithDomain:RPRecordingErrorDomain
101
+                                                 code:JMScreenSharingStopped
102
+                                             userInfo:@{NSLocalizedDescriptionKey: @"Screen sharing stopped"}];
103
+      [weakSelf finishBroadcastWithError:customError];
104
+    }
105
+  };
106
+}
107
+
108
+- (void)openConnection {
109
+  dispatch_queue_t queue = dispatch_queue_create("org.jitsi.meet.broadcast.connectTimer", 0);
110
+  dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
111
+  dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
112
+  
113
+  dispatch_source_set_event_handler(timer, ^{
114
+    BOOL success = [self.clientConnection open];
115
+    if (success) {
116
+      dispatch_source_cancel(timer);
117
+    }
118
+  });
119
+
120
+  dispatch_resume(timer);
121
+}
122
+
123
+@end

+ 33
- 0
ios/app/JitsiMeetBroadcast Extension/SampleUploader.h Прегледај датотеку

@@ -0,0 +1,33 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+#import <ReplayKit/ReplayKit.h>
19
+
20
+NS_ASSUME_NONNULL_BEGIN
21
+
22
+@class SocketConnection;
23
+
24
+@interface SampleUploader : NSObject
25
+
26
+@property (nonatomic, assign, readonly) BOOL isReady;
27
+
28
+- (instancetype)initWithConnection:(SocketConnection *)connection;
29
+- (void)sendSample:(CMSampleBufferRef)sampleBuffer;
30
+
31
+@end
32
+
33
+NS_ASSUME_NONNULL_END

+ 155
- 0
ios/app/JitsiMeetBroadcast Extension/SampleUploader.m Прегледај датотеку

@@ -0,0 +1,155 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <MessageUI/MessageUI.h>
18
+#import <ReplayKit/ReplayKit.h>
19
+
20
+#import "SampleUploader.h"
21
+#import "SocketConnection.h"
22
+
23
+static const NSInteger kBufferMaxLenght = 10 * 1024;
24
+
25
+@interface SampleUploader ()
26
+
27
+@property (nonatomic, assign) BOOL isReady;
28
+
29
+@property (nonatomic, strong) dispatch_queue_t serialQueue;
30
+@property (nonatomic, strong) SocketConnection *connection;
31
+@property (nonatomic, strong) CIContext *imageContext;
32
+
33
+@property (nonatomic, strong) NSData *dataToSend;
34
+@property (nonatomic, assign) NSUInteger byteIndex;
35
+
36
+@end
37
+
38
+@implementation SampleUploader
39
+
40
+- (instancetype)initWithConnection:(SocketConnection *)connection {
41
+  self = [super init];
42
+  if (self) {
43
+    self.serialQueue = dispatch_queue_create("org.jitsi.meet.broadcast.sampleUploader", DISPATCH_QUEUE_SERIAL);
44
+    
45
+    self.connection = connection;
46
+    [self setupConnection];
47
+    
48
+    self.imageContext = [[CIContext alloc] initWithOptions:nil];
49
+    self.isReady = false;
50
+  }
51
+  
52
+  return self;
53
+}
54
+
55
+- (void)sendSample:(CMSampleBufferRef)sampleBuffer {
56
+  self.isReady = false;
57
+  
58
+  self.dataToSend = [self prepareSample:sampleBuffer];
59
+  self.byteIndex = 0;
60
+  
61
+  dispatch_async(self.serialQueue, ^{
62
+    [self sendData];
63
+  });
64
+}
65
+
66
+// MARK: Private Methods
67
+
68
+- (void)setupConnection {
69
+  __weak __typeof(self) weakSelf = self;
70
+  self.connection.didOpen = ^{
71
+    weakSelf.isReady = true;
72
+  };
73
+  self.connection.streamHasSpaceAvailable = ^{
74
+    dispatch_async(weakSelf.serialQueue, ^{
75
+      weakSelf.isReady = ![weakSelf sendData];
76
+    });
77
+  };
78
+}
79
+
80
+/**
81
+ This function downscales and converts to jpeg the provided sample buffer, then wraps the resulted image data into a CFHTTPMessageRef. Returns the serialized CFHTTPMessageRef.
82
+ */
83
+- (NSData *)prepareSample:(CMSampleBufferRef)sampleBuffer {
84
+  CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
85
+    
86
+  CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
87
+  
88
+  CGFloat scaleFactor = 2;
89
+  size_t width = CVPixelBufferGetWidth(imageBuffer)/scaleFactor;
90
+  size_t height = CVPixelBufferGetHeight(imageBuffer)/scaleFactor;
91
+    
92
+  CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1/scaleFactor, 1/scaleFactor);
93
+  NSData *bufferData = [self jpegDataFromPixelBuffer:imageBuffer withScaling:scaleTransform];
94
+  
95
+  CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
96
+  
97
+  if (bufferData) {
98
+    CFHTTPMessageRef httpResponse = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
99
+    CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", bufferData.length]);
100
+    CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Width", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", width]);
101
+    CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Height", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", height]);
102
+
103
+    CFHTTPMessageSetBody(httpResponse, (__bridge CFDataRef)bufferData);
104
+
105
+    CFDataRef serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse);
106
+    CFRelease(httpResponse);
107
+    
108
+    return CFBridgingRelease(serializedMessage);
109
+  }
110
+  
111
+  return nil;
112
+}
113
+
114
+- (BOOL)sendData {
115
+  if (!self.dataToSend) {
116
+    NSLog(@"no data to send");
117
+    return false;
118
+  }
119
+
120
+  NSUInteger bytesLeft = self.dataToSend.length - self.byteIndex;
121
+  
122
+  NSInteger length = bytesLeft > kBufferMaxLenght ? kBufferMaxLenght : bytesLeft;
123
+  uint8_t buffer[length];
124
+  [self.dataToSend getBytes:&buffer range:NSMakeRange(self.byteIndex, length)];
125
+
126
+  length = [self.connection writeBufferToStream:buffer maxLength:length];
127
+  if (length > 0) {
128
+    self.byteIndex += length;
129
+    bytesLeft -= length;
130
+
131
+    if (bytesLeft == 0) {
132
+      NSLog(@"video sample processed successfully");
133
+      self.dataToSend = nil;
134
+      self.byteIndex = 0;
135
+    }
136
+  }
137
+  else {
138
+    NSLog(@"writeBufferToStream failure");
139
+  }
140
+  
141
+  return true;
142
+}
143
+
144
+- (NSData *)jpegDataFromPixelBuffer:(CVPixelBufferRef)pixelBuffer withScaling:(CGAffineTransform)scaleTransform {
145
+  CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
146
+  image = [image imageByApplyingTransform:scaleTransform];
147
+  
148
+  NSDictionary *options = @{(NSString *)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithFloat:1.0]};
149
+  NSData *imageData = [self.imageContext JPEGRepresentationOfImage:image
150
+                                                        colorSpace:image.colorSpace
151
+                                                           options:options];
152
+  return imageData;
153
+}
154
+
155
+@end

+ 34
- 0
ios/app/JitsiMeetBroadcast Extension/SocketConnection.h Прегледај датотеку

@@ -0,0 +1,34 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+NS_ASSUME_NONNULL_BEGIN
20
+
21
+@interface SocketConnection : NSObject
22
+
23
+@property (nonatomic, copy, nullable) void (^didOpen)(void);
24
+@property (nonatomic, copy, nullable) void (^didClose)(NSError*);
25
+@property (nonatomic, copy, nullable) void (^streamHasSpaceAvailable)(void);
26
+
27
+- (instancetype)initWithFilePath:(nonnull NSString *)filePath;
28
+- (BOOL)open;
29
+- (void)close;
30
+- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length;
31
+
32
+@end
33
+
34
+NS_ASSUME_NONNULL_END

+ 189
- 0
ios/app/JitsiMeetBroadcast Extension/SocketConnection.m Прегледај датотеку

@@ -0,0 +1,189 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#include <sys/socket.h>
18
+#include <sys/un.h>
19
+
20
+#import "SocketConnection.h"
21
+
22
+@interface SocketConnection () <NSStreamDelegate>
23
+
24
+@property (nonatomic, copy) NSString *filePath;
25
+
26
+@property (nonatomic, strong) NSInputStream *inputStream;
27
+@property (nonatomic, strong) NSOutputStream *outputStream;
28
+
29
+@property (nonatomic, strong) NSThread *networkThread;
30
+
31
+@end
32
+
33
+@implementation SocketConnection {
34
+  int _socket;
35
+  struct sockaddr_un _socketAddr;
36
+}
37
+
38
+- (instancetype)initWithFilePath:(NSString *)path {
39
+  self = [super init];
40
+  if (self) {
41
+    self.filePath = path;
42
+    
43
+    [self setupSocketWithFilePath:path];
44
+    [self setupNetworkThread];
45
+  }
46
+  
47
+  return self;
48
+}
49
+
50
+- (BOOL)open {
51
+  NSLog(@"Open socket connection");
52
+  
53
+  if (![[NSFileManager defaultManager] fileExistsAtPath:self.filePath]) {
54
+      NSLog(@"failure: socket file missing");
55
+      return false;
56
+  }
57
+
58
+  int status = connect(_socket, (struct sockaddr *)&_socketAddr, sizeof(_socketAddr));
59
+  if (status < 0) {
60
+      NSLog(@"failure: socket connect (%d)", status);
61
+      return false;
62
+  }
63
+  
64
+  [self.networkThread start];
65
+
66
+  CFReadStreamRef readStream;
67
+  CFWriteStreamRef writeStream;
68
+
69
+  CFStreamCreatePairWithSocket(kCFAllocatorDefault, _socket, &readStream, &writeStream);
70
+
71
+  self.inputStream = (__bridge_transfer NSInputStream *)readStream;
72
+  self.inputStream.delegate = self;
73
+  [self.inputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
74
+
75
+  self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
76
+  self.outputStream.delegate = self;
77
+  [self.outputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
78
+  
79
+  [self performSelector:@selector(scheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
80
+
81
+  [self.inputStream open];
82
+  [self.outputStream open];
83
+
84
+  NSLog(@"read stream status: %ld", CFReadStreamGetStatus(readStream));
85
+  NSLog(@"write stream status: %ld", CFWriteStreamGetStatus(writeStream));
86
+  
87
+  return true;
88
+}
89
+
90
+- (void)close {
91
+  [self performSelector:@selector(unscheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
92
+
93
+  self.inputStream.delegate = nil;
94
+  self.outputStream.delegate = nil;
95
+  
96
+  [self.inputStream close];
97
+  [self.outputStream close];
98
+
99
+  [self.networkThread cancel];
100
+}
101
+
102
+- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length {
103
+  return [self.outputStream write:buffer maxLength:length];
104
+}
105
+
106
+// MARK: Private Methods
107
+
108
+- (BOOL)isOpen {
109
+  return self.inputStream.streamStatus == NSStreamStatusOpen && self.outputStream.streamStatus == NSStreamStatusOpen;
110
+}
111
+
112
+- (void)setupSocketWithFilePath:(NSString*)path {
113
+  _socket = socket(AF_UNIX, SOCK_STREAM, 0);
114
+  
115
+  memset(&_socketAddr, 0, sizeof(_socketAddr));
116
+  _socketAddr.sun_family = AF_UNIX;
117
+  strncpy(_socketAddr.sun_path, path.UTF8String, sizeof(_socketAddr.sun_path) - 1);
118
+}
119
+
120
+- (void)setupNetworkThread {
121
+  self.networkThread = [[NSThread alloc] initWithBlock:^{
122
+    do {
123
+      @autoreleasepool {
124
+        [[NSRunLoop currentRunLoop] run];
125
+      }
126
+    } while (![NSThread currentThread].isCancelled);
127
+  }];
128
+  self.networkThread.qualityOfService = NSQualityOfServiceUserInitiated;
129
+}
130
+
131
+- (void)scheduleStreams {
132
+  [self.inputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
133
+  [self.outputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
134
+}
135
+
136
+- (void)unscheduleStreams {
137
+  [self.inputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
138
+  [self.outputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
139
+}
140
+
141
+- (void)notifyDidClose:(NSError *)error {
142
+  if (self.didClose) {
143
+    self.didClose(error);
144
+  }
145
+}
146
+
147
+@end
148
+
149
+#pragma mark - NSStreamDelegate
150
+
151
+@implementation SocketConnection (NSStreamDelegate)
152
+
153
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
154
+  switch (eventCode) {
155
+    case NSStreamEventOpenCompleted:
156
+      NSLog(@"client stream open completed");
157
+      if (aStream == self.outputStream && self.didOpen) {
158
+        self.didOpen();
159
+      }
160
+      break;
161
+    case NSStreamEventHasBytesAvailable:
162
+      if (aStream == self.inputStream) {
163
+        uint8_t buffer;
164
+        NSInteger numberOfBytesRead = [(NSInputStream *)aStream read:&buffer maxLength:sizeof(buffer)];
165
+        if (!numberOfBytesRead && aStream.streamStatus == NSStreamStatusAtEnd) {
166
+          NSLog(@"server socket closed");
167
+          [self close];
168
+          [self notifyDidClose:nil];
169
+        }
170
+      }
171
+      break;
172
+    case NSStreamEventHasSpaceAvailable:
173
+      if (aStream == self.outputStream && self.streamHasSpaceAvailable) {
174
+        NSLog(@"client stream has space available");
175
+        self.streamHasSpaceAvailable();
176
+      }
177
+      break;
178
+    case NSStreamEventErrorOccurred:
179
+      NSLog(@"client stream error occurred: %@", aStream.streamError);
180
+      [self close];
181
+      [self notifyDidClose:aStream.streamError];
182
+      break;
183
+      
184
+    default:
185
+      break;
186
+  }
187
+}
188
+
189
+@end

+ 4
- 0
ios/app/app.entitlements Прегледај датотеку

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

+ 183
- 0
ios/app/app.xcodeproj/project.pbxproj Прегледај датотеку

@@ -23,6 +23,12 @@
23 23
 		13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
24 24
 		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
25 25
 		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
26
+		4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */; };
27
+		4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EC49B8625BED71300E76218 /* ReplayKit.framework */; };
28
+		4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BBA25BEDAC100E76218 /* SampleHandler.m */; };
29
+		4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
30
+		4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BCA25BEDB6400E76218 /* SocketConnection.m */; };
31
+		4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BD025BF19CF00E76218 /* SampleUploader.m */; };
26 32
 		55BEDABDA92D47D399A70A5E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */; };
27 33
 		DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; };
28 34
 		DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -48,6 +54,13 @@
48 54
 			remoteGlobalIDString = 0BEA5C241F7B8F73000D0AB4;
49 55
 			remoteInfo = JitsiMeetCompanion;
50 56
 		};
57
+		4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */ = {
58
+			isa = PBXContainerItemProxy;
59
+			containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
60
+			proxyType = 1;
61
+			remoteGlobalIDString = 4EC49BB525BEDAC100E76218;
62
+			remoteInfo = "JitsiMeetBroadcast Extension";
63
+		};
51 64
 /* End PBXContainerItemProxy section */
52 65
 
53 66
 /* Begin PBXCopyFilesBuildPhase section */
@@ -85,6 +98,17 @@
85 98
 			name = "Embed Watch Content";
86 99
 			runOnlyForDeploymentPostprocessing = 0;
87 100
 		};
101
+		4EC49B9025BED71300E76218 /* Embed App Extensions */ = {
102
+			isa = PBXCopyFilesBuildPhase;
103
+			buildActionMask = 2147483647;
104
+			dstPath = "";
105
+			dstSubfolderSpec = 13;
106
+			files = (
107
+				4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */,
108
+			);
109
+			name = "Embed App Extensions";
110
+			runOnlyForDeploymentPostprocessing = 0;
111
+		};
88 112
 /* End PBXCopyFilesBuildPhase section */
89 113
 
90 114
 /* Begin PBXFileReference section */
@@ -115,6 +139,18 @@
115 139
 		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
116 140
 		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
117 141
 		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>"; };
142
+		4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinNotificationCenter.h; sourceTree = "<group>"; };
143
+		4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DarwinNotificationCenter.m; sourceTree = "<group>"; };
144
+		4EC49B8625BED71300E76218 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
145
+		4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "JitsiMeetBroadcast Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
146
+		4EC49BB925BEDAC100E76218 /* SampleHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleHandler.h; sourceTree = "<group>"; };
147
+		4EC49BBA25BEDAC100E76218 /* SampleHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleHandler.m; sourceTree = "<group>"; };
148
+		4EC49BBC25BEDAC100E76218 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
149
+		4EC49BC925BEDB6400E76218 /* SocketConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SocketConnection.h; sourceTree = "<group>"; };
150
+		4EC49BCA25BEDB6400E76218 /* SocketConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SocketConnection.m; sourceTree = "<group>"; };
151
+		4EC49BCF25BF19CF00E76218 /* SampleUploader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleUploader.h; sourceTree = "<group>"; };
152
+		4EC49BD025BF19CF00E76218 /* SampleUploader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleUploader.m; sourceTree = "<group>"; };
153
+		4EC49BDB25BF280A00E76218 /* JitsiMeetBroadcast Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "JitsiMeetBroadcast Extension.entitlements"; sourceTree = "<group>"; };
118 154
 		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>"; };
119 155
 		B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
120 156
 		D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -153,6 +189,14 @@
153 189
 			);
154 190
 			runOnlyForDeploymentPostprocessing = 0;
155 191
 		};
192
+		4EC49BB325BEDAC100E76218 /* Frameworks */ = {
193
+			isa = PBXFrameworksBuildPhase;
194
+			buildActionMask = 2147483647;
195
+			files = (
196
+				4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */,
197
+			);
198
+			runOnlyForDeploymentPostprocessing = 0;
199
+		};
156 200
 /* End PBXFrameworksBuildPhase section */
157 201
 
158 202
 /* Begin PBXGroup section */
@@ -165,6 +209,7 @@
165 209
 				DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
166 210
 				0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
167 211
 				D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */,
212
+				4EC49B8625BED71300E76218 /* ReplayKit.framework */,
168 213
 			);
169 214
 			name = Frameworks;
170 215
 			sourceTree = "<group>";
@@ -216,6 +261,23 @@
216 261
 			path = src;
217 262
 			sourceTree = "<group>";
218 263
 		};
264
+		4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
265
+			isa = PBXGroup;
266
+			children = (
267
+				4EC49BDB25BF280A00E76218 /* JitsiMeetBroadcast Extension.entitlements */,
268
+				4EC49BB925BEDAC100E76218 /* SampleHandler.h */,
269
+				4EC49BBA25BEDAC100E76218 /* SampleHandler.m */,
270
+				4EC49BC925BEDB6400E76218 /* SocketConnection.h */,
271
+				4EC49BCA25BEDB6400E76218 /* SocketConnection.m */,
272
+				4EC49BCF25BF19CF00E76218 /* SampleUploader.h */,
273
+				4EC49BD025BF19CF00E76218 /* SampleUploader.m */,
274
+				4EC49BBC25BEDAC100E76218 /* Info.plist */,
275
+				4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */,
276
+				4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */,
277
+			);
278
+			path = "JitsiMeetBroadcast Extension";
279
+			sourceTree = "<group>";
280
+		};
219 281
 		5E96ADD5E49F3B3822EF9A52 /* Pods */ = {
220 282
 			isa = PBXGroup;
221 283
 			children = (
@@ -236,6 +298,7 @@
236 298
 				13B07FAE1A68108700A75B9A /* src */,
237 299
 				5E96ADD5E49F3B3822EF9A52 /* Pods */,
238 300
 				0BEA5C261F7B8F73000D0AB4 /* Watch app */,
301
+				4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
239 302
 				0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
240 303
 			);
241 304
 			indentWidth = 2;
@@ -248,6 +311,7 @@
248 311
 				13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
249 312
 				0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
250 313
 				0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
314
+				4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */,
251 315
 			);
252 316
 			name = Products;
253 317
 			sourceTree = "<group>";
@@ -305,17 +369,36 @@
305 369
 				DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
306 370
 				DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
307 371
 				0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
372
+				4EC49B9025BED71300E76218 /* Embed App Extensions */,
308 373
 			);
309 374
 			buildRules = (
310 375
 			);
311 376
 			dependencies = (
312 377
 				0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
378
+				4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */,
313 379
 			);
314 380
 			name = JitsiMeet;
315 381
 			productName = "Jitsi Meet";
316 382
 			productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
317 383
 			productType = "com.apple.product-type.application";
318 384
 		};
385
+		4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
386
+			isa = PBXNativeTarget;
387
+			buildConfigurationList = 4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */;
388
+			buildPhases = (
389
+				4EC49BB225BEDAC100E76218 /* Sources */,
390
+				4EC49BB325BEDAC100E76218 /* Frameworks */,
391
+				4EC49BB425BEDAC100E76218 /* Resources */,
392
+			);
393
+			buildRules = (
394
+			);
395
+			dependencies = (
396
+			);
397
+			name = "JitsiMeetBroadcast Extension";
398
+			productName = "JitsiMeetBroadcast Extension";
399
+			productReference = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */;
400
+			productType = "com.apple.product-type.app-extension";
401
+		};
319 402
 /* End PBXNativeTarget section */
320 403
 
321 404
 /* Begin PBXProject section */
@@ -347,6 +430,9 @@
347 430
 							};
348 431
 						};
349 432
 					};
433
+					4EC49BB525BEDAC100E76218 = {
434
+						CreatedOnToolsVersion = 12.2;
435
+					};
350 436
 				};
351 437
 			};
352 438
 			buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
@@ -365,6 +451,7 @@
365 451
 				13B07F861A680F5B00A75B9A /* JitsiMeet */,
366 452
 				0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
367 453
 				0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
454
+				4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
368 455
 			);
369 456
 		};
370 457
 /* End PBXProject section */
@@ -397,6 +484,13 @@
397 484
 			);
398 485
 			runOnlyForDeploymentPostprocessing = 0;
399 486
 		};
487
+		4EC49BB425BEDAC100E76218 /* Resources */ = {
488
+			isa = PBXResourcesBuildPhase;
489
+			buildActionMask = 2147483647;
490
+			files = (
491
+			);
492
+			runOnlyForDeploymentPostprocessing = 0;
493
+		};
400 494
 /* End PBXResourcesBuildPhase section */
401 495
 
402 496
 /* Begin PBXShellScriptBuildPhase section */
@@ -532,6 +626,17 @@
532 626
 			);
533 627
 			runOnlyForDeploymentPostprocessing = 0;
534 628
 		};
629
+		4EC49BB225BEDAC100E76218 /* Sources */ = {
630
+			isa = PBXSourcesBuildPhase;
631
+			buildActionMask = 2147483647;
632
+			files = (
633
+				4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */,
634
+				4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */,
635
+				4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */,
636
+				4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */,
637
+			);
638
+			runOnlyForDeploymentPostprocessing = 0;
639
+		};
535 640
 /* End PBXSourcesBuildPhase section */
536 641
 
537 642
 /* Begin PBXTargetDependency section */
@@ -545,6 +650,11 @@
545 650
 			target = 0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */;
546 651
 			targetProxy = 0BEA5C3F1F7B8F73000D0AB4 /* PBXContainerItemProxy */;
547 652
 		};
653
+		4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */ = {
654
+			isa = PBXTargetDependency;
655
+			target = 4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */;
656
+			targetProxy = 4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */;
657
+		};
548 658
 /* End PBXTargetDependency section */
549 659
 
550 660
 /* Begin PBXVariantGroup section */
@@ -770,6 +880,70 @@
770 880
 			};
771 881
 			name = Release;
772 882
 		};
883
+		4EC49BC125BEDAC100E76218 /* Debug */ = {
884
+			isa = XCBuildConfiguration;
885
+			buildSettings = {
886
+				CLANG_ANALYZER_NONNULL = YES;
887
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
888
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
889
+				CLANG_ENABLE_OBJC_WEAK = YES;
890
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
891
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
892
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
893
+				CODE_SIGN_ENTITLEMENTS = "JitsiMeetBroadcast Extension/JitsiMeetBroadcast Extension.entitlements";
894
+				CODE_SIGN_IDENTITY = "iPhone Developer";
895
+				CODE_SIGN_STYLE = Automatic;
896
+				DEBUG_INFORMATION_FORMAT = dwarf;
897
+				DEVELOPMENT_TEAM = FC967L3QRG;
898
+				GCC_C_LANGUAGE_STANDARD = gnu11;
899
+				INFOPLIST_FILE = "JitsiMeetBroadcast Extension/Info.plist";
900
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
901
+				LD_RUNPATH_SEARCH_PATHS = (
902
+					"$(inherited)",
903
+					"@executable_path/Frameworks",
904
+					"@executable_path/../../Frameworks",
905
+				);
906
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
907
+				MTL_FAST_MATH = YES;
908
+				PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
909
+				PRODUCT_NAME = "$(TARGET_NAME)";
910
+				SKIP_INSTALL = YES;
911
+				TARGETED_DEVICE_FAMILY = "1,2";
912
+			};
913
+			name = Debug;
914
+		};
915
+		4EC49BC225BEDAC100E76218 /* Release */ = {
916
+			isa = XCBuildConfiguration;
917
+			buildSettings = {
918
+				CLANG_ANALYZER_NONNULL = YES;
919
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
920
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
921
+				CLANG_ENABLE_OBJC_WEAK = YES;
922
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
923
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
924
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
925
+				CODE_SIGN_ENTITLEMENTS = "JitsiMeetBroadcast Extension/JitsiMeetBroadcast Extension.entitlements";
926
+				CODE_SIGN_IDENTITY = "iPhone Developer";
927
+				CODE_SIGN_STYLE = Automatic;
928
+				COPY_PHASE_STRIP = NO;
929
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
930
+				DEVELOPMENT_TEAM = FC967L3QRG;
931
+				GCC_C_LANGUAGE_STANDARD = gnu11;
932
+				INFOPLIST_FILE = "JitsiMeetBroadcast Extension/Info.plist";
933
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
934
+				LD_RUNPATH_SEARCH_PATHS = (
935
+					"$(inherited)",
936
+					"@executable_path/Frameworks",
937
+					"@executable_path/../../Frameworks",
938
+				);
939
+				MTL_FAST_MATH = YES;
940
+				PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
941
+				PRODUCT_NAME = "$(TARGET_NAME)";
942
+				SKIP_INSTALL = YES;
943
+				TARGETED_DEVICE_FAMILY = "1,2";
944
+			};
945
+			name = Release;
946
+		};
773 947
 		83CBBA201A601CBA00E9B192 /* Debug */ = {
774 948
 			isa = XCBuildConfiguration;
775 949
 			buildSettings = {
@@ -914,6 +1088,15 @@
914 1088
 			defaultConfigurationIsVisible = 0;
915 1089
 			defaultConfigurationName = Release;
916 1090
 		};
1091
+		4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */ = {
1092
+			isa = XCConfigurationList;
1093
+			buildConfigurations = (
1094
+				4EC49BC125BEDAC100E76218 /* Debug */,
1095
+				4EC49BC225BEDAC100E76218 /* Release */,
1096
+			);
1097
+			defaultConfigurationIsVisible = 0;
1098
+			defaultConfigurationName = Release;
1099
+		};
917 1100
 		83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = {
918 1101
 			isa = XCConfigurationList;
919 1102
 			buildConfigurations = (

+ 8
- 4
ios/app/src/Info.plist Прегледај датотеку

@@ -45,6 +45,8 @@
45 45
 	</array>
46 46
 	<key>CFBundleVersion</key>
47 47
 	<string>1</string>
48
+	<key>FirebaseCrashlyticsCollectionEnabled</key>
49
+	<string>false</string>
48 50
 	<key>FirebaseScreenReportingEnabled</key>
49 51
 	<false/>
50 52
 	<key>ITSAppUsesNonExemptEncryption</key>
@@ -66,14 +68,18 @@
66 68
 	<string>See your scheduled meetings in the app.</string>
67 69
 	<key>NSCameraUsageDescription</key>
68 70
 	<string>Participate in meetings with video.</string>
69
-	<key>NSMicrophoneUsageDescription</key>
70
-	<string>Participate in meetings with voice.</string>
71 71
 	<key>NSLocalNetworkUsageDescription</key>
72 72
 	<string>Local network is used for establishing Peer-to-Peer connections.</string>
73
+	<key>NSMicrophoneUsageDescription</key>
74
+	<string>Participate in meetings with voice.</string>
73 75
 	<key>NSUserActivityTypes</key>
74 76
 	<array>
75 77
 		<string>org.jitsi.JitsiMeet.ios.conference</string>
76 78
 	</array>
79
+	<key>RTCAppGroupIdentifier</key>
80
+	<string>group.org.jitsi.meet.appgroup</string>
81
+	<key>RTCScreenSharingExtension</key>
82
+	<string>org.jitsi.meet.broadcast.extension</string>
77 83
 	<key>UIBackgroundModes</key>
78 84
 	<array>
79 85
 		<string>audio</string>
@@ -99,7 +105,5 @@
99 105
 	</array>
100 106
 	<key>UIViewControllerBasedStatusBarAppearance</key>
101 107
 	<false/>
102
-	<key>FirebaseCrashlyticsCollectionEnabled</key>
103
-	<string>false</string>
104 108
 </dict>
105 109
 </plist>

+ 8
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj Прегледај датотеку

@@ -24,6 +24,8 @@
24 24
 		0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495D1EC4B6C600B793EE /* POSIX.m */; };
25 25
 		0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495E1EC4B6C600B793EE /* Proximity.m */; };
26 26
 		0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
27
+		4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
28
+		4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
27 29
 		6C31EDC820C06D490089C899 /* recordingOn.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC720C06D490089C899 /* recordingOn.mp3 */; };
28 30
 		6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
29 31
 		6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
@@ -85,6 +87,8 @@
85 87
 		0BD906E51EC0C00300C8C18E /* JitsiMeetSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeetSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
86 88
 		0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
87 89
 		0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
90
+		4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
91
+		4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
88 92
 		6C31EDC720C06D490089C899 /* recordingOn.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOn.mp3; path = ../../sounds/recordingOn.mp3; sourceTree = "<group>"; };
89 93
 		6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
90 94
 		75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
@@ -231,6 +235,8 @@
231 235
 				C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */,
232 236
 				C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */,
233 237
 				C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */,
238
+				4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */,
239
+				4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */,
234 240
 			);
235 241
 			path = src;
236 242
 			sourceTree = "<group>";
@@ -298,6 +304,7 @@
298 304
 				0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
299 305
 				DE81A2DE2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h in Headers */,
300 306
 				DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
307
+				4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
301 308
 				DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
302 309
 				0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
303 310
 				0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
@@ -466,6 +473,7 @@
466 473
 				C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
467 474
 				DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
468 475
 				C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
476
+				4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
469 477
 				A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
470 478
 				C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
471 479
 				DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,

+ 5
- 0
ios/sdk/src/JitsiMeet.m Прегледај датотеку

@@ -23,6 +23,7 @@
23 23
 #import "RCTBridgeWrapper.h"
24 24
 #import "ReactUtils.h"
25 25
 #import "RNSplashScreen.h"
26
+#import "ScheenshareEventEmiter.h"
26 27
 
27 28
 #import <RNGoogleSignin/RNGoogleSignin.h>
28 29
 #import <WebRTC/RTCLogging.h>
@@ -31,6 +32,7 @@
31 32
 @implementation JitsiMeet {
32 33
     RCTBridgeWrapper *_bridgeWrapper;
33 34
     NSDictionary *_launchOptions;
35
+    ScheenshareEventEmiter *_screenshareEventEmiter;
34 36
 }
35 37
 
36 38
 #pragma mak - This class is a singleton
@@ -50,6 +52,9 @@
50 52
     if (self = [super init]) {
51 53
         // Initialize the on and only bridge for interfacing with React Native.
52 54
         _bridgeWrapper = [[RCTBridgeWrapper alloc] init];
55
+        
56
+        // Initialize the listener for handling start/stop screensharing notifications.
57
+        _screenshareEventEmiter = [[ScheenshareEventEmiter alloc] init];
53 58
 
54 59
         // Register a fatal error handler for React.
55 60
         registerReactFatalErrorHandler();

+ 25
- 0
ios/sdk/src/ScheenshareEventEmiter.h Прегледај датотеку

@@ -0,0 +1,25 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+NS_ASSUME_NONNULL_BEGIN
20
+
21
+@interface ScheenshareEventEmiter : NSObject
22
+
23
+@end
24
+
25
+NS_ASSUME_NONNULL_END

+ 63
- 0
ios/sdk/src/ScheenshareEventEmiter.m Прегледај датотеку

@@ -0,0 +1,63 @@
1
+/*
2
+ * Copyright @ 2021-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "ScheenshareEventEmiter.h"
18
+#import "JitsiMeet+Private.h"
19
+#import "ExternalAPI.h"
20
+
21
+NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
22
+NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
23
+
24
+@implementation ScheenshareEventEmiter {
25
+    CFNotificationCenterRef _notificationCenter;
26
+}
27
+
28
+- (instancetype)init {
29
+    self = [super init];
30
+    if (self) {
31
+        _notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
32
+        [self setupObserver];
33
+    }
34
+    
35
+    return self;
36
+}
37
+
38
+- (void)dealloc {
39
+    [self clearObserver];
40
+}
41
+
42
+// MARK: Private Methods
43
+
44
+- (void)setupObserver {
45
+    CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStartedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
46
+    CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStoppedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
47
+}
48
+
49
+- (void)clearObserver {
50
+    CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStartedNotification, NULL);
51
+    CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStoppedNotification, NULL);
52
+}
53
+
54
+void broadcastToggleNotificationCallback(CFNotificationCenterRef center,
55
+                                         void *observer,
56
+                                         CFStringRef name,
57
+                                         const void *object,
58
+                                         CFDictionaryRef userInfo) {
59
+    ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
60
+    [externalAPI toggleScreenShare];
61
+}
62
+
63
+@end

+ 2
- 3
package-lock.json Прегледај датотеку

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

+ 1
- 1
package.json Прегледај датотеку

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

+ 3
- 1
react/features/mobile/picture-in-picture/functions.js Прегледај датотеку

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

+ 74
- 0
react/features/toolbox/components/native/ScreenSharingAndroidButton.js Прегледај датотеку

@@ -0,0 +1,74 @@
1
+// @flow
2
+
3
+import { translate } from '../../../base/i18n';
4
+import { IconShareDesktop } from '../../../base/icons';
5
+import { connect } from '../../../base/redux';
6
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
7
+import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
8
+
9
+/**
10
+ * The type of the React {@code Component} props of {@link ScreenSharingAndroidButton}.
11
+ */
12
+type Props = AbstractButtonProps & {
13
+
14
+    /**
15
+     * Whether video is currently muted or not.
16
+     */
17
+    _screensharing: boolean,
18
+
19
+    /**
20
+     * The redux {@code dispatch} function.
21
+     */
22
+    dispatch: Function
23
+};
24
+
25
+/**
26
+ * An implementation of a button for toggling screen sharing.
27
+ */
28
+class ScreenSharingAndroidButton extends AbstractButton<Props, *> {
29
+    accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
30
+    icon = IconShareDesktop;
31
+    label = 'toolbar.startScreenSharing';
32
+    toggledLabel = 'toolbar.stopScreenSharing';
33
+
34
+    /**
35
+     * Handles clicking / pressing the button.
36
+     *
37
+     * @override
38
+     * @protected
39
+     * @returns {void}
40
+     */
41
+    _handleClick() {
42
+        this.props.dispatch(toggleScreensharing());
43
+    }
44
+
45
+    /**
46
+     * Indicates whether this button is in toggled state or not.
47
+     *
48
+     * @override
49
+     * @protected
50
+     * @returns {boolean}
51
+     */
52
+    _isToggled() {
53
+        return this.props._screensharing;
54
+    }
55
+}
56
+
57
+/**
58
+ * Maps (parts of) the redux state to the associated props for the
59
+ * {@code ToggleCameraButton} component.
60
+ *
61
+ * @param {Object} state - The Redux state.
62
+ * @private
63
+ * @returns {{
64
+ *     _disabled: boolean,
65
+ *     _screensharing: boolean
66
+ * }}
67
+ */
68
+function _mapStateToProps(state): Object {
69
+    return {
70
+        _screensharing: isLocalVideoTrackDesktop(state)
71
+    };
72
+}
73
+
74
+export default translate(connect(_mapStateToProps)(ScreenSharingAndroidButton));

+ 16
- 75
react/features/toolbox/components/native/ScreenSharingButton.js Прегледај датотеку

@@ -1,77 +1,18 @@
1
-// @flow
2
-
1
+import React from 'react';
3 2
 import { Platform } from 'react-native';
4 3
 
5
-import { translate } from '../../../base/i18n';
6
-import { IconShareDesktop } from '../../../base/icons';
7
-import { connect } from '../../../base/redux';
8
-import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
9
-import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
10
-
11
-/**
12
- * The type of the React {@code Component} props of {@link ScreenSharingButton}.
13
- */
14
-type Props = AbstractButtonProps & {
15
-
16
-    /**
17
-     * Whether video is currently muted or not.
18
-     */
19
-    _screensharing: boolean,
20
-
21
-    /**
22
-     * The redux {@code dispatch} function.
23
-     */
24
-    dispatch: Function
25
-};
26
-
27
-/**
28
- * An implementation of a button for toggling screen sharing.
29
- */
30
-class ScreenSharingButton extends AbstractButton<Props, *> {
31
-    accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
32
-    icon = IconShareDesktop;
33
-    label = 'toolbar.startScreenSharing';
34
-    toggledLabel = 'toolbar.stopScreenSharing';
35
-
36
-    /**
37
-     * Handles clicking / pressing the button.
38
-     *
39
-     * @override
40
-     * @protected
41
-     * @returns {void}
42
-     */
43
-    _handleClick() {
44
-        this.props.dispatch(toggleScreensharing());
45
-    }
46
-
47
-    /**
48
-     * Indicates whether this button is in toggled state or not.
49
-     *
50
-     * @override
51
-     * @protected
52
-     * @returns {boolean}
53
-     */
54
-    _isToggled() {
55
-        return this.props._screensharing;
56
-    }
57
-}
58
-
59
-/**
60
- * Maps (parts of) the redux state to the associated props for the
61
- * {@code ToggleCameraButton} component.
62
- *
63
- * @param {Object} state - The Redux state.
64
- * @private
65
- * @returns {{
66
- *     _disabled: boolean,
67
- *     _screensharing: boolean
68
- * }}
69
- */
70
-function _mapStateToProps(state): Object {
71
-    return {
72
-        _screensharing: isLocalVideoTrackDesktop(state),
73
-        visible: Platform.OS === 'android'
74
-    };
75
-}
76
-
77
-export default translate(connect(_mapStateToProps)(ScreenSharingButton));
4
+import ScreenSharingAndroidButton from './ScreenSharingAndroidButton.js';
5
+import ScreenSharingIosButton from './ScreenSharingIosButton.js';
6
+
7
+const ScreenSharingButton = props => (
8
+    <>
9
+        {Platform.OS === 'android'
10
+            && <ScreenSharingAndroidButton { ...props } />
11
+        }
12
+        {Platform.OS === 'ios'
13
+            && <ScreenSharingIosButton { ...props } />
14
+        }
15
+    </>
16
+);
17
+
18
+export default ScreenSharingButton;

+ 132
- 0
react/features/toolbox/components/native/ScreenSharingIosButton.js Прегледај датотеку

@@ -0,0 +1,132 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { findNodeHandle, NativeModules, Platform } from 'react-native';
5
+import { ScreenCapturePickerView } from 'react-native-webrtc';
6
+
7
+import { translate } from '../../../base/i18n';
8
+import { IconShareDesktop } from '../../../base/icons';
9
+import { connect } from '../../../base/redux';
10
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
11
+import { isLocalVideoTrackDesktop } from '../../../base/tracks';
12
+
13
+/**
14
+ * The type of the React {@code Component} props of {@link ScreenSharingIosButton}.
15
+ */
16
+type Props = AbstractButtonProps & {
17
+
18
+    /**
19
+     * Whether video is currently muted or not.
20
+     */
21
+    _screensharing: boolean,
22
+
23
+    /**
24
+     * The redux {@code dispatch} function.
25
+     */
26
+    dispatch: Function
27
+};
28
+
29
+const styles = {
30
+    screenCapturePickerView: {
31
+        display: 'none'
32
+    }
33
+};
34
+
35
+/**
36
+ * An implementation of a button for toggling screen sharing on iOS.
37
+ */
38
+class ScreenSharingIosButton extends AbstractButton<Props, *> {
39
+  _nativeComponent: ?Object;
40
+  _setNativeComponent: Function;
41
+
42
+  accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
43
+  icon = IconShareDesktop;
44
+  label = 'toolbar.startScreenSharing';
45
+  toggledLabel = 'toolbar.stopScreenSharing';
46
+
47
+  /**
48
+   * Initializes a new {@code ScreenSharingIosButton} instance.
49
+   *
50
+   * @param {Object} props - The React {@code Component} props to initialize
51
+   * the new {@code ScreenSharingIosButton} instance with.
52
+   */
53
+  constructor(props) {
54
+      super(props);
55
+
56
+      this._nativeComponent = null;
57
+
58
+      // Bind event handlers so they are only bound once per instance.
59
+      this._setNativeComponent = this._setNativeComponent.bind(this);
60
+  }
61
+
62
+  /**
63
+    * Sets the internal reference to the React Component wrapping the
64
+    * {@code RPSystemBroadcastPickerView} component.
65
+    *
66
+    * @param {ReactComponent} component - React Component.
67
+    * @returns {void}
68
+    */
69
+  _setNativeComponent(component) {
70
+      this._nativeComponent = component;
71
+  }
72
+
73
+  /**
74
+   * Handles clicking / pressing the button.
75
+   *
76
+   * @override
77
+   * @protected
78
+   * @returns {void}
79
+   */
80
+  _handleClick() {
81
+      const handle = findNodeHandle(this._nativeComponent);
82
+
83
+      NativeModules.ScreenCapturePickerViewManager.show(handle);
84
+  }
85
+
86
+  /**
87
+   * Indicates whether this button is in toggled state or not.
88
+   *
89
+   * @override
90
+   * @protected
91
+   * @returns {boolean}
92
+   */
93
+  _isToggled() {
94
+      return this.props._screensharing;
95
+  }
96
+
97
+  /**
98
+   * Helper function to be implemented by subclasses, which may return a
99
+   * new React Element to be appended at the end of the button.
100
+   *
101
+   * @protected
102
+   * @returns {ReactElement|null}
103
+   */
104
+  _getElementAfter() {
105
+      return (
106
+          <ScreenCapturePickerView
107
+              ref = { this._setNativeComponent }
108
+              style = { styles.screenCapturePickerView } />
109
+      );
110
+  }
111
+}
112
+
113
+/**
114
+ * Maps (parts of) the redux state to the associated props for the
115
+ * {@code ScreenSharingIosButton} component.
116
+ *
117
+ * @param {Object} state - The Redux state.
118
+ * @private
119
+ * @returns {{
120
+ *     _disabled: boolean,
121
+ * }}
122
+ */
123
+function _mapStateToProps(state): Object {
124
+    return {
125
+        _screensharing: isLocalVideoTrackDesktop(state),
126
+
127
+        // TODO: this should work on iOS 12 too, but our trick to show the picker doesn't work.
128
+        visible: Platform.OS === 'ios' && Platform.Version.split('.')[0] >= 14
129
+    };
130
+}
131
+
132
+export default translate(connect(_mapStateToProps)(ScreenSharingIosButton));

Loading…
Откажи
Сачувај