-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #486 from opentok/VIDCS-1549
VIDCS-1549: Add Screen Sharing sample on Kotlin using WebViewer
- Loading branch information
Showing
26 changed files
with
922 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# intellij | ||
*.iml | ||
|
||
.gradle | ||
/local.properties | ||
/.idea/workspace.xml | ||
/.idea/libraries | ||
.DS_Store | ||
/build | ||
/captures | ||
.externalNativeBuild | ||
app/build | ||
|
||
.settings/ | ||
app/jniLibs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Screen Sharing | ||
|
||
This app shows how to use `WebView` 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 | ||
|
||
Custom video capturer is using `WebView` from the Android application as the source of | ||
a published stream. | ||
|
||
When the app starts up, the `onCreate` method instantiates a `WebView` object: | ||
|
||
```java | ||
webViewContainer = findViewById(R.id.webview); | ||
``` | ||
|
||
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: | ||
|
||
```kotlin | ||
override fun onConnected(session: Session) { | ||
Log.d(TAG, "onConnected: Connected to session: ${session.sessionId}") | ||
|
||
val screenSharingCapturer = ScreenSharingCapturer(this@MainActivity, webViewContainer) | ||
|
||
publisher = Publisher.Builder(this@MainActivity).capturer(screenSharingCapturer).build() | ||
publisher?.setPublisherListener(publisherListener) | ||
publisher?.publisherVideoType = PublisherKit.PublisherKitVideoType.PublisherKitVideoTypeScreen | ||
|
||
webViewContainer.setWebViewClient(WebViewClient()) | ||
val webSettings: WebSettings = webViewContainer.getSettings() | ||
webSettings.javaScriptEnabled = true | ||
webViewContainer.setLayerType(View.LAYER_TYPE_SOFTWARE, null) | ||
webViewContainer.loadUrl("https://www.tokbox.com") | ||
|
||
publisher?.renderer?.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) | ||
publisherViewContainer.addView(publisher?.view) | ||
if (publisher?.view is GLSurfaceView) { | ||
(publisher?.view as GLSurfaceView).setZOrderOnTop(true) | ||
} | ||
session.publish(publisher) | ||
} | ||
``` | ||
|
||
> Note: that 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 `onConnected` method also calls the `loadScreenWebView` method. This method | ||
configures the WebView object, loading the TokBox URL. | ||
|
||
Note that the `webViewContainer` object is passed into the `ScreenSharingCapturer` constructor, | ||
which assigns it to the `contentView` property. | ||
|
||
The `getCaptureSettings` method initializes capture settings to be used by the custom | ||
video capturer: | ||
|
||
```kotlin | ||
override fun getCaptureSettings(): CaptureSettings { | ||
val captureSettings = CaptureSettings() | ||
captureSettings.fps = fps | ||
captureSettings.width = width | ||
captureSettings.height = height | ||
captureSettings.format = ARGB | ||
return captureSettings | ||
} | ||
``` | ||
|
||
The `startCapture` method starts the `frameProducer` thread after 1/15 second: | ||
|
||
```kotlin | ||
override fun startCapture(): Int { | ||
capturing = true | ||
handler.postDelayed(newFrame, (1000 / fps).toLong()) | ||
return 0 | ||
} | ||
``` | ||
|
||
The `frameProducer` thread gets a `Bitmap` representation of the `contentView` object | ||
(the `WebView`), writes its pixels to a buffer, and then calls the `provideIntArrayFrame()` | ||
method, passing in that buffer as a parameter: | ||
|
||
```kotlin | ||
private val newFrame: Runnable = object : Runnable { | ||
override fun run() { | ||
if (capturing) { | ||
val width = contentView.width | ||
val height = contentView.height | ||
if (frame == null || this@ScreenSharingCapturer.width != width || this@ScreenSharingCapturer.height != height) { | ||
this@ScreenSharingCapturer.width = width | ||
this@ScreenSharingCapturer.height = height | ||
if (bmp != null) { | ||
bmp!!.recycle() | ||
bmp = null | ||
} | ||
bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) | ||
canvas = Canvas(bmp!!) | ||
frame = IntArray(width * height) | ||
} | ||
canvas!!.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null) | ||
canvas!!.translate(-contentView.scrollX.toFloat(), -contentView.scrollY.toFloat()) | ||
contentView.draw(canvas) | ||
bmp!!.getPixels(frame, 0, width, 0, 0, width, height) | ||
provideIntArrayFrame(frame, ARGB, width, height, 0, false) | ||
canvas!!.restore() | ||
handler.postDelayed(this, (1000 / fps).toLong()) | ||
} | ||
} | ||
} | ||
``` | ||
|
||
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/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/build | ||
config.gradle | ||
*.jar | ||
*.so |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
plugins { | ||
id 'com.android.application' | ||
id 'kotlin-android' | ||
} | ||
|
||
apply { | ||
from '../../commons.gradle' | ||
} | ||
|
||
android { | ||
compileSdkVersion extCompileSdkVersion | ||
|
||
defaultConfig { | ||
applicationId "com.tokbox.sample.screensharing" | ||
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 | ||
} | ||
|
||
kotlinOptions { | ||
jvmTarget = '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}" | ||
|
||
implementation "com.squareup.retrofit2:retrofit:${extRetrofitVersion}" | ||
implementation "com.squareup.okhttp3:okhttp:${extOkHttpVersion}" | ||
implementation "com.squareup.retrofit2:converter-moshi:${extRetrofit2ConverterMoshi}" | ||
implementation "com.squareup.okhttp3:logging-interceptor:${extOkHttpLoggingInterceptor}" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.tokbox.sample.screensharing" > | ||
|
||
<uses-permission android:name="android.permission.CAMERA" /> | ||
<uses-permission android:name="android.permission.INTERNET" /> | ||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> | ||
<uses-permission android:name="android.permission.WAKE_LOCK" /> | ||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> | ||
|
||
<uses-feature android:name="android.hardware.camera" /> | ||
<uses-feature android:name="android.hardware.camera.autofocus" /> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:theme="@style/AppTheme" > | ||
<activity | ||
android:name=".MainActivity" | ||
android:screenOrientation="portrait" | ||
android:label="@string/app_name" | ||
android:exported="true"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
|
||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
|
||
</manifest> |
Oops, something went wrong.