@cloudflare/realtimekit-react-native 0.0.0 → 0.0.1-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +83 -0
- package/RealtimeKitCore.podspec +19 -0
- package/android/build.gradle +58 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/android/gradlew +185 -0
- package/android/gradlew.bat +89 -0
- package/android/src/main/AndroidManifest.xml +23 -0
- package/android/src/main/java/com/reactnativecore/BackgroundTimer/RNBackgroundTimer.java +97 -0
- package/android/src/main/java/com/reactnativecore/Constants.java +14 -0
- package/android/src/main/java/com/reactnativecore/CoreModule.java +49 -0
- package/android/src/main/java/com/reactnativecore/CorePackage.java +34 -0
- package/android/src/main/java/com/reactnativecore/DyteHelperModule.java +268 -0
- package/android/src/main/java/com/reactnativecore/DyteHolder.java +7 -0
- package/android/src/main/java/com/reactnativecore/ForegroundService.java +48 -0
- package/android/src/main/java/com/reactnativecore/NotificationHelper.java +127 -0
- package/android/src/main/java/com/reactnativecore/RNPermissionsModule.java +226 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCBluetoothManager.java +637 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCProximitySensor.java +133 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCUtils.java +41 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallManagerModule.java +1881 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallProximityManager.java +162 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallWakeLockUtils.java +116 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/ThreadUtils.java +39 -0
- package/app.plugin.js +1 -0
- package/ios/BroadcastEventEmitter.h +11 -0
- package/ios/BroadcastEventEmitter.m +72 -0
- package/ios/Core.h +5 -0
- package/ios/Core.m +28 -0
- package/ios/Core.xcodeproj/project.pbxproj +287 -0
- package/ios/DyteRNBackgroundTimer.h +22 -0
- package/ios/DyteRNBackgroundTimer.m +90 -0
- package/ios/DyteRNPermissionHandlerNotifications.h +17 -0
- package/ios/DyteRNPermissionHandlerNotifications.m +127 -0
- package/ios/DyteRNPermissions.h +50 -0
- package/ios/DyteRNPermissions.m +275 -0
- package/ios/DyteScreensharePickerView.h +8 -0
- package/ios/DyteScreensharePickerView.m +27 -0
- package/ios/PrivacyInfo.xcprivacy +60 -0
- package/ios/RNInCallManager.h +16 -0
- package/ios/RNInCallManager.m +1354 -0
- package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.h +4 -0
- package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.m +87 -0
- package/ios/permissions/DyteRNPermissionHandlerCamera.h +5 -0
- package/ios/permissions/DyteRNPermissionHandlerCamera.m +38 -0
- package/ios/permissions/DyteRNPermissionHandlerMicrophone.h +5 -0
- package/ios/permissions/DyteRNPermissionHandlerMicrophone.m +34 -0
- package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.h +8 -0
- package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.m +70 -0
- package/ios/screenshare/DyteDarwinNotificationCenter.swift +21 -0
- package/ios/screenshare/DyteScreenshareAtomic.swift +29 -0
- package/ios/screenshare/DyteScreenshareHandler.swift +103 -0
- package/ios/screenshare/DyteScreenshareUploader.swift +139 -0
- package/ios/screenshare/DyteSocketConnection.swift +190 -0
- package/lib/BackgroundHandler.d.ts +20 -0
- package/lib/BackgroundHandler.js +87 -0
- package/lib/LocalMediaError.d.ts +4 -0
- package/lib/LocalMediaError.js +6 -0
- package/lib/LocalMediaHandler.d.ts +118 -0
- package/lib/LocalMediaHandler.js +853 -0
- package/lib/LocalMediaInterfaces.d.ts +76 -0
- package/lib/LocalMediaInterfaces.js +1 -0
- package/lib/LocalMediaUtils.d.ts +93 -0
- package/lib/LocalMediaUtils.js +276 -0
- package/lib/NativeAudioManager.d.ts +16 -0
- package/lib/NativeAudioManager.js +93 -0
- package/lib/PermissionHandler.d.ts +34 -0
- package/lib/PermissionHandler.js +179 -0
- package/lib/ReactContext.d.ts +42 -0
- package/lib/ReactContext.js +96 -0
- package/lib/ReactHooks.d.ts +11 -0
- package/lib/ReactHooks.js +73 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +40 -0
- package/lib/utils/crypto.d.ts +17 -0
- package/lib/utils/crypto.js +82 -0
- package/package.json +51 -4
- package/plugin/build/withDyte.d.ts +25 -0
- package/plugin/build/withDyte.js +100 -0
- package/plugin/src/withDyte.ts +150 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#import "DyteRNPermissionHandlerBluetoothPeripheral.h"
|
|
2
|
+
|
|
3
|
+
@import CoreBluetooth;
|
|
4
|
+
|
|
5
|
+
@interface DyteRNPermissionHandlerBluetoothPeripheral() <CBPeripheralManagerDelegate>
|
|
6
|
+
|
|
7
|
+
@property (nonatomic, strong) CBPeripheralManager* manager;
|
|
8
|
+
@property (nonatomic, strong) void (^resolve)(RNPermissionStatus status);
|
|
9
|
+
@property (nonatomic, strong) void (^reject)(NSError *error);
|
|
10
|
+
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation DyteRNPermissionHandlerBluetoothPeripheral
|
|
14
|
+
|
|
15
|
+
+ (NSArray<NSString *> * _Nonnull)usageDescriptionKeys {
|
|
16
|
+
return @[
|
|
17
|
+
@"NSBluetoothPeripheralUsageDescription",
|
|
18
|
+
@"NSBluetoothAlwaysUsageDescription",
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
+ (NSString * _Nonnull)handlerUniqueId {
|
|
23
|
+
return @"ios.permission.BLUETOOTH_PERIPHERAL";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
27
|
+
rejecter:(void (__unused ^ _Nonnull)(NSError * _Nonnull))reject {
|
|
28
|
+
#if TARGET_OS_SIMULATOR
|
|
29
|
+
return resolve(RNPermissionStatusNotAvailable);
|
|
30
|
+
#else
|
|
31
|
+
|
|
32
|
+
if (@available(iOS 13.1, *)) {
|
|
33
|
+
switch ([CBManager authorization]) {
|
|
34
|
+
case CBManagerAuthorizationNotDetermined:
|
|
35
|
+
return resolve(RNPermissionStatusNotDetermined);
|
|
36
|
+
case CBManagerAuthorizationRestricted:
|
|
37
|
+
return resolve(RNPermissionStatusRestricted);
|
|
38
|
+
case CBManagerAuthorizationDenied:
|
|
39
|
+
return resolve(RNPermissionStatusDenied);
|
|
40
|
+
case CBManagerAuthorizationAllowedAlways:
|
|
41
|
+
return resolve(RNPermissionStatusAuthorized);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
switch ([CBPeripheralManager authorizationStatus]) {
|
|
45
|
+
case CBPeripheralManagerAuthorizationStatusNotDetermined:
|
|
46
|
+
return resolve(RNPermissionStatusNotDetermined);
|
|
47
|
+
case CBPeripheralManagerAuthorizationStatusRestricted:
|
|
48
|
+
return resolve(RNPermissionStatusRestricted);
|
|
49
|
+
case CBPeripheralManagerAuthorizationStatusDenied:
|
|
50
|
+
return resolve(RNPermissionStatusDenied);
|
|
51
|
+
case CBPeripheralManagerAuthorizationStatusAuthorized:
|
|
52
|
+
return resolve(RNPermissionStatusAuthorized);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
#endif
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
59
|
+
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
|
|
60
|
+
#if TARGET_OS_SIMULATOR
|
|
61
|
+
return resolve(RNPermissionStatusNotAvailable);
|
|
62
|
+
#else
|
|
63
|
+
_resolve = resolve;
|
|
64
|
+
_reject = reject;
|
|
65
|
+
|
|
66
|
+
_manager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:@{
|
|
67
|
+
CBPeripheralManagerOptionShowPowerAlertKey: @false,
|
|
68
|
+
}];
|
|
69
|
+
#endif
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)peripheralManagerDidUpdateState:(nonnull CBPeripheralManager *)peripheral {
|
|
73
|
+
switch (peripheral.state) {
|
|
74
|
+
case CBManagerStatePoweredOff:
|
|
75
|
+
case CBManagerStateResetting:
|
|
76
|
+
case CBManagerStateUnsupported:
|
|
77
|
+
return _resolve(RNPermissionStatusNotAvailable);
|
|
78
|
+
case CBManagerStateUnknown:
|
|
79
|
+
return _resolve(RNPermissionStatusNotDetermined);
|
|
80
|
+
case CBManagerStateUnauthorized:
|
|
81
|
+
return _resolve(RNPermissionStatusDenied);
|
|
82
|
+
case CBManagerStatePoweredOn:
|
|
83
|
+
return [self checkWithResolver:_resolve rejecter:_reject];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#import "DyteRNPermissionHandlerCamera.h"
|
|
2
|
+
//#import <React/RCTLog.h>
|
|
3
|
+
|
|
4
|
+
@import AVFoundation;
|
|
5
|
+
|
|
6
|
+
@implementation DyteRNPermissionHandlerCamera
|
|
7
|
+
|
|
8
|
+
+ (NSArray<NSString *> * _Nonnull)usageDescriptionKeys {
|
|
9
|
+
return @[@"NSCameraUsageDescription"];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
+ (NSString * _Nonnull)handlerUniqueId {
|
|
13
|
+
return @"ios.permission.CAMERA";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
17
|
+
rejecter:(void (__unused ^ _Nonnull)(NSError * _Nonnull))reject {
|
|
18
|
+
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
|
|
19
|
+
case AVAuthorizationStatusNotDetermined:
|
|
20
|
+
return resolve(RNPermissionStatusNotDetermined);
|
|
21
|
+
case AVAuthorizationStatusRestricted:
|
|
22
|
+
return resolve(RNPermissionStatusRestricted);
|
|
23
|
+
case AVAuthorizationStatusDenied:
|
|
24
|
+
return resolve(RNPermissionStatusDenied);
|
|
25
|
+
case AVAuthorizationStatusAuthorized:
|
|
26
|
+
return resolve(RNPermissionStatusAuthorized);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
31
|
+
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
|
|
32
|
+
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
|
|
33
|
+
completionHandler:^(__unused BOOL granted) {
|
|
34
|
+
[self checkWithResolver:resolve rejecter:reject];
|
|
35
|
+
}];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#import "DyteRNPermissionHandlerMicrophone.h"
|
|
2
|
+
|
|
3
|
+
@import AVFoundation;
|
|
4
|
+
|
|
5
|
+
@implementation DyteRNPermissionHandlerMicrophone
|
|
6
|
+
|
|
7
|
+
+ (NSArray<NSString *> * _Nonnull)usageDescriptionKeys {
|
|
8
|
+
return @[@"NSMicrophoneUsageDescription"];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
+ (NSString * _Nonnull)handlerUniqueId {
|
|
12
|
+
return @"ios.permission.MICROPHONE";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
16
|
+
rejecter:(void (__unused ^ _Nonnull)(NSError * _Nonnull))reject {
|
|
17
|
+
switch ([[AVAudioSession sharedInstance] recordPermission]) {
|
|
18
|
+
case AVAudioSessionRecordPermissionUndetermined:
|
|
19
|
+
return resolve(RNPermissionStatusNotDetermined);
|
|
20
|
+
case AVAudioSessionRecordPermissionDenied:
|
|
21
|
+
return resolve(RNPermissionStatusDenied);
|
|
22
|
+
case AVAudioSessionRecordPermissionGranted:
|
|
23
|
+
return resolve(RNPermissionStatusAuthorized);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
28
|
+
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
|
|
29
|
+
[[AVAudioSession sharedInstance] requestRecordPermission:^(__unused BOOL granted) {
|
|
30
|
+
[self checkWithResolver:resolve rejecter:reject];
|
|
31
|
+
}];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#import "DyteRNPermissions.h"
|
|
2
|
+
|
|
3
|
+
@interface DyteRNPermissionHandlerPhotoLibrary : NSObject<DyteRNPermissionHandler>
|
|
4
|
+
|
|
5
|
+
- (void)openLimitedPhotoLibraryPickerWithResolver:(RCTPromiseResolveBlock _Nonnull)resolve
|
|
6
|
+
rejecter:(RCTPromiseRejectBlock _Nonnull)reject;
|
|
7
|
+
|
|
8
|
+
@end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#import "DyteRNPermissionHandlerPhotoLibrary.h"
|
|
2
|
+
#import <React/RCTUtils.h>
|
|
3
|
+
|
|
4
|
+
@import Photos;
|
|
5
|
+
@import PhotosUI;
|
|
6
|
+
|
|
7
|
+
@implementation DyteRNPermissionHandlerPhotoLibrary
|
|
8
|
+
|
|
9
|
+
+ (NSArray<NSString *> * _Nonnull)usageDescriptionKeys {
|
|
10
|
+
return @[@"NSPhotoLibraryUsageDescription"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
+ (NSString * _Nonnull)handlerUniqueId {
|
|
14
|
+
return @"ios.permission.PHOTO_LIBRARY";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
18
|
+
rejecter:(void (__unused ^ _Nonnull)(NSError * _Nonnull))reject {
|
|
19
|
+
PHAuthorizationStatus status;
|
|
20
|
+
|
|
21
|
+
if (@available(iOS 14.0, *)) {
|
|
22
|
+
status = [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite];
|
|
23
|
+
} else {
|
|
24
|
+
status = [PHPhotoLibrary authorizationStatus];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch (status) {
|
|
28
|
+
case PHAuthorizationStatusNotDetermined:
|
|
29
|
+
return resolve(RNPermissionStatusNotDetermined);
|
|
30
|
+
case PHAuthorizationStatusRestricted:
|
|
31
|
+
return resolve(RNPermissionStatusRestricted);
|
|
32
|
+
case PHAuthorizationStatusDenied:
|
|
33
|
+
return resolve(RNPermissionStatusDenied);
|
|
34
|
+
case PHAuthorizationStatusLimited:
|
|
35
|
+
return resolve(RNPermissionStatusLimited);
|
|
36
|
+
case PHAuthorizationStatusAuthorized:
|
|
37
|
+
return resolve(RNPermissionStatusAuthorized);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
|
|
42
|
+
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
|
|
43
|
+
if (@available(iOS 14.0, *)) {
|
|
44
|
+
[PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite handler:^(__unused PHAuthorizationStatus status) {
|
|
45
|
+
[self checkWithResolver:resolve rejecter:reject];
|
|
46
|
+
}];
|
|
47
|
+
} else {
|
|
48
|
+
[PHPhotoLibrary requestAuthorization:^(__unused PHAuthorizationStatus status) {
|
|
49
|
+
[self checkWithResolver:resolve rejecter:reject];
|
|
50
|
+
}];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)openLimitedPhotoLibraryPickerWithResolver:(RCTPromiseResolveBlock _Nonnull)resolve
|
|
55
|
+
rejecter:(RCTPromiseRejectBlock _Nonnull)reject {
|
|
56
|
+
if (@available(iOS 14, *)) {
|
|
57
|
+
if ([PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] != PHAuthorizationStatusLimited) {
|
|
58
|
+
return reject(@"cannot_open_limited_picker", @"Photo library permission isn't limited", nil);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
UIViewController *presentedViewController = RCTPresentedViewController();
|
|
62
|
+
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:presentedViewController];
|
|
63
|
+
|
|
64
|
+
resolve(@(true));
|
|
65
|
+
} else {
|
|
66
|
+
reject(@"cannot_open_limited_picker", @"Only available on iOS 14 or higher", nil);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum DyteDarwinNotification: String {
|
|
4
|
+
case broadcastStarted = "iOS_BroadcastStarted"
|
|
5
|
+
case broadcastStopped = "iOS_BroadcastStopped"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class DyteDarwinNotificationCenter {
|
|
9
|
+
|
|
10
|
+
static let shared = DyteDarwinNotificationCenter()
|
|
11
|
+
|
|
12
|
+
private let notificationCenter: CFNotificationCenter
|
|
13
|
+
|
|
14
|
+
init() {
|
|
15
|
+
notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func postNotification(_ name: DyteDarwinNotification) {
|
|
19
|
+
CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@propertyWrapper
|
|
4
|
+
struct DyteScreenshareAtomic<Value> {
|
|
5
|
+
|
|
6
|
+
private var value: Value
|
|
7
|
+
private let lock = NSLock()
|
|
8
|
+
|
|
9
|
+
init(wrappedValue value: Value) {
|
|
10
|
+
self.value = value
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
var wrappedValue: Value {
|
|
14
|
+
get { load() }
|
|
15
|
+
set { store(newValue: newValue) }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func load() -> Value {
|
|
19
|
+
lock.lock()
|
|
20
|
+
defer { lock.unlock() }
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mutating func store(newValue: Value) {
|
|
25
|
+
lock.lock()
|
|
26
|
+
defer { lock.unlock() }
|
|
27
|
+
value = newValue
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import ReplayKit
|
|
2
|
+
|
|
3
|
+
open class DyteScreenshareHandler: RPBroadcastSampleHandler {
|
|
4
|
+
|
|
5
|
+
private var clientConnection: DyteSocketConnection?
|
|
6
|
+
private var uploader: DyteScreenshareUploader?
|
|
7
|
+
private var frameCount: Int = 0
|
|
8
|
+
|
|
9
|
+
private let appGroupIdentifier: String
|
|
10
|
+
private let bundleIdentifier: String
|
|
11
|
+
|
|
12
|
+
public override init() {
|
|
13
|
+
appGroupIdentifier = ""
|
|
14
|
+
bundleIdentifier = ""
|
|
15
|
+
super.init()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public init(appGroupIdentifier: String, bundleIdentifier: String) {
|
|
19
|
+
self.appGroupIdentifier = appGroupIdentifier
|
|
20
|
+
self.bundleIdentifier = bundleIdentifier
|
|
21
|
+
super.init()
|
|
22
|
+
if let connection = DyteSocketConnection(filePath: socketFilePath) {
|
|
23
|
+
clientConnection = connection
|
|
24
|
+
print("Setup Connection")
|
|
25
|
+
setupConnection()
|
|
26
|
+
uploader = DyteScreenshareUploader(connection: connection, bundleIdentifier: bundleIdentifier)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private var socketFilePath: String {
|
|
31
|
+
let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
|
|
32
|
+
return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) {
|
|
36
|
+
// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
|
|
37
|
+
frameCount = 0
|
|
38
|
+
DyteDarwinNotificationCenter.shared.postNotification(.broadcastStarted)
|
|
39
|
+
openConnection()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public override func broadcastPaused() {
|
|
43
|
+
// User has requested to pause the broadcast. Samples will stop being delivered.
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public override func broadcastResumed() {
|
|
47
|
+
// User has requested to resume the broadcast. Samples delivery will resume.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public override func broadcastFinished() {
|
|
51
|
+
// User has requested to finish the broadcast.
|
|
52
|
+
print("Broadcast Stopped")
|
|
53
|
+
DyteDarwinNotificationCenter.shared.postNotification(.broadcastStopped)
|
|
54
|
+
clientConnection?.close()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
|
58
|
+
switch sampleBufferType {
|
|
59
|
+
case RPSampleBufferType.video:
|
|
60
|
+
frameCount += 1
|
|
61
|
+
uploader?.send(sample: sampleBuffer)
|
|
62
|
+
if frameCount % 3 == 0 {
|
|
63
|
+
uploader?.send(sample: sampleBuffer)
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private extension DyteScreenshareHandler {
|
|
72
|
+
|
|
73
|
+
func setupConnection() {
|
|
74
|
+
clientConnection?.didClose = { [weak self] error in
|
|
75
|
+
print("client connection did close \(String(describing: error))")
|
|
76
|
+
|
|
77
|
+
if let error = error {
|
|
78
|
+
self?.finishBroadcastWithError(error)
|
|
79
|
+
} else {
|
|
80
|
+
// the displayed failure message is more user friendly when using NSError instead of Error
|
|
81
|
+
let DyteScreenSharingStopped = 10001
|
|
82
|
+
let customError = NSError(domain: RPRecordingErrorDomain, code: DyteScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"])
|
|
83
|
+
self?.finishBroadcastWithError(customError)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func openConnection() {
|
|
89
|
+
let queue = DispatchQueue(label: "broadcast.connectTimer")
|
|
90
|
+
let timer = DispatchSource.makeTimerSource(queue: queue)
|
|
91
|
+
timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
|
|
92
|
+
timer.setEventHandler { [weak self] in
|
|
93
|
+
guard self?.clientConnection?.open() == true else {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
timer.cancel()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
timer.resume()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ReplayKit
|
|
3
|
+
|
|
4
|
+
private enum Constants {
|
|
5
|
+
static let bufferMaxLength = 10240
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class DyteScreenshareUploader {
|
|
9
|
+
|
|
10
|
+
private static var imageContext = CIContext(options: nil)
|
|
11
|
+
|
|
12
|
+
@DyteScreenshareAtomic private var isReady = false
|
|
13
|
+
private var connection: DyteSocketConnection
|
|
14
|
+
|
|
15
|
+
private var dataToSend: Data?
|
|
16
|
+
private var byteIndex = 0
|
|
17
|
+
|
|
18
|
+
private let serialQueue: DispatchQueue
|
|
19
|
+
|
|
20
|
+
init(connection: DyteSocketConnection, bundleIdentifier: String) {
|
|
21
|
+
self.connection = connection
|
|
22
|
+
self.serialQueue = DispatchQueue(label: bundleIdentifier)
|
|
23
|
+
|
|
24
|
+
setupConnection()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@discardableResult func send(sample buffer: CMSampleBuffer) -> Bool {
|
|
28
|
+
guard isReady else {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isReady = false
|
|
33
|
+
|
|
34
|
+
dataToSend = prepare(sample: buffer)
|
|
35
|
+
byteIndex = 0
|
|
36
|
+
|
|
37
|
+
serialQueue.async { [weak self] in
|
|
38
|
+
self?.sendDataChunk()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private extension DyteScreenshareUploader {
|
|
46
|
+
|
|
47
|
+
func setupConnection() {
|
|
48
|
+
connection.didOpen = { [weak self] in
|
|
49
|
+
self?.isReady = true
|
|
50
|
+
}
|
|
51
|
+
connection.streamHasSpaceAvailable = { [weak self] in
|
|
52
|
+
self?.serialQueue.async {
|
|
53
|
+
if let success = self?.sendDataChunk() {
|
|
54
|
+
self?.isReady = !success
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@discardableResult func sendDataChunk() -> Bool {
|
|
61
|
+
guard let dataToSend = dataToSend else {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var bytesLeft = dataToSend.count - byteIndex
|
|
66
|
+
var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft
|
|
67
|
+
|
|
68
|
+
length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes {
|
|
69
|
+
guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else {
|
|
70
|
+
return 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return connection.writeToStream(buffer: ptr, maxLength: length)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if length > 0 {
|
|
77
|
+
byteIndex += length
|
|
78
|
+
bytesLeft -= length
|
|
79
|
+
|
|
80
|
+
if bytesLeft == 0 {
|
|
81
|
+
self.dataToSend = nil
|
|
82
|
+
byteIndex = 0
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
print("writeBufferToStream failure")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func prepare(sample buffer: CMSampleBuffer) -> Data? {
|
|
92
|
+
guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
|
|
93
|
+
print("image buffer not available")
|
|
94
|
+
return nil
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
|
|
98
|
+
|
|
99
|
+
let scaleFactor = 2.0
|
|
100
|
+
let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor)
|
|
101
|
+
let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor)
|
|
102
|
+
let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0
|
|
103
|
+
|
|
104
|
+
let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor))
|
|
105
|
+
let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform)
|
|
106
|
+
|
|
107
|
+
CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
|
|
108
|
+
|
|
109
|
+
guard let messageData = bufferData else {
|
|
110
|
+
print("corrupted image buffer")
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue()
|
|
115
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString)
|
|
116
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString)
|
|
117
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString)
|
|
118
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString)
|
|
119
|
+
|
|
120
|
+
CFHTTPMessageSetBody(httpResponse, messageData as CFData)
|
|
121
|
+
|
|
122
|
+
let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data?
|
|
123
|
+
|
|
124
|
+
return serializedMessage
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? {
|
|
128
|
+
let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform)
|
|
129
|
+
|
|
130
|
+
guard let colorSpace = image.colorSpace else {
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0]
|
|
135
|
+
|
|
136
|
+
return DyteScreenshareUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|