Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ec35604
Adding support to use media projection for screen capture
Nov 5, 2020
8f8ee8f
Updating repo info, readme
Nov 5, 2020
373d7d1
Undo package.json
Nov 6, 2020
93288ef
Minor cleanup
Nov 6, 2020
70086f8
Reduce ImageReader buffer size
Nov 6, 2020
7e445d9
fix: incorrect audio bitrate sanitization
deermichel Feb 17, 2021
004fd41
Merge branch '0.15.1' of github.com:opentok/opentok-react-native
Mar 23, 2021
b6326fb
fix: audio crash on teardown
Apr 27, 2021
59496d6
kick off co
abdulajet May 26, 2021
a51ef21
Update OTCustomAudioDriver.swift
abdulajet May 26, 2021
b0e2c3f
ignore: CI
abdulajet May 26, 2021
3175783
ignore: CI
abdulajet May 26, 2021
96459ff
Merge pull request #473 from deermichel/deermichel/audiobitrate
May 26, 2021
cdfc954
Merge pull request #495 from marsch/fix/audio-teardown-crash
May 26, 2021
78e870c
update package and changelog
May 27, 2021
a865f1a
Merge tag '0.17.2' of github.com:opentok/opentok-react-native
Jun 3, 2021
84b88eb
Merge branch 'main' of github.com:opentok/opentok-react-native
Jul 12, 2021
adf31b8
Merge commit 'f7e74476fd08b8084c80621eb551325e264a91b7'
Oct 15, 2021
b5057f9
Bump path-parse from 1.0.6 to 1.0.7
dependabot[bot] Jan 31, 2022
4a5df1e
Bump shelljs from 0.8.4 to 0.8.5
dependabot[bot] Jan 31, 2022
418f2cf
Bump react-native from 0.64.0 to 0.64.1
dependabot[bot] Jan 31, 2022
9557160
Bump node-fetch from 2.6.1 to 2.6.7
dependabot[bot] Jan 31, 2022
4ec34d2
Bump tmpl from 1.0.4 to 1.0.5
dependabot[bot] Jan 31, 2022
fab7b4e
Merge branch 'main' of github.com:opentok/opentok-react-native
Jan 31, 2022
1073508
Bump ajv from 6.10.2 to 6.12.6
dependabot[bot] Feb 11, 2022
8754849
Bump follow-redirects from 1.14.0 to 1.14.8
dependabot[bot] Feb 11, 2022
df73e09
Bump plist from 3.0.2 to 3.0.4
dependabot[bot] Mar 1, 2022
b847816
Bump minimist from 1.2.5 to 1.2.6
dependabot[bot] Apr 9, 2022
77b16c1
Bump async from 2.6.3 to 2.6.4
dependabot[bot] Apr 28, 2022
d61739a
Merge pull request #589 from opentok/dependabot/npm_and_yarn/async-2.6.4
May 16, 2022
0b41235
Merge pull request #586 from opentok/dependabot/npm_and_yarn/minimist…
May 16, 2022
a0a95cf
Merge pull request #581 from opentok/dependabot/npm_and_yarn/plist-3.0.4
May 16, 2022
e44aafc
Merge pull request #579 from opentok/dependabot/npm_and_yarn/follow-r…
May 16, 2022
ede4fce
Merge pull request #578 from opentok/dependabot/npm_and_yarn/ajv-6.12.6
May 16, 2022
e89ddba
Merge pull request #572 from opentok/dependabot/npm_and_yarn/tmpl-1.0.5
May 16, 2022
36454ce
Merge pull request #571 from opentok/dependabot/npm_and_yarn/node-fet…
May 16, 2022
a2179b5
Merge pull request #570 from opentok/dependabot/npm_and_yarn/shelljs-…
May 16, 2022
cf478a6
Merge pull request #569 from opentok/dependabot/npm_and_yarn/path-par…
May 16, 2022
312af91
Merge pull request #529 from opentok/dependabot/npm_and_yarn/react-na…
May 16, 2022
3bdfd78
0.20.2 (#587)
May 16, 2022
8eebf49
Merge branch 'main' of https://github.com/opentok/opentok-react-native
enricop89 May 16, 2022
1a3ba6f
Bump plist from 3.0.2 to 3.0.5
dependabot[bot] May 16, 2022
157cf4b
Bump ansi-regex from 4.1.0 to 4.1.1
dependabot[bot] May 16, 2022
5e0dc01
Merge branch 'dependabot/npm_and_yarn/plist-3.0.5' of https://github.…
enricop89 May 18, 2022
a6a8e76
Merge pull request #594 from opentok/dependabot/npm_and_yarn/ansi-reg…
May 18, 2022
6e030c7
- resolve package json lock conflicts
enricop89 May 18, 2022
cca409a
Merge branch 'updates-from-dependantbot' of https://github.com/opento…
enricop89 May 18, 2022
54e4366
Merge pull request #598 from opentok/updates-from-dependantbot
May 18, 2022
5d24498
0.20.3
enricop89 May 18, 2022
0c40c95
- update changelog
enricop89 May 18, 2022
cf5fe03
Merge branch '0.20.3' of github.com:opentok/opentok-react-native
May 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.20.3 (May 18, 2022)

- [Fix]: Updates from DependatBot

# 0.20.2 (May 16, 2022)

- [Update]: Readme file update with Bintray instructions
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,21 @@ If you try to archive the app and it fails, please do the following:
}
```

5. Sync Gradle
5. Add the following to the `AndroidManifest.xml` file (needed for Screen Sharing to work):

6. Make sure the following in your app's gradle `compileSdkVersion`, `buildToolsVersion`, `minSdkVersion`, and `targetSdkVersion` are greater than or equal to versions specified in the OpenTok React Native library.
```xml
<service
android:name="com.opentokreactnative.OTForegroundService"
android:foregroundServiceType="mediaProjection"
android:enabled="true"
android:exported="false" />
```

6. Sync Gradle

7. Make sure the following in your app's gradle `compileSdkVersion`, `buildToolsVersion`, `minSdkVersion`, and `targetSdkVersion` are greater than or equal to versions specified in the OpenTok React Native library.

7. As for the older Android devices, ensure you add camera and audio permissions to your `AndroidManifest.xml` file:
8. As for the older Android devices, ensure you add camera and audio permissions to your `AndroidManifest.xml` file:

```xml
<uses-permission android:name="android.permission.CAMERA" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.opentokreactnative;

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.Build;
import android.os.IBinder;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;

