Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build Screen-Sharing-Java
name: Build Device-Screen-Sharing-Java

on:
push:
Expand All @@ -22,4 +22,4 @@ jobs:
java-version: 17

- name: Build
run: cd Screen-Sharing-Java && ./gradlew app:assembleRelease && cd ..
run: cd Device-Screen-Sharing-Java && ./gradlew app:assembleRelease && cd ..
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build Screen-Sharing-Kotlin
name: Build Webview-Screen-Sharing-Java

on:
push:
Expand All @@ -22,4 +22,4 @@ jobs:
java-version: 17

- name: Build
run: cd Screen-Sharing-Kotlin && ./gradlew app:assembleRelease && cd ..
run: cd Webview-Screen-Sharing-Java && ./gradlew app:assembleRelease && cd ..
25 changes: 25 additions & 0 deletions .github/workflows/build-webview-screen-sharing-kotlin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Build Webview-Screen-Sharing-Kotlin

on:
push:
branches: [main] # Just in case main was not up to date while merging PR
pull_request:
types: [opened, synchronize]

jobs:
run:
continue-on-error: true
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v2

- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 17

- name: Build
run: cd Webview-Screen-Sharing-Kotlin && ./gradlew app:assembleRelease && cd ..
File renamed without changes.
119 changes: 119 additions & 0 deletions Device-Screen-Sharing-Java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Screen Sharing

This app demonstrates how to use the Media Projection API as the source for screen-sharing video.

> Check [Basic-Video-Capturer-Camera-2](../Basic-Video-Capturer-Camera-2) project to see how a device camera can be used as the video source for the custom `Capturer`.

## Screen sharing

The custom video capturer uses the Media Projection API to capture the device's screen and publish it as a video stream.

When the app starts up, the `onCreate` method initializes the Media Projection API by requesting permission to capture the screen:

```java
mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
```

Upon connecting to the OpenTok session, the app instantiates a `Publisher` object and calls its `setCapturer` method to use a custom video capturer, defined by the `ScreenSharingCapturer` class:

```java
@Override
public void onConnected(Session session) {
ScreenSharingCapturer screenSharingCapturer = new ScreenSharingCapturer(MainActivity.this, mediaProjection);

publisher = new Publisher.Builder(MainActivity.this)
.capturer(screenSharingCapturer)
.build();

publisher.setPublisherListener(publisherListener);
publisher.setPublisherVideoType(PublisherKit.PublisherKitVideoType.PublisherKitVideoTypeScreen);
publisher.setAudioFallbackEnabled(false);

publisher.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
publisherViewContainer.addView(publisher.getView());

session.publish(publisher);
}
```

> Note: The call to the `setPublisherVideoType` method sets the video type of the published stream to `PublisherKitVideoType.PublisherKitVideoTypeScreen`. This optimizes the video encoding for screen sharing. It is recommended to use a low frame rate (15 frames per second or lower) with this video type. When using the screen video type in a session that uses the [OpenTok Media Server](https://tokbox.com/opentok/tutorials/create-session/#media-mode), the audio-only fallback feature is disabled, so that the video does not drop out in subscribers.

The `ScreenSharingCapturer` class uses the Media Projection API to capture the screen. The `getCaptureSettings` method initializes capture settings to be used by the custom video capturer:

```java
@Override
public CaptureSettings getCaptureSettings() {

CaptureSettings captureSettings = new CaptureSettings();
captureSettings.fps = fps;
captureSettings.width = width;
captureSettings.height = height;
captureSettings.format = ARGB;
return captureSettings;
}
```

The `startCapture` method starts the screen capture process:

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

virtualDisplay = mediaProjection.createVirtualDisplay(
"ScreenSharing",
width,
height,
density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface,
null,
null
);

handler.postDelayed(newFrame, 1000 / fps);
return 0;
}
```

The `backgroundHandler` thread captures frames from the virtual display, processes them, and sends them to the publisher:

```java
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image != null) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();

if (frame == null) {
frame = new int[width * height];
}

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = y * rowStride + x * pixelStride;
int pixel = buffer.getInt(index);
frame[y * width + x] = pixel;
}
}

provideIntArrayFrame(frame, ABGR, width, height, 0, false);
image.close();
}
}
}, backgroundHandler);
```

The `provideIntArrayFrame` method, defined by the `BaseVideoCapturer` class, sends an integer array of data to the publisher, to be used for the next video frame published.

If the publisher is still capturing video, the thread starts again after another 1/15 of a second, so that the capturer continues to supply the publisher with new video frames to publish.

## Further Reading

* Review [other sample projects](../)
* Read more about [OpenTok Android SDK](https://tokbox.com/developer/sdks/android/)
40 changes: 40 additions & 0 deletions Device-Screen-Sharing-Java/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id 'com.android.application'
}

apply {
from '../../commons.gradle'
}

android {
namespace "com.tokbox.sample.devicescreensharing"
compileSdkVersion extCompileSdkVersion

defaultConfig {
applicationId "com.tokbox.sample.devicescreensharing"
minSdkVersion extMinSdkVersion
targetSdkVersion extTargetSdkVersion
versionCode extVersionCode
versionName extVersionName
}

buildTypes {
release {
minifyEnabled extMinifyEnabled
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
// Dependency versions are defined in the ../../commons.gradle file
implementation "com.opentok.android:opentok-android-sdk:${extOpentokSdkVersion}"
implementation "androidx.appcompat:appcompat:${extAppCompatVersion}"
implementation "pub.devrel:easypermissions:${extEasyPermissionsVersion}"
implementation "androidx.constraintlayout:constraintlayout:${extConstraintLyoutVersion}"
}
29 changes: 29 additions & 0 deletions Device-Screen-Sharing-Java/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tokbox.sample.devicescreensharing">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />

<application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".ScreenSharingService"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
</application>

</manifest>
Loading