@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.
Files changed (83) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +83 -0
  3. package/RealtimeKitCore.podspec +19 -0
  4. package/android/build.gradle +58 -0
  5. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  7. package/android/gradlew +185 -0
  8. package/android/gradlew.bat +89 -0
  9. package/android/src/main/AndroidManifest.xml +23 -0
  10. package/android/src/main/java/com/reactnativecore/BackgroundTimer/RNBackgroundTimer.java +97 -0
  11. package/android/src/main/java/com/reactnativecore/Constants.java +14 -0
  12. package/android/src/main/java/com/reactnativecore/CoreModule.java +49 -0
  13. package/android/src/main/java/com/reactnativecore/CorePackage.java +34 -0
  14. package/android/src/main/java/com/reactnativecore/DyteHelperModule.java +268 -0
  15. package/android/src/main/java/com/reactnativecore/DyteHolder.java +7 -0
  16. package/android/src/main/java/com/reactnativecore/ForegroundService.java +48 -0
  17. package/android/src/main/java/com/reactnativecore/NotificationHelper.java +127 -0
  18. package/android/src/main/java/com/reactnativecore/RNPermissionsModule.java +226 -0
  19. package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCBluetoothManager.java +637 -0
  20. package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCProximitySensor.java +133 -0
  21. package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCUtils.java +41 -0
  22. package/android/src/main/java/com/reactnativecore/incallmanager/InCallManagerModule.java +1881 -0
  23. package/android/src/main/java/com/reactnativecore/incallmanager/InCallProximityManager.java +162 -0
  24. package/android/src/main/java/com/reactnativecore/incallmanager/InCallWakeLockUtils.java +116 -0
  25. package/android/src/main/java/com/reactnativecore/incallmanager/ThreadUtils.java +39 -0
  26. package/app.plugin.js +1 -0
  27. package/ios/BroadcastEventEmitter.h +11 -0
  28. package/ios/BroadcastEventEmitter.m +72 -0
  29. package/ios/Core.h +5 -0
  30. package/ios/Core.m +28 -0
  31. package/ios/Core.xcodeproj/project.pbxproj +287 -0
  32. package/ios/DyteRNBackgroundTimer.h +22 -0
  33. package/ios/DyteRNBackgroundTimer.m +90 -0
  34. package/ios/DyteRNPermissionHandlerNotifications.h +17 -0
  35. package/ios/DyteRNPermissionHandlerNotifications.m +127 -0
  36. package/ios/DyteRNPermissions.h +50 -0
  37. package/ios/DyteRNPermissions.m +275 -0
  38. package/ios/DyteScreensharePickerView.h +8 -0
  39. package/ios/DyteScreensharePickerView.m +27 -0
  40. package/ios/PrivacyInfo.xcprivacy +60 -0
  41. package/ios/RNInCallManager.h +16 -0
  42. package/ios/RNInCallManager.m +1354 -0
  43. package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.h +4 -0
  44. package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.m +87 -0
  45. package/ios/permissions/DyteRNPermissionHandlerCamera.h +5 -0
  46. package/ios/permissions/DyteRNPermissionHandlerCamera.m +38 -0
  47. package/ios/permissions/DyteRNPermissionHandlerMicrophone.h +5 -0
  48. package/ios/permissions/DyteRNPermissionHandlerMicrophone.m +34 -0
  49. package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.h +8 -0
  50. package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.m +70 -0
  51. package/ios/screenshare/DyteDarwinNotificationCenter.swift +21 -0
  52. package/ios/screenshare/DyteScreenshareAtomic.swift +29 -0
  53. package/ios/screenshare/DyteScreenshareHandler.swift +103 -0
  54. package/ios/screenshare/DyteScreenshareUploader.swift +139 -0
  55. package/ios/screenshare/DyteSocketConnection.swift +190 -0
  56. package/lib/BackgroundHandler.d.ts +20 -0
  57. package/lib/BackgroundHandler.js +87 -0
  58. package/lib/LocalMediaError.d.ts +4 -0
  59. package/lib/LocalMediaError.js +6 -0
  60. package/lib/LocalMediaHandler.d.ts +118 -0
  61. package/lib/LocalMediaHandler.js +853 -0
  62. package/lib/LocalMediaInterfaces.d.ts +76 -0
  63. package/lib/LocalMediaInterfaces.js +1 -0
  64. package/lib/LocalMediaUtils.d.ts +93 -0
  65. package/lib/LocalMediaUtils.js +276 -0
  66. package/lib/NativeAudioManager.d.ts +16 -0
  67. package/lib/NativeAudioManager.js +93 -0
  68. package/lib/PermissionHandler.d.ts +34 -0
  69. package/lib/PermissionHandler.js +179 -0
  70. package/lib/ReactContext.d.ts +42 -0
  71. package/lib/ReactContext.js +96 -0
  72. package/lib/ReactHooks.d.ts +11 -0
  73. package/lib/ReactHooks.js +73 -0
  74. package/lib/index.d.ts +10 -0
  75. package/lib/index.js +40 -0
  76. package/lib/utils/crypto.d.ts +17 -0
  77. package/lib/utils/crypto.js +82 -0
  78. package/package.json +51 -4
  79. package/plugin/build/withDyte.d.ts +25 -0
  80. package/plugin/build/withDyte.js +100 -0
  81. package/plugin/src/withDyte.ts +150 -0
  82. package/plugin/tsconfig.json +10 -0
  83. package/plugin/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,4 @@
1
+ #import "DyteRNPermissions.h"
2
+ @interface DyteRNPermissionHandlerBluetoothPeripheral : NSObject<DyteRNPermissionHandler>
3
+
4
+ @end
@@ -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,5 @@
1
+ #import "DyteRNPermissions.h"
2
+
3
+ @interface DyteRNPermissionHandlerCamera : NSObject<DyteRNPermissionHandler>
4
+
5
+ @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,5 @@
1
+ #import "DyteRNPermissions.h"
2
+
3
+ @interface DyteRNPermissionHandlerMicrophone : NSObject<DyteRNPermissionHandler>
4
+
5
+ @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
+