public class OTForegroundService extends Service {
private static final int ID_SERVICE = 10101;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();

NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? createNotificationChannel(notificationManager) : "";

NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
Notification notification = notificationBuilder.setOngoing(true)
.setPriority(Notification.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build();

startForeground(ID_SERVICE, notification);
}

@RequiresApi(Build.VERSION_CODES.O)
private String createNotificationChannel(NotificationManager notificationManager) {
String channelId = "ot_service_channelid";
String channelName = "OT Foreground Service";
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager.createNotificationChannel(channel);
return channelId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.opentokreactnative;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.util.DisplayMetrics;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.*;

public class OTNativeScreenRecorder extends ReactContextBaseJavaModule {
private static final String TAG = "OTNativeScreenRecorder";

private MediaProjectionManager projectManager;
private MediaProjection mediaProjection;
private VirtualDisplay virtualDisplay;

private final int REQUEST_CODE = 1010;
public int screenDensity;
public int screenWidth;
public int screenHeight;

private ImageReader imageReader;
ImageReader.OnImageAvailableListener listener;

class MediaProjectionCallback extends MediaProjection.Callback {
@Override
public void onStop() {
super.onStop();
}
}

private ActivityEventListener activityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
try {
mediaProjection = projectManager.getMediaProjection(resultCode, data);
mediaProjection.registerCallback(new MediaProjectionCallback(), null);
virtualDisplay = createVirtualDisplay();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};

public OTNativeScreenRecorder(ReactApplicationContext reactContext, ImageReader.OnImageAvailableListener listener) {
super(reactContext);
reactContext.addActivityEventListener(activityEventListener);
this.listener = listener;

DisplayMetrics metrics = new DisplayMetrics();
reactContext.getCurrentActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
screenDensity = metrics.densityDpi;
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
}

private VirtualDisplay createVirtualDisplay() {
if (mediaProjection == null) {
return null;
}
return mediaProjection.createVirtualDisplay(
TAG,
screenWidth,
screenHeight,
screenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.getSurface(),
null,
null
);
}

public void startRecording() {
try {
Intent serviceIntent = new Intent(getCurrentActivity(), OTForegroundService.class);
getReactApplicationContext().startService(serviceIntent);

if (projectManager == null) {
projectManager = (MediaProjectionManager) this.getReactApplicationContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
}

imageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, 1);
imageReader.setOnImageAvailableListener(listener, null);

Intent captureIntent = projectManager.createScreenCaptureIntent();
this.getCurrentActivity().startActivityForResult(captureIntent, REQUEST_CODE);
} catch (Exception e) {
e.printStackTrace();
}
}

public void stopRecording() {
try {
if (imageReader != null) {
imageReader.setOnImageAvailableListener(null, null);
mediaProjection.stop();
virtualDisplay = null;
imageReader = null;
mediaProjection = null;
}

Intent intent = new Intent(getCurrentActivity(), OTForegroundService.class);
getReactApplicationContext().stopService(intent);
} catch (Exception e) {
e.printStackTrace();
}
}

@NonNull
@Override
public String getName() {
return TAG;
}
}
119 changes: 54 additions & 65 deletions android/src/main/java/com/opentokreactnative/OTScreenCapturer.java
Original file line number Diff line number Diff line change
@@ -1,89 +1,40 @@
package com.opentokreactnative;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Handler;
import android.media.Image;
import android.media.ImageReader;
import android.view.View;

import com.facebook.react.bridge.ReactApplicationContext;
import com.opentok.android.BaseVideoCapturer;

public class OTScreenCapturer extends BaseVideoCapturer {
import java.nio.ByteBuffer;

public class OTScreenCapturer extends BaseVideoCapturer implements ImageReader.OnImageAvailableListener {
private boolean capturing = false;
private View contentView;
private ReactApplicationContext reactContext;

private int fps = 15;
private int width = 20;
private int height = 20;
private int[] frame;
private OTNativeScreenRecorder nativeScreenRecorder;

private Bitmap bmp;
private Canvas canvas;

private Handler mHandler = new Handler();

private Runnable newFrame = new Runnable() {
@Override
public void run() {
if (capturing) {
int width = contentView.getWidth();
int height = contentView.getHeight();

if (frame == null ||
OTScreenCapturer.this.width != width ||
OTScreenCapturer.this.height != height) {

OTScreenCapturer.this.width = width;
OTScreenCapturer.this.height = height;

if (bmp != null) {
bmp.recycle();
bmp = null;
}
bmp = Bitmap.createBitmap(width,
height, Bitmap.Config.ARGB_8888);

canvas = new Canvas(bmp);
frame = new int[width * height];
}
canvas.save();
canvas.translate(-contentView.getScrollX(), - contentView.getScrollY());
contentView.draw(canvas);

bmp.getPixels(frame, 0, width, 0, 0, width, height);

provideIntArrayFrame(frame, ARGB, width, height, 0, false);

canvas.restore();

mHandler.postDelayed(newFrame, 1000 / fps);

}
}
};

public OTScreenCapturer(View view) {
this.contentView = view;
public OTScreenCapturer(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
this.nativeScreenRecorder = new OTNativeScreenRecorder(reactContext, this);
}

@Override
public void init() {

}
public void init() {}

@Override
public int startCapture() {
capturing = true;

mHandler.postDelayed(newFrame, 1000 / fps);
nativeScreenRecorder.startRecording();
return 0;
}

@Override
public int stopCapture() {
capturing = false;
mHandler.removeCallbacks(newFrame);
nativeScreenRecorder.stopRecording();
return 0;
}

Expand All @@ -96,9 +47,9 @@ public boolean isCaptureStarted() {
public CaptureSettings getCaptureSettings() {

CaptureSettings settings = new CaptureSettings();
settings.fps = fps;
settings.width = width;
settings.height = height;
settings.fps = 15;
settings.width = nativeScreenRecorder.screenWidth;
settings.height = nativeScreenRecorder.screenHeight;
settings.format = ARGB;
return settings;
}
Expand All @@ -118,4 +69,42 @@ public void onResume() {

}

@Override
public void onImageAvailable(final ImageReader reader) {
Image image = null;
Bitmap bitmap = null;
try {
image = reader.acquireNextImage();

if (image != null) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
buffer.rewind();

int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * reader.getWidth();
int bitmapWidth = reader.getWidth() + rowPadding / pixelStride;
bitmap = Bitmap.createBitmap(bitmapWidth, reader.getHeight(), Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);

int width = nativeScreenRecorder.screenWidth;
int height = nativeScreenRecorder.screenHeight;
int[] frame = new int[width * height];
bitmap.getPixels(frame, 0, width, 0, 0, width, height);

provideIntArrayFrame(frame, ARGB, width, height, 0, false);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bitmap != null) {
bitmap.recycle();
}

if (image != null) {
image.close();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@

import android.util.Log;
import android.widget.FrameLayout;
import android.view.View;

import androidx.annotation.Nullable;
import android.view.View;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
Expand Down Expand Up @@ -156,8 +155,7 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c
String videoSource = properties.getString("videoSource");
Publisher mPublisher = null;
if (videoSource.equals("screen")) {
View view = getCurrentActivity().getWindow().getDecorView().getRootView();
OTScreenCapturer capturer = new OTScreenCapturer(view);
OTScreenCapturer capturer = new OTScreenCapturer(this.getReactApplicationContext());
mPublisher = new Publisher.Builder(this.getReactApplicationContext())
.audioTrack(audioTrack)
.videoTrack(videoTrack)
Expand Down
Loading