@stream-io/video-react-native-sdk 0.2.0 → 0.2.1

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 (109) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/commonjs/components/Call/CallControls/ScreenShareButton.js +120 -0
  3. package/dist/commonjs/components/Call/CallControls/ScreenShareButton.js.map +1 -0
  4. package/dist/commonjs/components/Call/CallControls/index.js +11 -0
  5. package/dist/commonjs/components/Call/CallControls/index.js.map +1 -1
  6. package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js +5 -4
  7. package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
  8. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js +1 -1
  9. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
  10. package/dist/commonjs/constants/TestIds.js +1 -0
  11. package/dist/commonjs/constants/TestIds.js.map +1 -1
  12. package/dist/commonjs/hooks/index.js +11 -0
  13. package/dist/commonjs/hooks/index.js.map +1 -1
  14. package/dist/commonjs/hooks/useIsIosScreenshareBroadcastStarted.js +25 -0
  15. package/dist/commonjs/hooks/useIsIosScreenshareBroadcastStarted.js.map +1 -0
  16. package/dist/commonjs/icons/ScreenShare.js +2 -14
  17. package/dist/commonjs/icons/ScreenShare.js.map +1 -1
  18. package/dist/commonjs/icons/ScreenShareIndicator.js +38 -0
  19. package/dist/commonjs/icons/ScreenShareIndicator.js.map +1 -0
  20. package/dist/commonjs/icons/index.js +11 -0
  21. package/dist/commonjs/icons/index.js.map +1 -1
  22. package/dist/commonjs/theme/theme.js +4 -0
  23. package/dist/commonjs/theme/theme.js.map +1 -1
  24. package/dist/commonjs/translations/en.json +1 -0
  25. package/dist/commonjs/version.js +1 -1
  26. package/dist/module/components/Call/CallControls/ScreenShareButton.js +112 -0
  27. package/dist/module/components/Call/CallControls/ScreenShareButton.js.map +1 -0
  28. package/dist/module/components/Call/CallControls/index.js +1 -0
  29. package/dist/module/components/Call/CallControls/index.js.map +1 -1
  30. package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js +6 -5
  31. package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
  32. package/dist/module/components/Participant/ParticipantView/VideoRenderer.js +1 -1
  33. package/dist/module/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
  34. package/dist/module/constants/TestIds.js +1 -0
  35. package/dist/module/constants/TestIds.js.map +1 -1
  36. package/dist/module/hooks/index.js +1 -0
  37. package/dist/module/hooks/index.js.map +1 -1
  38. package/dist/module/hooks/useIsIosScreenshareBroadcastStarted.js +19 -0
  39. package/dist/module/hooks/useIsIosScreenshareBroadcastStarted.js.map +1 -0
  40. package/dist/module/icons/ScreenShare.js +3 -15
  41. package/dist/module/icons/ScreenShare.js.map +1 -1
  42. package/dist/module/icons/ScreenShareIndicator.js +28 -0
  43. package/dist/module/icons/ScreenShareIndicator.js.map +1 -0
  44. package/dist/module/icons/index.js +1 -0
  45. package/dist/module/icons/index.js.map +1 -1
  46. package/dist/module/theme/theme.js +4 -0
  47. package/dist/module/theme/theme.js.map +1 -1
  48. package/dist/module/translations/en.json +1 -0
  49. package/dist/module/version.js +1 -1
  50. package/dist/typescript/components/Call/CallControls/ScreenShareButton.d.ts +22 -0
  51. package/dist/typescript/components/Call/CallControls/ScreenShareButton.d.ts.map +1 -0
  52. package/dist/typescript/components/Call/CallControls/index.d.ts +1 -0
  53. package/dist/typescript/components/Call/CallControls/index.d.ts.map +1 -1
  54. package/dist/typescript/components/Participant/ParticipantView/ParticipantLabel.d.ts.map +1 -1
  55. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer.d.ts.map +1 -1
  56. package/dist/typescript/constants/TestIds.d.ts +1 -0
  57. package/dist/typescript/constants/TestIds.d.ts.map +1 -1
  58. package/dist/typescript/hooks/index.d.ts +1 -0
  59. package/dist/typescript/hooks/index.d.ts.map +1 -1
  60. package/dist/typescript/hooks/useIsIosScreenshareBroadcastStarted.d.ts +2 -0
  61. package/dist/typescript/hooks/useIsIosScreenshareBroadcastStarted.d.ts.map +1 -0
  62. package/dist/typescript/icons/ScreenShare.d.ts.map +1 -1
  63. package/dist/typescript/icons/ScreenShareIndicator.d.ts +8 -0
  64. package/dist/typescript/icons/ScreenShareIndicator.d.ts.map +1 -0
  65. package/dist/typescript/icons/index.d.ts +1 -0
  66. package/dist/typescript/icons/index.d.ts.map +1 -1
  67. package/dist/typescript/theme/theme.d.ts +4 -0
  68. package/dist/typescript/theme/theme.d.ts.map +1 -1
  69. package/dist/typescript/translations/index.d.ts +1 -0
  70. package/dist/typescript/translations/index.d.ts.map +1 -1
  71. package/dist/typescript/version.d.ts +1 -1
  72. package/expo-config-plugin/dist/common/types.d.ts +2 -0
  73. package/expo-config-plugin/dist/index.js +2 -0
  74. package/expo-config-plugin/dist/withAndroidManifest.js +9 -5
  75. package/expo-config-plugin/dist/withBuildProperties.d.ts +2 -2
  76. package/expo-config-plugin/dist/withBuildProperties.js +2 -2
  77. package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.d.ts +11 -0
  78. package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.js +225 -0
  79. package/expo-config-plugin/dist/withIosScreenCapture/index.d.ts +4 -0
  80. package/expo-config-plugin/dist/withIosScreenCapture/index.js +20 -0
  81. package/expo-config-plugin/dist/withIosScreenCapture/withFilesMod.d.ts +4 -0
  82. package/expo-config-plugin/dist/withIosScreenCapture/withFilesMod.js +71 -0
  83. package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.d.ts +4 -0
  84. package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.js +33 -0
  85. package/expo-config-plugin/dist/withIosScreenCapture/withTarget.d.ts +4 -0
  86. package/expo-config-plugin/dist/withIosScreenCapture/withTarget.js +122 -0
  87. package/expo-config-plugin/static/Atomic.swift +36 -0
  88. package/expo-config-plugin/static/DarwinNotificationCenter.swift +25 -0
  89. package/expo-config-plugin/static/SampleHandler.swift +99 -0
  90. package/expo-config-plugin/static/SampleUploader.swift +143 -0
  91. package/expo-config-plugin/static/SocketConnection.swift +195 -0
  92. package/ios/StreamVideoReactNative-Bridging-Header.h +2 -1
  93. package/ios/StreamVideoReactNative.h +4 -2
  94. package/ios/StreamVideoReactNative.m +82 -1
  95. package/ios/StreamVideoReactNative.xcodeproj/project.pbxproj +3 -3
  96. package/package.json +2 -1
  97. package/src/components/Call/CallControls/ScreenShareButton.tsx +127 -0
  98. package/src/components/Call/CallControls/index.tsx +1 -0
  99. package/src/components/Participant/ParticipantView/ParticipantLabel.tsx +14 -5
  100. package/src/components/Participant/ParticipantView/VideoRenderer.tsx +3 -1
  101. package/src/constants/TestIds.ts +1 -0
  102. package/src/hooks/index.ts +1 -0
  103. package/src/hooks/useIsIosScreenshareBroadcastStarted.ts +33 -0
  104. package/src/icons/ScreenShare.tsx +3 -18
  105. package/src/icons/ScreenShareIndicator.tsx +34 -0
  106. package/src/icons/index.tsx +1 -0
  107. package/src/theme/theme.ts +8 -0
  108. package/src/translations/en.json +1 -0
  109. package/src/version.ts +1 -1
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const config_plugins_1 = require("@expo/config-plugins");
30
+ const plist_1 = __importDefault(require("@expo/plist"));
31
+ const fs = __importStar(require("fs"));
32
+ const path = __importStar(require("path"));
33
+ const addBroadcastExtensionXcodeTarget_1 = __importDefault(require("./addBroadcastExtensionXcodeTarget"));
34
+ // adds the extension's entitlements file
35
+ const withTarget = (configuration, props) => {
36
+ return (0, config_plugins_1.withXcodeProject)(configuration, (config) => {
37
+ const appName = config.modRequest.projectName;
38
+ const extensionName = 'broadcast';
39
+ const extensionBundleIdentifier = `${config.ios
40
+ .bundleIdentifier}.broadcast`;
41
+ const currentProjectVersion = config.ios.buildNumber || '1';
42
+ const marketingVersion = config.version;
43
+ const proj = config.modResults;
44
+ const developmentTeamId = props?.appleTeamId;
45
+ if (!developmentTeamId) {
46
+ throw new Error('No appleTeamId was provided in the Expo config. Please provide one to create the screenshare broadcast extension');
47
+ }
48
+ (0, addBroadcastExtensionXcodeTarget_1.default)(proj, {
49
+ appName,
50
+ extensionName,
51
+ extensionBundleIdentifier,
52
+ currentProjectVersion,
53
+ marketingVersion,
54
+ developmentTeamId,
55
+ });
56
+ const appGroupIdentifier = `group.${config.ios
57
+ .bundleIdentifier}.appgroup`;
58
+ const extensionRootPath = path.join(config.modRequest.platformProjectRoot, 'broadcast');
59
+ addBroadcastEntitlementsFile({
60
+ proj,
61
+ extensionRootPath,
62
+ appGroupIdentifier,
63
+ });
64
+ addBroadcastSourceFiles({
65
+ proj,
66
+ extensionRootPath,
67
+ appGroupIdentifier,
68
+ });
69
+ return config;
70
+ });
71
+ };
72
+ exports.default = withTarget;
73
+ const addBroadcastEntitlementsFile = ({ proj, extensionRootPath, appGroupIdentifier, }) => {
74
+ const entitlementsPath = path.join(extensionRootPath, 'broadcast.entitlements');
75
+ const extensionEntitlements = {
76
+ 'com.apple.security.application-groups': [appGroupIdentifier],
77
+ };
78
+ // create file
79
+ fs.mkdirSync(path.dirname(entitlementsPath), {
80
+ recursive: true,
81
+ });
82
+ fs.writeFileSync(entitlementsPath, plist_1.default.build(extensionEntitlements));
83
+ // add file to extension group
84
+ const targetUuid = proj.findTargetKey('broadcast');
85
+ const groupUuid = proj.findPBXGroupKey({ name: 'broadcast' });
86
+ proj.addFile('broadcast.entitlements', groupUuid, {
87
+ target: targetUuid,
88
+ lastKnownFileType: 'text.plist.entitlements',
89
+ });
90
+ };
91
+ const addBroadcastSourceFiles = ({ proj, extensionRootPath, appGroupIdentifier, }) => {
92
+ fs.mkdirSync(extensionRootPath, { recursive: true });
93
+ fs.copyFileSync(path.join(__dirname, '..', '..', 'static', 'Atomic.swift'), path.join(extensionRootPath, 'Atomic.swift'));
94
+ fs.copyFileSync(path.join(__dirname, '..', '..', 'static', 'DarwinNotificationCenter.swift'), path.join(extensionRootPath, 'DarwinNotificationCenter.swift'));
95
+ fs.copyFileSync(path.join(__dirname, '..', '..', 'static', 'SampleUploader.swift'), path.join(extensionRootPath, 'SampleUploader.swift'));
96
+ fs.copyFileSync(path.join(__dirname, '..', '..', 'static', 'SocketConnection.swift'), path.join(extensionRootPath, 'SocketConnection.swift'));
97
+ // Update app group bundle id in SampleHandler code
98
+ const code = fs.readFileSync(path.join(extensionRootPath, 'SampleHandler.swift'), { encoding: 'utf-8' });
99
+ fs.writeFileSync(path.join(extensionRootPath, 'SampleHandler.swift'), code.replace('group.com.example.broadcast.appgroup', appGroupIdentifier));
100
+ const targetUuid = proj.findTargetKey('broadcast');
101
+ const groupUuid = proj.findPBXGroupKey({ name: 'broadcast' });
102
+ if (!targetUuid) {
103
+ console.error('Failed to find "broadcast" target!');
104
+ return;
105
+ }
106
+ if (!groupUuid) {
107
+ console.error('Failed to find "broadcast" group!');
108
+ return;
109
+ }
110
+ proj.addSourceFile('Atomic.swift', {
111
+ target: targetUuid,
112
+ }, groupUuid);
113
+ proj.addSourceFile('DarwinNotificationCenter.swift', {
114
+ target: targetUuid,
115
+ }, groupUuid);
116
+ proj.addSourceFile('SampleUploader.swift', {
117
+ target: targetUuid,
118
+ }, groupUuid);
119
+ proj.addSourceFile('SocketConnection.swift', {
120
+ target: targetUuid,
121
+ }, groupUuid);
122
+ };
@@ -0,0 +1,36 @@
1
+ //
2
+ // Atomic.swift
3
+ // Broadcast Extension
4
+ //
5
+ // Created by Maksym Shcheglov.
6
+ // https://www.onswiftwings.com/posts/atomic-property-wrapper/
7
+ //
8
+ import Foundation
9
+
10
+ @propertyWrapper
11
+ struct Atomic<Value> {
12
+
13
+ private var value: Value
14
+ private let lock = NSLock()
15
+
16
+ init(wrappedValue value: Value) {
17
+ self.value = value
18
+ }
19
+
20
+ var wrappedValue: Value {
21
+ get { load() }
22
+ set { store(newValue: newValue) }
23
+ }
24
+
25
+ func load() -> Value {
26
+ lock.lock()
27
+ defer { lock.unlock() }
28
+ return value
29
+ }
30
+
31
+ mutating func store(newValue: Value) {
32
+ lock.lock()
33
+ defer { lock.unlock() }
34
+ value = newValue
35
+ }
36
+ }
@@ -0,0 +1,25 @@
1
+ //
2
+ // DarwinNotificationCenter.swift
3
+ // Broadcast Extension
4
+ //
5
+ import Foundation
6
+
7
+ enum DarwinNotification: String {
8
+ case broadcastStarted = "iOS_BroadcastStarted"
9
+ case broadcastStopped = "iOS_BroadcastStopped"
10
+ }
11
+
12
+ class DarwinNotificationCenter {
13
+
14
+ static let shared = DarwinNotificationCenter()
15
+
16
+ private let notificationCenter: CFNotificationCenter
17
+
18
+ init() {
19
+ notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
20
+ }
21
+
22
+ func postNotification(_ name: DarwinNotification) {
23
+ CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true)
24
+ }
25
+ }
@@ -0,0 +1,99 @@
1
+ //
2
+ // SampleHandler.swift
3
+ // Broadcast Extension
4
+ //
5
+ import ReplayKit
6
+ import OSLog
7
+
8
+ let broadcastLogger = OSLog(subsystem: "io.getstream.reactnative", category: "Broadcast")
9
+ private enum Constants {
10
+ // the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app.
11
+ static let appGroupIdentifier = "group.com.example.broadcast.appgroup"
12
+ }
13
+ class SampleHandler: RPBroadcastSampleHandler {
14
+
15
+ private var clientConnection: SocketConnection?
16
+ private var uploader: SampleUploader?
17
+
18
+ private var frameCount: Int = 0
19
+
20
+ var socketFilePath: String {
21
+ let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
22
+ return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
23
+ }
24
+
25
+ override init() {
26
+ super.init()
27
+ if let connection = SocketConnection(filePath: socketFilePath) {
28
+ clientConnection = connection
29
+ setupConnection()
30
+
31
+ uploader = SampleUploader(connection: connection)
32
+ }
33
+ os_log(.debug, log: broadcastLogger, "%{public}s", socketFilePath)
34
+ }
35
+
36
+ override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) {
37
+ // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
38
+ frameCount = 0
39
+
40
+ DarwinNotificationCenter.shared.postNotification(.broadcastStarted)
41
+ openConnection()
42
+ }
43
+
44
+ override func broadcastPaused() {
45
+ // User has requested to pause the broadcast. Samples will stop being delivered.
46
+ }
47
+
48
+ override func broadcastResumed() {
49
+ // User has requested to resume the broadcast. Samples delivery will resume.
50
+ }
51
+
52
+ override func broadcastFinished() {
53
+ // User has requested to finish the broadcast.
54
+ DarwinNotificationCenter.shared.postNotification(.broadcastStopped)
55
+ clientConnection?.close()
56
+ }
57
+
58
+ override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
59
+ switch sampleBufferType {
60
+ case RPSampleBufferType.video:
61
+ uploader?.send(sample: sampleBuffer)
62
+ default:
63
+ break
64
+ }
65
+ }
66
+ }
67
+
68
+ private extension SampleHandler {
69
+
70
+ func setupConnection() {
71
+ clientConnection?.didClose = { [weak self] error in
72
+ os_log(.debug, log: broadcastLogger, "client connection did close \(String(describing: error))")
73
+
74
+ if let error = error {
75
+ self?.finishBroadcastWithError(error)
76
+ } else {
77
+ // the displayed failure message is more user friendly when using NSError instead of Error
78
+ let JMScreenSharingStopped = 10001
79
+ let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"])
80
+ self?.finishBroadcastWithError(customError)
81
+ }
82
+ }
83
+ }
84
+
85
+ func openConnection() {
86
+ let queue = DispatchQueue(label: "broadcast.connectTimer")
87
+ let timer = DispatchSource.makeTimerSource(queue: queue)
88
+ timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
89
+ timer.setEventHandler { [weak self] in
90
+ guard self?.clientConnection?.open() == true else {
91
+ return
92
+ }
93
+
94
+ timer.cancel()
95
+ }
96
+
97
+ timer.resume()
98
+ }
99
+ }
@@ -0,0 +1,143 @@
1
+ //
2
+ // SampleUploader.swift
3
+ // Broadcast Extension
4
+ //
5
+ import Foundation
6
+ import ReplayKit
7
+ import OSLog
8
+
9
+ private enum Constants {
10
+ static let bufferMaxLength = 10240
11
+ }
12
+
13
+ class SampleUploader {
14
+
15
+ private static var imageContext = CIContext(options: nil)
16
+
17
+ @Atomic private var isReady = false
18
+ private var connection: SocketConnection
19
+
20
+ private var dataToSend: Data?
21
+ private var byteIndex = 0
22
+
23
+ private let serialQueue: DispatchQueue
24
+
25
+ init(connection: SocketConnection) {
26
+ self.connection = connection
27
+ self.serialQueue = DispatchQueue(label: "org.getstream.sampleUploader")
28
+
29
+ setupConnection()
30
+ }
31
+
32
+ @discardableResult func send(sample buffer: CMSampleBuffer) -> Bool {
33
+ guard isReady else {
34
+ return false
35
+ }
36
+
37
+ isReady = false
38
+
39
+ dataToSend = prepare(sample: buffer)
40
+ byteIndex = 0
41
+
42
+ serialQueue.async { [weak self] in
43
+ self?.sendDataChunk()
44
+ }
45
+
46
+ return true
47
+ }
48
+ }
49
+
50
+ private extension SampleUploader {
51
+
52
+ func setupConnection() {
53
+ connection.didOpen = { [weak self] in
54
+ self?.isReady = true
55
+ }
56
+ connection.streamHasSpaceAvailable = { [weak self] in
57
+ self?.serialQueue.async {
58
+ if let success = self?.sendDataChunk() {
59
+ self?.isReady = !success
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ @discardableResult func sendDataChunk() -> Bool {
66
+ guard let dataToSend = dataToSend else {
67
+ return false
68
+ }
69
+
70
+ var bytesLeft = dataToSend.count - byteIndex
71
+ var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft
72
+
73
+ length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes {
74
+ guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else {
75
+ return 0
76
+ }
77
+
78
+ return connection.writeToStream(buffer: ptr, maxLength: length)
79
+ }
80
+
81
+ if length > 0 {
82
+ byteIndex += length
83
+ bytesLeft -= length
84
+
85
+ if bytesLeft == 0 {
86
+ self.dataToSend = nil
87
+ byteIndex = 0
88
+ }
89
+ } else {
90
+ os_log(.debug, log: broadcastLogger, "writeBufferToStream failure")
91
+ }
92
+
93
+ return true
94
+ }
95
+
96
+ func prepare(sample buffer: CMSampleBuffer) -> Data? {
97
+ guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
98
+ os_log(.debug, log: broadcastLogger, "image buffer not available")
99
+ return nil
100
+ }
101
+
102
+ CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
103
+
104
+ let scaleFactor = 1.0
105
+ let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor)
106
+ let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor)
107
+ let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0
108
+
109
+ let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor))
110
+ let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform)
111
+
112
+ CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
113
+
114
+ guard let messageData = bufferData else {
115
+ os_log(.debug, log: broadcastLogger, "corrupted image buffer")
116
+ return nil
117
+ }
118
+
119
+ let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue()
120
+ CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString)
121
+ CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString)
122
+ CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString)
123
+ CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString)
124
+
125
+ CFHTTPMessageSetBody(httpResponse, messageData as CFData)
126
+
127
+ let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data?
128
+
129
+ return serializedMessage
130
+ }
131
+
132
+ func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? {
133
+ let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform)
134
+
135
+ guard let colorSpace = image.colorSpace else {
136
+ return nil
137
+ }
138
+
139
+ let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0]
140
+
141
+ return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options)
142
+ }
143
+ }
@@ -0,0 +1,195 @@
1
+ //
2
+ // SocketConnection.swift
3
+ // Broadcast Extension
4
+ //
5
+ import Foundation
6
+ import OSLog
7
+
8
+ class SocketConnection: NSObject {
9
+ var didOpen: (() -> Void)?
10
+ var didClose: ((Error?) -> Void)?
11
+ var streamHasSpaceAvailable: (() -> Void)?
12
+
13
+ private let filePath: String
14
+ private var socketHandle: Int32 = -1
15
+ private var address: sockaddr_un?
16
+
17
+ private var inputStream: InputStream?
18
+ private var outputStream: OutputStream?
19
+
20
+ private var networkQueue: DispatchQueue?
21
+ private var shouldKeepRunning = false
22
+
23
+ init?(filePath path: String) {
24
+ filePath = path
25
+ socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
26
+
27
+ guard socketHandle != -1 else {
28
+ os_log(.debug, log: broadcastLogger, "failure: create socket")
29
+ return nil
30
+ }
31
+ }
32
+
33
+ func open() -> Bool {
34
+ os_log(.debug, log: broadcastLogger, "open socket connection")
35
+
36
+ guard FileManager.default.fileExists(atPath: filePath) else {
37
+ os_log(.debug, log: broadcastLogger, "failure: socket file missing")
38
+ return false
39
+ }
40
+
41
+ guard setupAddress() == true else {
42
+ return false
43
+ }
44
+
45
+ guard connectSocket() == true else {
46
+ return false
47
+ }
48
+
49
+ setupStreams()
50
+
51
+ inputStream?.open()
52
+ outputStream?.open()
53
+
54
+ return true
55
+ }
56
+
57
+ func close() {
58
+ unscheduleStreams()
59
+
60
+ inputStream?.delegate = nil
61
+ outputStream?.delegate = nil
62
+
63
+ inputStream?.close()
64
+ outputStream?.close()
65
+
66
+ inputStream = nil
67
+ outputStream = nil
68
+ }
69
+
70
+ func writeToStream(buffer: UnsafePointer<UInt8>, maxLength length: Int) -> Int {
71
+ outputStream?.write(buffer, maxLength: length) ?? 0
72
+ }
73
+ }
74
+
75
+ extension SocketConnection: StreamDelegate {
76
+
77
+ func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
78
+ switch eventCode {
79
+ case .openCompleted:
80
+ os_log(.debug, log: broadcastLogger, "client stream open completed")
81
+ if aStream == outputStream {
82
+ didOpen?()
83
+ }
84
+ case .hasBytesAvailable:
85
+ if aStream == inputStream {
86
+ var buffer: UInt8 = 0
87
+ let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1)
88
+ if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd {
89
+ os_log(.debug, log: broadcastLogger, "server socket closed")
90
+ close()
91
+ notifyDidClose(error: nil)
92
+ }
93
+ }
94
+ case .hasSpaceAvailable:
95
+ if aStream == outputStream {
96
+ streamHasSpaceAvailable?()
97
+ }
98
+ case .errorOccurred:
99
+ os_log(.debug, log: broadcastLogger, "client stream error occured: \(String(describing: aStream.streamError))")
100
+ close()
101
+ notifyDidClose(error: aStream.streamError)
102
+
103
+ default:
104
+ break
105
+ }
106
+ }
107
+ }
108
+
109
+ private extension SocketConnection {
110
+
111
+ func setupAddress() -> Bool {
112
+ var addr = sockaddr_un()
113
+ guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else {
114
+ os_log(.debug, log: broadcastLogger, "failure: fd path is too long")
115
+ return false
116
+ }
117
+
118
+ _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
119
+ filePath.withCString {
120
+ strncpy(ptr, $0, filePath.count)
121
+ }
122
+ }
123
+
124
+ address = addr
125
+ return true
126
+ }
127
+
128
+ func connectSocket() -> Bool {
129
+ guard var addr = address else {
130
+ return false
131
+ }
132
+
133
+ let status = withUnsafePointer(to: &addr) { ptr in
134
+ ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
135
+ Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
136
+ }
137
+ }
138
+
139
+ guard status == noErr else {
140
+ os_log(.debug, log: broadcastLogger, "failure: \(status)")
141
+ return false
142
+ }
143
+
144
+ return true
145
+ }
146
+
147
+ func setupStreams() {
148
+ var readStream: Unmanaged<CFReadStream>?
149
+ var writeStream: Unmanaged<CFWriteStream>?
150
+
151
+ CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream)
152
+
153
+ inputStream = readStream?.takeRetainedValue()
154
+ inputStream?.delegate = self
155
+ inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
156
+
157
+ outputStream = writeStream?.takeRetainedValue()
158
+ outputStream?.delegate = self
159
+ outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
160
+
161
+ scheduleStreams()
162
+ }
163
+
164
+ func scheduleStreams() {
165
+ shouldKeepRunning = true
166
+
167
+ networkQueue = DispatchQueue.global(qos: .userInitiated)
168
+ networkQueue?.async { [weak self] in
169
+ self?.inputStream?.schedule(in: .current, forMode: .common)
170
+ self?.outputStream?.schedule(in: .current, forMode: .common)
171
+ RunLoop.current.run()
172
+
173
+ var isRunning = false
174
+
175
+ repeat {
176
+ isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture)
177
+ } while (isRunning)
178
+ }
179
+ }
180
+
181
+ func unscheduleStreams() {
182
+ networkQueue?.sync { [weak self] in
183
+ self?.inputStream?.remove(from: .current, forMode: .common)
184
+ self?.outputStream?.remove(from: .current, forMode: .common)
185
+ }
186
+
187
+ shouldKeepRunning = false
188
+ }
189
+
190
+ func notifyDidClose(error: Error?) {
191
+ if didClose != nil {
192
+ didClose?(error)
193
+ }
194
+ }
195
+ }
@@ -1,2 +1,3 @@
1
1
  #import <React/RCTBridgeModule.h>
2
- #import <React/RCTViewManager.h>
2
+ #import <React/RCTViewManager.h>
3
+ #import <React/RCTEventEmitter.h>
@@ -1,5 +1,7 @@
1
- @interface StreamVideoReactNative : NSObject <RCTBridgeModule>
1
+ @interface StreamVideoReactNative : RCTEventEmitter <RCTBridgeModule>
2
+
3
+ -(void)screenShareEventReceived:(NSString*)event;
2
4
 
3
5
  +(void)setup;
4
6
 
5
- @end
7
+ @end