getCommandsMap() {
.put("disconnect", DISCONNECT)
.put("switchCamera", SWITCH_CAMERA)
.put("toggleVideo", TOGGLE_VIDEO)
+ .put("toggleScreenShare", TOGGLE_SCREEN_SHARE)
.put("toggleSound", TOGGLE_SOUND)
.put("getStats", GET_STATS)
.put("disableOpenSLES", DISABLE_OPENSL_ES)
diff --git a/android/src/main/java/com/twiliorn/library/ScreenCapturerManager.java b/android/src/main/java/com/twiliorn/library/ScreenCapturerManager.java
new file mode 100644
index 00000000..80bd9120
--- /dev/null
+++ b/android/src/main/java/com/twiliorn/library/ScreenCapturerManager.java
@@ -0,0 +1,73 @@
+/**
+ * Service to orchestrate the Twilio Screen Share connection and the various video
+ * views.
+ *
+ * Authors:
+ * Manish Sahu
+ */
+package com.twiliorn.library;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+@TargetApi(29)
+public class ScreenCapturerManager {
+ private ScreenCapturerService mService;
+ private Context mContext;
+ private State currentState = State.UNBIND_SERVICE;
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private ServiceConnection connection =
+ new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // We've bound to ScreenCapturerService, cast the IBinder and get
+ // ScreenCapturerService instance
+ ScreenCapturerService.LocalBinder binder =
+ (ScreenCapturerService.LocalBinder) service;
+ mService = binder.getService();
+ currentState = State.BIND_SERVICE;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {}
+ };
+
+ /** An enum describing the possible states of a ScreenCapturerManager. */
+ public enum State {
+ BIND_SERVICE,
+ START_FOREGROUND,
+ END_FOREGROUND,
+ UNBIND_SERVICE
+ }
+
+ ScreenCapturerManager(Context context) {
+ mContext = context;
+ bindService();
+ }
+
+ private void bindService() {
+ Intent intent = new Intent(mContext, ScreenCapturerService.class);
+ mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ }
+
+ void startForeground() {
+ mService.startForeground();
+ currentState = State.START_FOREGROUND;
+ }
+
+ void endForeground() {
+ mService.endForeground();
+ currentState = State.END_FOREGROUND;
+ }
+
+ void unbindService() {
+ mContext.unbindService(connection);
+ currentState = State.UNBIND_SERVICE;
+ }
+}
diff --git a/android/src/main/java/com/twiliorn/library/ScreenCapturerService.java b/android/src/main/java/com/twiliorn/library/ScreenCapturerService.java
new file mode 100644
index 00000000..26dd4f61
--- /dev/null
+++ b/android/src/main/java/com/twiliorn/library/ScreenCapturerService.java
@@ -0,0 +1,81 @@
+/**
+ * Service to orchestrate the Twilio Screen Share connection and the various video
+ * views.
+ *
+ * Authors:
+ * Manish Sahu
+ */
+package com.twiliorn.library;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import androidx.core.app.NotificationCompat;
+
+@TargetApi(29)
+public class ScreenCapturerService extends Service {
+ private static final String CHANNEL_ID = "screen_capture";
+ private static final String CHANNEL_NAME = "Screen_Capture";
+
+ // Binder given to clients
+ private final IBinder binder = new LocalBinder();
+
+ /**
+ * Class used for the client Binder. We know this service always runs in the same process as its
+ * clients, we don't need to deal with IPC.
+ */
+ public class LocalBinder extends Binder {
+ public ScreenCapturerService getService() {
+ // Return this instance of ScreenCapturerService so clients can call public methods
+ return ScreenCapturerService.this;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_NOT_STICKY;
+ }
+
+ public void startForeground() {
+ NotificationChannel chan =
+ new NotificationChannel(
+ CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE);
+ NotificationManager manager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ assert manager != null;
+ manager.createNotificationChannel(chan);
+
+ final int notificationId = (int) System.currentTimeMillis();
+ NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this, CHANNEL_ID);
+ Notification notification =
+ notificationBuilder
+ .setOngoing(true)
+ // .setSmallIcon(R.drawable.ic_screen_share_white_24dp)
+ .setContentTitle("ScreenCapturerService is running in the foreground")
+ .setPriority(NotificationManager.IMPORTANCE_MIN)
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .build();
+ startForeground(notificationId, notification);
+ }
+
+ public void endForeground() {
+ stopForeground(true);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+}
diff --git a/docs/README.md b/docs/README.md
index cc2631b9..c578d95d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,6 +14,7 @@ Property | Type | Required | Default value | Description
:--- | :--- | :--- | :--- | :---
onCameraSwitched | func | no | | Callback that is called when camera source changes
onVideoChanged | func | no | | Callback that is called when video is toggled.
+onScreenShareChanged | func | no | | Callback that is called when screen share is toggled.
onAudioChanged | func | no | | Callback that is called when a audio is toggled.
onRoomDidConnect | func | no | | Called when the room has connected @param {{roomName, participants, localParticipant}}
onRoomDidFailToConnect | func | no | | Callback that is called when connecting to room fails.
@@ -46,7 +47,7 @@ onDominantSpeakerDidChange | func | no | | Called when dominant speaker changes
Property | Type | Required | Default value | Description
:--- | :--- | :--- | :--- | :---
-screenShare | bool | no | | Flag that enables screen sharing RCTRootView instead of camera capture
+onScreenShareChanged | func | no | | Callback that is called when screen share is toggled.
onRoomDidConnect | func | no | | Called when the room has connected @param {{roomName, participants, localParticipant}}
onRoomDidDisconnect | func | no | | Called when the room has disconnected @param {{roomName, error}}
onRoomDidFailToConnect | func | no | | Called when connection with room failed @param {{roomName, error}}
diff --git a/index.d.ts b/index.d.ts
index c8410162..f09f10bf 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -104,6 +104,7 @@ declare module "react-native-twilio-video-webrtc" {
onCameraDidStart?: () => void;
onCameraDidStopRunning?: (err: any) => void;
onCameraWasInterrupted?: () => void;
+ onScreenShareChanged?: (data: any) => void;
onDominantSpeakerDidChange?: DominantSpeakerChangedCb;
onParticipantAddedAudioTrack?: TrackEventCb;
onParticipantAddedVideoTrack?: TrackEventCb;
@@ -163,6 +164,7 @@ declare module "react-native-twilio-video-webrtc" {
class TwilioVideo extends React.Component {
setLocalVideoEnabled: (enabled: boolean) => Promise;
+ setScreenShareEnabled: (enabled: boolean) => void;
setLocalAudioEnabled: (enabled: boolean) => Promise;
setRemoteAudioEnabled: (enabled: boolean) => Promise;
setBluetoothHeadsetConnected: (enabled: boolean) => Promise;
diff --git a/ios/RCTTWVideoModule.m b/ios/RCTTWVideoModule.m
index 2311f6c5..0e1eabe7 100644
--- a/ios/RCTTWVideoModule.m
+++ b/ios/RCTTWVideoModule.m
@@ -10,6 +10,7 @@
#import "RCTTWSerializable.h"
+static NSString* screenShareChanged = @"screenShareChanged";
static NSString* roomDidConnect = @"roomDidConnect";
static NSString* roomDidDisconnect = @"roomDidDisconnect";
static NSString* roomDidFailToConnect = @"roomDidFailToConnect";
@@ -82,6 +83,7 @@ @implementation RCTTWVideoModule
- (void)dealloc {
[self clearCameraInstance];
+ [self clearScreenInstance];
}
- (dispatch_queue_t)methodQueue {
@@ -90,6 +92,7 @@ - (dispatch_queue_t)methodQueue {
- (NSArray *)supportedEvents {
return @[
+ screenShareChanged,
roomDidConnect,
roomDidDisconnect,
roomDidFailToConnect,
@@ -194,6 +197,7 @@ - (void)startCameraCapture:(NSString *)cameraType {
for (TVIVideoView *renderer in self.localVideoTrack.renderers) {
[self updateLocalViewMirroring:renderer];
}
+ NSLog(@"NSLog -------- Camera enabled -------- ");
[self sendEventCheckingListenerWithName:cameraDidStart body:nil];
}
}];
@@ -249,16 +253,50 @@ - (bool)_setLocalVideoEnabled:(bool)enabled {
}
- (bool)_setLocalVideoEnabled:(bool)enabled cameraType:(NSString *)cameraType {
- if (self.localVideoTrack != nil) {
+ if(enabled && self.screen != nil && self.localVideoTrack != nil) {
+ [self.localVideoTrack setEnabled:!enabled];
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant unpublishVideoTrack:self.localVideoTrack];
+
+ [self.screen stopCaptureWithCompletion:^(NSError * _Nullable error) {
+ if(!error) {
+ NSLog(@"NSLog -------- Screen share disabled -------- ");
+ [self sendEventCheckingListenerWithName:screenShareChanged body:@{ @"screenShareEnabled": [NSNumber numberWithBool:false] }];
+ }
+ }];
+
+ self.localVideoTrack = nil;
+ self.screen = nil;
+ }
+
+ if(enabled && self.camera == nil) {
+ TVICameraSourceOptions *options = [TVICameraSourceOptions optionsWithBlock:^(TVICameraSourceOptionsBuilder * _Nonnull builder) {
+
+ }];
+ self.camera = [[TVICameraSource alloc] initWithOptions:options delegate:self];
+ if (self.camera == nil) {
+ return false;
+ }
+ self.localVideoTrack = [TVILocalVideoTrack trackWithSource:self.camera enabled:NO name:@"camera"];
+ }
+
+ if (self.camera != nil && self.localVideoTrack != nil) {
+ if (enabled) {
[self.localVideoTrack setEnabled:enabled];
- if (self.camera) {
- if (enabled) {
- [self startCameraCapture:cameraType];
- } else {
- [self clearCameraInstance];
- }
- return enabled;
- }
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant publishVideoTrack:self.localVideoTrack];
+
+ [self startCameraCapture:cameraType];
+ } else {
+ [self.localVideoTrack setEnabled:enabled];
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant unpublishVideoTrack:self.localVideoTrack];
+
+ [self.camera stopCapture];
+ self.localVideoTrack = nil;
+ self.camera = nil;
+ }
+ return enabled;
}
return false;
}
@@ -288,26 +326,60 @@ - (bool)_setLocalVideoEnabled:(bool)enabled cameraType:(NSString *)cameraType {
}
}
-RCT_EXPORT_METHOD(toggleScreenSharing: (BOOL) value) {
- if (value) {
- TVIAppScreenSourceOptions *options = [TVIAppScreenSourceOptions optionsWithBlock:^(TVIAppScreenSourceOptionsBuilder * _Nonnull builder) {
+RCT_EXPORT_METHOD(toggleScreenShare:(BOOL)enabled) {
+ if (enabled) {
+ if(self.camera != nil && self.localVideoTrack != nil) {
+ [self.localVideoTrack setEnabled:!enabled];
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant unpublishVideoTrack:self.localVideoTrack];
- }];
- self.screen = [[TVIAppScreenSource alloc] initWithOptions:options delegate:self];
- if (self.screen == nil) {
- return;
- }
- self.localVideoTrack = [TVILocalVideoTrack trackWithSource:self.screen enabled:YES name:@"screen"];
- if(self.localVideoTrack != nil){
- TVILocalParticipant *localParticipant = self.room.localParticipant;
- [localParticipant publishVideoTrack:self.localVideoTrack];
- }
- [self.screen startCapture];
+ [self.camera stopCaptureWithCompletion:^(NSError * _Nullable error) {
+ if(!error) {
+ NSLog(@"NSLog -------- Camera disabled -------- ");
+ [self sendEventCheckingListenerWithName:cameraDidStopRunning body:nil];
+ }
+ }];
+
+ self.localVideoTrack = nil;
+ self.camera = nil;
+ }
+
+ if(self.screen == nil) {
+ TVIAppScreenSourceOptions *options = [TVIAppScreenSourceOptions optionsWithBlock:^(TVIAppScreenSourceOptionsBuilder * _Nonnull builder) {
+
+ }];
+ self.screen = [[TVIAppScreenSource alloc] initWithOptions:options delegate:self];
+ if (self.screen == nil) {
+ return;
+ }
+ self.localVideoTrack = [TVILocalVideoTrack trackWithSource:self.screen enabled:NO name:@"screen"];
+ }
+
+ if(self.screen != nil && self.localVideoTrack != nil){
+ [self.localVideoTrack setEnabled:enabled];
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant publishVideoTrack:self.localVideoTrack];
+
+ [self.screen startCaptureWithCompletion:^(NSError * _Nullable error) {
+ if (!error) {
+ NSLog(@"NSLog -------- Screen share enabled -------- ");
+ [self sendEventCheckingListenerWithName:screenShareChanged body:@{ @"screenShareEnabled": [NSNumber numberWithBool:true] }];
+ }
+ }];
+ }
} else {
- [self unpublishLocalVideo];
- [self.screen stopCapture];
- self.localVideoTrack = nil;
- }
+ if(self.screen != nil && self.localVideoTrack != nil) {
+ [self.localVideoTrack setEnabled:enabled];
+ TVILocalParticipant *localParticipant = self.room.localParticipant;
+ [localParticipant unpublishVideoTrack:self.localVideoTrack];
+
+ [self.screen stopCapture];
+ self.localVideoTrack = nil;
+ self.screen = nil;
+
+ [self sendEventCheckingListenerWithName:screenShareChanged body:@{ @"screenShareEnabled": [NSNumber numberWithBool:false] }];
+ }
+ }
}
@@ -474,6 +546,7 @@ -(NSMutableDictionary*)convertLocalVideoTrackStats:(TVILocalVideoTrackStats *)st
RCT_EXPORT_METHOD(disconnect) {
[self clearCameraInstance];
+ [self clearScreenInstance];
[self.room disconnect];
}
@@ -481,6 +554,15 @@ - (void)clearCameraInstance {
// We are done with camera
if (self.camera) {
[self.camera stopCapture];
+ self.camera = nil;
+ }
+}
+
+- (void)clearScreenInstance {
+ // We are done with camera
+ if (self.screen) {
+ [self.screen stopCapture];
+ self.screen = nil;
}
}
diff --git a/package.json b/package.json
index 7a96b487..74ca629a 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,9 @@
"name": "react-native-twilio-video-webrtc",
"repository": {
"type": "git",
- "url": "https://github.com/blackuy/react-native-twilio-video-webrtc.git"
+ "url": "https://github.com/ttebify/react-native-twilio-video-webrtc.git"
},
- "homepage": "https://github.com/blackuy/react-native-twilio-video-webrtc",
+ "homepage": "https://github.com/ttebify/react-native-twilio-video-webrtc",
"version": "3.2.0",
"description": "Twilio Video WebRTC for React Native.",
"main": "index.js",
diff --git a/src/TwilioVideo.android.js b/src/TwilioVideo.android.js
index 4e40bf49..6369e7a4 100644
--- a/src/TwilioVideo.android.js
+++ b/src/TwilioVideo.android.js
@@ -31,8 +31,13 @@ const propTypes = {
onVideoChanged: PropTypes.func,
/**
- * Callback that is called when a audio is toggled.
- */
+ * Callback that is called when screen share permission received.
+ */
+ onScreenShareChanged: PropTypes.func,
+
+ /**
+ * Callback that is called when a audio is toggled.
+ */
onAudioChanged: PropTypes.func,
/**
@@ -167,7 +172,8 @@ const nativeEvents = {
publishVideo: 13,
publishAudio: 14,
setRemoteAudioPlayback: 15,
-};
+ toggleScreenShare: 15
+}
class CustomTwilioVideoView extends Component {
connect({
@@ -233,9 +239,13 @@ class CustomTwilioVideoView extends Component {
return Promise.resolve(enabled);
}
- setLocalAudioEnabled(enabled) {
- this.runCommand(nativeEvents.toggleSound, [enabled]);
- return Promise.resolve(enabled);
+ setScreenShareEnabled (enabled) {
+ this.runCommand(nativeEvents.toggleScreenShare, [enabled])
+ }
+
+ setLocalAudioEnabled (enabled) {
+ this.runCommand(nativeEvents.toggleSound, [enabled])
+ return Promise.resolve(enabled)
}
setRemoteAudioEnabled(enabled) {
@@ -283,29 +293,30 @@ class CustomTwilioVideoView extends Component {
buildNativeEventWrappers() {
return [
- "onCameraSwitched",
- "onVideoChanged",
- "onAudioChanged",
- "onRoomDidConnect",
- "onRoomDidFailToConnect",
- "onRoomDidDisconnect",
- "onParticipantAddedDataTrack",
- "onParticipantRemovedDataTrack",
- "onDataTrackMessageReceived",
- "onParticipantAddedVideoTrack",
- "onParticipantRemovedVideoTrack",
- "onParticipantAddedAudioTrack",
- "onParticipantRemovedAudioTrack",
- "onRoomParticipantDidConnect",
- "onRoomParticipantDidDisconnect",
- "onParticipantEnabledVideoTrack",
- "onParticipantDisabledVideoTrack",
- "onParticipantEnabledAudioTrack",
- "onParticipantDisabledAudioTrack",
- "onStatsReceived",
- "onNetworkQualityLevelsChanged",
- "onDominantSpeakerDidChange",
- "onLocalParticipantSupportedCodecs",
+ 'onCameraSwitched',
+ 'onVideoChanged',
+ 'onScreenShareChanged',
+ 'onAudioChanged',
+ 'onRoomDidConnect',
+ 'onRoomDidFailToConnect',
+ 'onRoomDidDisconnect',
+ 'onParticipantAddedDataTrack',
+ 'onParticipantRemovedDataTrack',
+ 'onDataTrackMessageReceived',
+ 'onParticipantAddedVideoTrack',
+ 'onParticipantRemovedVideoTrack',
+ 'onParticipantAddedAudioTrack',
+ 'onParticipantRemovedAudioTrack',
+ 'onRoomParticipantDidConnect',
+ 'onRoomParticipantDidDisconnect',
+ 'onParticipantEnabledVideoTrack',
+ 'onParticipantDisabledVideoTrack',
+ 'onParticipantEnabledAudioTrack',
+ 'onParticipantDisabledAudioTrack',
+ 'onStatsReceived',
+ 'onNetworkQualityLevelsChanged',
+ 'onDominantSpeakerDidChange',
+ 'onLocalParticipantSupportedCodecs'
].reduce((wrappedEvents, eventName) => {
if (this.props[eventName]) {
return {
diff --git a/src/TwilioVideo.ios.js b/src/TwilioVideo.ios.js
index 99e5c132..fc354f3b 100644
--- a/src/TwilioVideo.ios.js
+++ b/src/TwilioVideo.ios.js
@@ -14,6 +14,10 @@ const { TWVideoModule } = NativeModules;
export default class TwilioVideo extends Component {
static propTypes = {
+ /**
+ * Callback that is called when screen share permission received.
+ */
+ onScreenShareChanged: PropTypes.func,
/**
* Called when the room has connected
*
@@ -215,6 +219,11 @@ export default class TwilioVideo extends Component {
/**
* Toggle screen sharing
*/
+
+ setScreenShareEnabled (enabled) {
+ TWVideoModule.toggleScreenShare(enabled)
+ }
+
toggleScreenSharing(status) {
TWVideoModule.toggleScreenSharing(status);
}
@@ -330,7 +339,12 @@ export default class TwilioVideo extends Component {
_registerEvents() {
TWVideoModule.changeListenerStatus(true);
this._subscriptions = [
- this._eventEmitter.addListener("roomDidConnect", (data) => {
+ this._eventEmitter.addListener('screenShareChanged', (data) => {
+ if (this.props.onScreenShareChanged) {
+ this.props.onScreenShareChanged(data)
+ }
+ }),
+ this._eventEmitter.addListener('roomDidConnect', (data) => {
if (this.props.onRoomDidConnect) {
this.props.onRoomDidConnect(data);
}