@stream-io/video-react-native-sdk 0.1.14 → 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.
- package/CHANGELOG.md +22 -0
- package/dist/commonjs/components/Call/CallControls/ScreenShareButton.js +120 -0
- package/dist/commonjs/components/Call/CallControls/ScreenShareButton.js.map +1 -0
- package/dist/commonjs/components/Call/CallControls/index.js +11 -0
- package/dist/commonjs/components/Call/CallControls/index.js.map +1 -1
- package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js +5 -4
- package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
- package/dist/commonjs/constants/TestIds.js +1 -0
- package/dist/commonjs/constants/TestIds.js.map +1 -1
- package/dist/commonjs/hooks/index.js +11 -0
- package/dist/commonjs/hooks/index.js.map +1 -1
- package/dist/commonjs/hooks/useIsIosScreenshareBroadcastStarted.js +25 -0
- package/dist/commonjs/hooks/useIsIosScreenshareBroadcastStarted.js.map +1 -0
- package/dist/commonjs/icons/ScreenShare.js +2 -14
- package/dist/commonjs/icons/ScreenShare.js.map +1 -1
- package/dist/commonjs/icons/ScreenShareIndicator.js +38 -0
- package/dist/commonjs/icons/ScreenShareIndicator.js.map +1 -0
- package/dist/commonjs/icons/index.js +11 -0
- package/dist/commonjs/icons/index.js.map +1 -1
- package/dist/commonjs/theme/theme.js +4 -0
- package/dist/commonjs/theme/theme.js.map +1 -1
- package/dist/commonjs/translations/en.json +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/components/Call/CallControls/ScreenShareButton.js +112 -0
- package/dist/module/components/Call/CallControls/ScreenShareButton.js.map +1 -0
- package/dist/module/components/Call/CallControls/index.js +1 -0
- package/dist/module/components/Call/CallControls/index.js.map +1 -1
- package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js +6 -5
- package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer.js +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
- package/dist/module/constants/TestIds.js +1 -0
- package/dist/module/constants/TestIds.js.map +1 -1
- package/dist/module/hooks/index.js +1 -0
- package/dist/module/hooks/index.js.map +1 -1
- package/dist/module/hooks/useIsIosScreenshareBroadcastStarted.js +19 -0
- package/dist/module/hooks/useIsIosScreenshareBroadcastStarted.js.map +1 -0
- package/dist/module/icons/ScreenShare.js +3 -15
- package/dist/module/icons/ScreenShare.js.map +1 -1
- package/dist/module/icons/ScreenShareIndicator.js +28 -0
- package/dist/module/icons/ScreenShareIndicator.js.map +1 -0
- package/dist/module/icons/index.js +1 -0
- package/dist/module/icons/index.js.map +1 -1
- package/dist/module/theme/theme.js +4 -0
- package/dist/module/theme/theme.js.map +1 -1
- package/dist/module/translations/en.json +1 -0
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/components/Call/CallControls/ScreenShareButton.d.ts +22 -0
- package/dist/typescript/components/Call/CallControls/ScreenShareButton.d.ts.map +1 -0
- package/dist/typescript/components/Call/CallControls/index.d.ts +1 -0
- package/dist/typescript/components/Call/CallControls/index.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallParticipantsList/CallParticipantsList.d.ts +2 -2
- package/dist/typescript/components/Call/CallParticipantsList/CallParticipantsList.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/ParticipantLabel.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer.d.ts.map +1 -1
- package/dist/typescript/constants/TestIds.d.ts +1 -0
- package/dist/typescript/constants/TestIds.d.ts.map +1 -1
- package/dist/typescript/hooks/index.d.ts +1 -0
- package/dist/typescript/hooks/index.d.ts.map +1 -1
- package/dist/typescript/hooks/useIsIosScreenshareBroadcastStarted.d.ts +2 -0
- package/dist/typescript/hooks/useIsIosScreenshareBroadcastStarted.d.ts.map +1 -0
- package/dist/typescript/icons/ScreenShare.d.ts.map +1 -1
- package/dist/typescript/icons/ScreenShareIndicator.d.ts +8 -0
- package/dist/typescript/icons/ScreenShareIndicator.d.ts.map +1 -0
- package/dist/typescript/icons/index.d.ts +1 -0
- package/dist/typescript/icons/index.d.ts.map +1 -1
- package/dist/typescript/theme/theme.d.ts +4 -0
- package/dist/typescript/theme/theme.d.ts.map +1 -1
- package/dist/typescript/translations/index.d.ts +1 -0
- package/dist/typescript/translations/index.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/expo-config-plugin/dist/common/types.d.ts +2 -0
- package/expo-config-plugin/dist/index.js +2 -0
- package/expo-config-plugin/dist/withAndroidManifest.js +9 -5
- package/expo-config-plugin/dist/withBuildProperties.d.ts +2 -2
- package/expo-config-plugin/dist/withBuildProperties.js +2 -2
- package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.d.ts +11 -0
- package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.js +225 -0
- package/expo-config-plugin/dist/withIosScreenCapture/index.d.ts +4 -0
- package/expo-config-plugin/dist/withIosScreenCapture/index.js +20 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withFilesMod.d.ts +4 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withFilesMod.js +71 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.d.ts +4 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.js +33 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withTarget.d.ts +4 -0
- package/expo-config-plugin/dist/withIosScreenCapture/withTarget.js +122 -0
- package/expo-config-plugin/static/Atomic.swift +36 -0
- package/expo-config-plugin/static/DarwinNotificationCenter.swift +25 -0
- package/expo-config-plugin/static/SampleHandler.swift +99 -0
- package/expo-config-plugin/static/SampleUploader.swift +143 -0
- package/expo-config-plugin/static/SocketConnection.swift +195 -0
- package/ios/StreamVideoReactNative-Bridging-Header.h +2 -1
- package/ios/StreamVideoReactNative.h +4 -2
- package/ios/StreamVideoReactNative.m +82 -1
- package/ios/StreamVideoReactNative.xcodeproj/project.pbxproj +3 -3
- package/package.json +4 -3
- package/src/components/Call/CallControls/ScreenShareButton.tsx +127 -0
- package/src/components/Call/CallControls/index.tsx +1 -0
- package/src/components/Call/CallParticipantsList/CallParticipantsList.tsx +2 -3
- package/src/components/Participant/ParticipantView/ParticipantLabel.tsx +14 -5
- package/src/components/Participant/ParticipantView/VideoRenderer.tsx +3 -1
- package/src/constants/TestIds.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useIsIosScreenshareBroadcastStarted.ts +33 -0
- package/src/icons/ScreenShare.tsx +3 -18
- package/src/icons/ScreenShareIndicator.tsx +34 -0
- package/src/icons/index.tsx +1 -0
- package/src/theme/theme.ts +8 -0
- package/src/translations/en.json +1 -0
- package/src/version.ts +1 -1
|
@@ -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,9 +1,29 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
2
3
|
#import "StreamVideoReactNative.h"
|
|
3
4
|
#import "WebRTCModule.h"
|
|
4
5
|
#import "WebRTCModuleOptions.h"
|
|
5
6
|
|
|
7
|
+
// Do not change these consts, it is what is used react-native-webrtc
|
|
8
|
+
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
|
|
9
|
+
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
|
|
10
|
+
|
|
11
|
+
void broadcastNotificationCallback(CFNotificationCenterRef center,
|
|
12
|
+
void *observer,
|
|
13
|
+
CFStringRef name,
|
|
14
|
+
const void *object,
|
|
15
|
+
CFDictionaryRef userInfo) {
|
|
16
|
+
StreamVideoReactNative *this = (__bridge StreamVideoReactNative*)observer;
|
|
17
|
+
NSString *eventName = (__bridge NSString*)name;
|
|
18
|
+
[this screenShareEventReceived: eventName];
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
@implementation StreamVideoReactNative
|
|
23
|
+
{
|
|
24
|
+
bool hasListeners;
|
|
25
|
+
CFNotificationCenterRef _notificationCenter;
|
|
26
|
+
}
|
|
7
27
|
RCT_EXPORT_MODULE();
|
|
8
28
|
|
|
9
29
|
+(BOOL)requiresMainQueueSetup {
|
|
@@ -16,4 +36,65 @@ RCT_EXPORT_MODULE();
|
|
|
16
36
|
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
|
|
17
37
|
options.videoEncoderFactory = simulcastVideoEncoderFactory;
|
|
18
38
|
}
|
|
19
|
-
|
|
39
|
+
|
|
40
|
+
-(instancetype)init {
|
|
41
|
+
self = [super init];
|
|
42
|
+
if (self) {
|
|
43
|
+
_notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
|
|
44
|
+
[self setupObserver];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return self;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
-(void)dealloc {
|
|
51
|
+
[self clearObserver];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
-(void)setupObserver {
|
|
56
|
+
CFNotificationCenterAddObserver(_notificationCenter,
|
|
57
|
+
(__bridge const void *)(self),
|
|
58
|
+
broadcastNotificationCallback,
|
|
59
|
+
(__bridge CFStringRef)kBroadcastStartedNotification,
|
|
60
|
+
NULL,
|
|
61
|
+
CFNotificationSuspensionBehaviorDeliverImmediately);
|
|
62
|
+
CFNotificationCenterAddObserver(_notificationCenter,
|
|
63
|
+
(__bridge const void *)(self),
|
|
64
|
+
broadcastNotificationCallback,
|
|
65
|
+
(__bridge CFStringRef)kBroadcastStoppedNotification,
|
|
66
|
+
NULL,
|
|
67
|
+
CFNotificationSuspensionBehaviorDeliverImmediately);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
-(void)clearObserver {
|
|
71
|
+
CFNotificationCenterRemoveObserver(_notificationCenter,
|
|
72
|
+
(__bridge const void *)(self),
|
|
73
|
+
(__bridge CFStringRef)kBroadcastStartedNotification,
|
|
74
|
+
NULL);
|
|
75
|
+
CFNotificationCenterRemoveObserver(_notificationCenter,
|
|
76
|
+
(__bridge const void *)(self),
|
|
77
|
+
(__bridge CFStringRef)kBroadcastStoppedNotification,
|
|
78
|
+
NULL);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
-(void)startObserving {
|
|
82
|
+
hasListeners = YES;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
-(void)stopObserving {
|
|
86
|
+
hasListeners = NO;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
-(void)screenShareEventReceived:(NSString*)event {
|
|
90
|
+
if (hasListeners) {
|
|
91
|
+
[self sendEventWithName:@"StreamVideoReactNative_Ios_Screenshare_Event" body:@{@"name": event}];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
-(NSArray<NSString *> *)supportedEvents {
|
|
96
|
+
return @[@"StreamVideoReactNative_Ios_Screenshare_Event"];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@end
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
134814211AA4EA7D00B7C361 /* Products */ = {
|
|
40
40
|
isa = PBXGroup;
|
|
41
41
|
children = (
|
|
42
|
-
134814201AA4EA6300B7C361 /*
|
|
42
|
+
134814201AA4EA6300B7C361 /* libStreamVideoReactNative.a */,
|
|
43
43
|
);
|
|
44
44
|
name = Products;
|
|
45
45
|
sourceTree = "<group>";
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
);
|
|
72
72
|
name = StreamVideoReactNative;
|
|
73
73
|
productName = RCTDataManager;
|
|
74
|
-
productReference = 134814201AA4EA6300B7C361 /*
|
|
74
|
+
productReference = 134814201AA4EA6300B7C361 /* libStreamVideoReactNative.a */;
|
|
75
75
|
productType = "com.apple.product-type.library.static";
|
|
76
76
|
};
|
|
77
77
|
/* End PBXNativeTarget section */
|
|
@@ -271,4 +271,4 @@
|
|
|
271
271
|
/* End XCConfigurationList section */
|
|
272
272
|
};
|
|
273
273
|
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
|
|
274
|
-
}
|
|
274
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-native-sdk",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"packageManager": "yarn@3.2.4",
|
|
5
5
|
"main": "dist/commonjs/index.js",
|
|
6
6
|
"module": "dist/module/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"package.json",
|
|
31
31
|
"app.plugin.js",
|
|
32
32
|
"expo-config-plugin/README.md",
|
|
33
|
+
"expo-config-plugin/static/**/*",
|
|
33
34
|
"expo-config-plugin/dist/**/*",
|
|
34
35
|
"!expo-config-plugin/src/*",
|
|
35
36
|
"!expo-config-plugin/fixtures/*",
|
|
@@ -45,8 +46,8 @@
|
|
|
45
46
|
"!**/.*"
|
|
46
47
|
],
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@stream-io/video-client": "^0.
|
|
49
|
-
"@stream-io/video-react-bindings": "^0.
|
|
49
|
+
"@stream-io/video-client": "^0.4.0",
|
|
50
|
+
"@stream-io/video-react-bindings": "^0.3.0",
|
|
50
51
|
"intl-pluralrules": "2.0.1",
|
|
51
52
|
"lodash.merge": "^4.6.2",
|
|
52
53
|
"react-native-url-polyfill": "1.3.0",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { NativeModules, Platform, findNodeHandle } from 'react-native';
|
|
3
|
+
import { ScreenCapturePickerView } from '@stream-io/react-native-webrtc';
|
|
4
|
+
import { ScreenShare } from '../../../icons';
|
|
5
|
+
import { CallControlsButton } from './CallControlsButton';
|
|
6
|
+
import { SfuModels } from '@stream-io/video-client';
|
|
7
|
+
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
8
|
+
import { useTheme } from '../../../contexts';
|
|
9
|
+
import { useIsIosScreenshareBroadcastStarted } from '../../../hooks';
|
|
10
|
+
import { usePrevious } from '../../../utils/hooks';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The props for the Screen Share button in the Call Controls.
|
|
14
|
+
*/
|
|
15
|
+
export type ScreenShareButtonProps = {
|
|
16
|
+
/**
|
|
17
|
+
* Handler to be called when the screen-share has been started.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
onScreenShareStartedHandler?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Handler to be called when the screen-share has been stopped.
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
onScreenShareStoppedHandler?: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ios >= 14.0 or android
|
|
29
|
+
const CanScreenShare =
|
|
30
|
+
(Platform.OS === 'ios' &&
|
|
31
|
+
Number.parseInt(Platform.Version.split('.')[0], 10) >= 14) ||
|
|
32
|
+
Platform.OS === 'android';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Button to start/stop screen share.
|
|
36
|
+
* Note: This button is enabled only on iOS >= 14.0 and any Android version.
|
|
37
|
+
*/
|
|
38
|
+
export const ScreenShareButton = ({
|
|
39
|
+
onScreenShareStartedHandler,
|
|
40
|
+
onScreenShareStoppedHandler,
|
|
41
|
+
}: ScreenShareButtonProps) => {
|
|
42
|
+
const {
|
|
43
|
+
theme: { colors, screenShareButton },
|
|
44
|
+
} = useTheme();
|
|
45
|
+
const call = useCall();
|
|
46
|
+
const { useLocalParticipant } = useCallStateHooks();
|
|
47
|
+
|
|
48
|
+
const onScreenShareStartedHandlerRef = useRef(onScreenShareStartedHandler);
|
|
49
|
+
onScreenShareStartedHandlerRef.current = onScreenShareStartedHandler;
|
|
50
|
+
const onScreenShareStoppedHandlerRef = useRef(onScreenShareStoppedHandler);
|
|
51
|
+
onScreenShareStoppedHandlerRef.current = onScreenShareStoppedHandler;
|
|
52
|
+
|
|
53
|
+
const iosScreenShareStarted = useIsIosScreenshareBroadcastStarted();
|
|
54
|
+
const prevIosScreenShareStarted = usePrevious(iosScreenShareStarted);
|
|
55
|
+
|
|
56
|
+
const localParticipant = useLocalParticipant();
|
|
57
|
+
const hasPublishedScreenShare = localParticipant?.publishedTracks.includes(
|
|
58
|
+
SfuModels.TrackType.SCREEN_SHARE,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// listens to iOS screen share broadcast started event
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const run = async () => {
|
|
64
|
+
if (Platform.OS !== 'ios') {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (iosScreenShareStarted && !prevIosScreenShareStarted) {
|
|
68
|
+
onScreenShareStartedHandlerRef.current?.();
|
|
69
|
+
const media = await navigator.mediaDevices.getDisplayMedia({
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
deviceId: 'broadcast',
|
|
72
|
+
video: true,
|
|
73
|
+
audio: true,
|
|
74
|
+
});
|
|
75
|
+
await call?.publishScreenShareStream(media);
|
|
76
|
+
} else if (!iosScreenShareStarted && prevIosScreenShareStarted) {
|
|
77
|
+
onScreenShareStoppedHandlerRef.current?.();
|
|
78
|
+
await call?.stopPublish(SfuModels.TrackType.SCREEN_SHARE);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
run();
|
|
82
|
+
}, [call, iosScreenShareStarted, prevIosScreenShareStarted]);
|
|
83
|
+
|
|
84
|
+
const screenCaptureRef = React.useRef(null);
|
|
85
|
+
|
|
86
|
+
const onPress = async () => {
|
|
87
|
+
if (Platform.OS === 'ios') {
|
|
88
|
+
const reactTag = findNodeHandle(screenCaptureRef.current);
|
|
89
|
+
await NativeModules.ScreenCapturePickerViewManager.show(reactTag);
|
|
90
|
+
// After this the iOS screen share broadcast started/stopped event will be triggered
|
|
91
|
+
// and the useEffect listener will handle the rest
|
|
92
|
+
} else {
|
|
93
|
+
if (!hasPublishedScreenShare) {
|
|
94
|
+
try {
|
|
95
|
+
const media = await navigator.mediaDevices.getDisplayMedia({
|
|
96
|
+
video: true,
|
|
97
|
+
audio: true,
|
|
98
|
+
});
|
|
99
|
+
onScreenShareStartedHandlerRef.current?.();
|
|
100
|
+
await call?.publishScreenShareStream(media);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// ignored.. user didnt allow the screen share in the popup
|
|
103
|
+
}
|
|
104
|
+
} else if (hasPublishedScreenShare) {
|
|
105
|
+
onScreenShareStoppedHandlerRef.current?.();
|
|
106
|
+
await call?.stopPublish(SfuModels.TrackType.SCREEN_SHARE);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<CallControlsButton
|
|
113
|
+
disabled={!CanScreenShare}
|
|
114
|
+
onPress={onPress}
|
|
115
|
+
color={colors.static_white}
|
|
116
|
+
style={{
|
|
117
|
+
container: screenShareButton.container,
|
|
118
|
+
svgContainer: screenShareButton.svgContainer,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<ScreenShare color={colors.static_black} />
|
|
122
|
+
{Platform.OS === 'ios' && (
|
|
123
|
+
<ScreenCapturePickerView ref={screenCaptureRef} />
|
|
124
|
+
)}
|
|
125
|
+
</CallControlsButton>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -8,7 +8,6 @@ import React, {
|
|
|
8
8
|
import { FlatList, StyleProp, StyleSheet, ViewStyle } from 'react-native';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
StreamVideoLocalParticipant,
|
|
12
11
|
StreamVideoParticipant,
|
|
13
12
|
StreamVideoParticipantPatches,
|
|
14
13
|
VisibilityState,
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
24
23
|
import { CallContentProps } from '../CallContent';
|
|
25
24
|
|
|
26
25
|
type FlatListProps = React.ComponentProps<
|
|
27
|
-
typeof FlatList<StreamVideoParticipant
|
|
26
|
+
typeof FlatList<StreamVideoParticipant>
|
|
28
27
|
>;
|
|
29
28
|
|
|
30
29
|
const VIEWABILITY_CONFIG: FlatListProps['viewabilityConfig'] = {
|
|
@@ -48,7 +47,7 @@ export type CallParticipantsListProps = CallParticipantsListComponentProps &
|
|
|
48
47
|
/**
|
|
49
48
|
* The list of participants to display in the list
|
|
50
49
|
*/
|
|
51
|
-
participants:
|
|
50
|
+
participants: StreamVideoParticipant[];
|
|
52
51
|
/**
|
|
53
52
|
* The number of columns to display in the list of participants while in vertical or horizontal scrolling mode. This property is only used when there are more than 2 participants.
|
|
54
53
|
* @default 2
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
MicOff,
|
|
5
|
+
PinVertical,
|
|
6
|
+
ScreenShareIndicator,
|
|
7
|
+
VideoSlash,
|
|
8
|
+
} from '../../../icons';
|
|
4
9
|
import { useCall, useI18n } from '@stream-io/video-react-bindings';
|
|
5
10
|
import { ComponentTestIds } from '../../../constants/TestIds';
|
|
6
11
|
import { ParticipantViewProps } from './ParticipantView';
|
|
@@ -43,6 +48,7 @@ export const ParticipantLabel = ({
|
|
|
43
48
|
const call = useCall();
|
|
44
49
|
const { t } = useI18n();
|
|
45
50
|
const participantName = name ?? userId;
|
|
51
|
+
|
|
46
52
|
const participantLabel = isLocalParticipant ? t('You') : participantName;
|
|
47
53
|
const isPinningEnabled = pin?.isLocalPin;
|
|
48
54
|
const isAudioMuted = !publishedTracks.includes(SfuModels.TrackType.AUDIO);
|
|
@@ -53,6 +59,11 @@ export const ParticipantLabel = ({
|
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
if (trackType === 'screenShareTrack') {
|
|
62
|
+
const screenShareText = isLocalParticipant
|
|
63
|
+
? t('You are sharing your screen')
|
|
64
|
+
: t('{{ userName }} is sharing their screen', {
|
|
65
|
+
userName: participantName,
|
|
66
|
+
});
|
|
56
67
|
return (
|
|
57
68
|
<View
|
|
58
69
|
style={[
|
|
@@ -72,7 +83,7 @@ export const ParticipantLabel = ({
|
|
|
72
83
|
screenShareIconContainer,
|
|
73
84
|
]}
|
|
74
85
|
>
|
|
75
|
-
<
|
|
86
|
+
<ScreenShareIndicator color={colors.static_white} />
|
|
76
87
|
</View>
|
|
77
88
|
<Text
|
|
78
89
|
style={[
|
|
@@ -83,9 +94,7 @@ export const ParticipantLabel = ({
|
|
|
83
94
|
]}
|
|
84
95
|
numberOfLines={1}
|
|
85
96
|
>
|
|
86
|
-
{
|
|
87
|
-
userName: participantLabel,
|
|
88
|
-
})}
|
|
97
|
+
{screenShareText}
|
|
89
98
|
</Text>
|
|
90
99
|
</View>
|
|
91
100
|
);
|
|
@@ -74,7 +74,9 @@ export const VideoRenderer = ({
|
|
|
74
74
|
const videoStreamToRender = (isScreenSharing
|
|
75
75
|
? screenShareStream
|
|
76
76
|
: videoStream) as unknown as MediaStream | undefined;
|
|
77
|
-
|
|
77
|
+
|
|
78
|
+
const mirror =
|
|
79
|
+
isLocalParticipant && !isScreenSharing && direction === 'front';
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
82
|
* This effect updates the participant's viewportVisibilityState
|
package/src/constants/TestIds.ts
CHANGED
package/src/hooks/index.ts
CHANGED
|
@@ -3,5 +3,6 @@ export * from './usePermissionRequest';
|
|
|
3
3
|
export * from './usePermissionNotification';
|
|
4
4
|
export * from './push';
|
|
5
5
|
export * from './useAndroidKeepCallAliveEffect';
|
|
6
|
+
export * from './useIsIosScreenshareBroadcastStarted';
|
|
6
7
|
export * from './useIsInPiPMode';
|
|
7
8
|
export * from './useAutoEnterPiPEffect';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type Event = {
|
|
5
|
+
name: 'iOS_BroadcastStarted' | 'iOS_BroadcastStopped';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function useIsIosScreenshareBroadcastStarted() {
|
|
9
|
+
const [hasStarted, setHasStarted] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (Platform.OS !== 'ios') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const eventEmitter = new NativeEventEmitter(
|
|
17
|
+
NativeModules.StreamVideoReactNative,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const subscription = eventEmitter.addListener(
|
|
21
|
+
'StreamVideoReactNative_Ios_Screenshare_Event',
|
|
22
|
+
(event: Event) => {
|
|
23
|
+
setHasStarted(event.name === 'iOS_BroadcastStarted');
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
subscription.remove();
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return hasStarted;
|
|
33
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import Svg, {
|
|
2
|
+
import Svg, { Path } from 'react-native-svg';
|
|
3
3
|
import { ColorValue } from 'react-native/types';
|
|
4
4
|
import { IconTestIds } from '../constants/TestIds';
|
|
5
5
|
|
|
@@ -9,25 +9,10 @@ type Props = {
|
|
|
9
9
|
|
|
10
10
|
export const ScreenShare = ({ color }: Props) => {
|
|
11
11
|
return (
|
|
12
|
-
<Svg viewBox="0 0
|
|
13
|
-
<Mask id="path-1-inside-1_1396_84816">
|
|
14
|
-
<Path
|
|
15
|
-
fillRule="evenodd"
|
|
16
|
-
clipRule="evenodd"
|
|
17
|
-
d="M 2 6.68176 C 2 5.57719 2.89543 4.68176 4 4.68176 H 20 C 21.1046 4.68176 22 5.57719 22 6.68176 V 16.3181 C 22 17.4227 21.1046 18.3181 20 18.3181 H 4 C 2.89543 18.3181 2 17.4227 2 16.3181 V 6.68176 Z M 10.4209 11.5243 L 8.3635 11.5 L 11.9999 7.25755 L 15.6362 11.5 L 13.5065 11.5247 V 15.7424 L 10.4209 15.7424 V 11.5243 Z"
|
|
18
|
-
/>
|
|
19
|
-
</Mask>
|
|
20
|
-
|
|
21
|
-
<Path
|
|
22
|
-
fill={color}
|
|
23
|
-
fillRule="evenodd"
|
|
24
|
-
clipRule="evenodd"
|
|
25
|
-
d="M 2 6.68176 C 2 5.57719 2.89543 4.68176 4 4.68176 H 20 C 21.1046 4.68176 22 5.57719 22 6.68176 V 16.3181 C 22 17.4227 21.1046 18.3181 20 18.3181 H 4 C 2.89543 18.3181 2 17.4227 2 16.3181 V 6.68176 Z M 10.4209 11.5243 L 8.3635 11.5 L 11.9999 7.25755 L 15.6362 11.5 L 13.5065 11.5247 V 15.7424 L 10.4209 15.7424 V 11.5243 Z"
|
|
26
|
-
/>
|
|
12
|
+
<Svg viewBox="0 0 36 36" testID={IconTestIds.SCREEN_SHARE}>
|
|
27
13
|
<Path
|
|
28
|
-
mask="url(#path-1-inside-1_1396_84816)"
|
|
29
14
|
fill={color}
|
|
30
|
-
d="
|
|
15
|
+
d="m25.407 1.718-14.814-.014a2.959 2.959 0 0 0-2.948 2.963v26.666a2.959 2.959 0 0 0 2.948 2.963h14.814a2.972 2.972 0 0 0 2.963-2.963V4.667a2.96 2.96 0 0 0-2.963-2.949Zm0 26.652H10.593V7.63h14.814v20.74Zm-6.222-8.563V22.4l4.741-4.43-4.74-4.414v2.518c-4.608.637-6.445 3.793-7.112 6.963 1.645-2.222 3.822-3.23 7.111-3.23Z"
|
|
31
16
|
/>
|
|
32
17
|
</Svg>
|
|
33
18
|
);
|