-
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 #495 from opentok/e2ee-example
Added e2ee android example
- Loading branch information
Showing
27 changed files
with
874 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,39 @@ | ||
# E2EE Video Chat | ||
|
||
Upon deploying this sample application, you should be able to have two-way End to End Encrypted (E2EE) audio and video communication using OpenTok. | ||
|
||
Main features: | ||
* Connect to an E2EE OpenTok session | ||
* Publish an audio-video stream to the session | ||
* Subscribe to another client's audio-video stream | ||
|
||
# Configure the app | ||
Open the `OpenTokConfig` file and configure the `API_KEY`, `SESSION_ID`, and `TOKEN` variables. You can obtain these values from your [TokBox account](https://tokbox.com/account/#/). | ||
Set the `SECRET` to your own Encryption Secret. A valid secret is a string between 8 and 256 characters. | ||
|
||
# Enabling E2EE | ||
To create an E2EE connection you must first enable this functionality server side. | ||
You enable end-to-end encryption when you create a session using the REST API. Set the e2ee property to true. See [session creation](https://tokbox.com/developer/guides/create-session/). | ||
|
||
The following Node.js example creates an end-to-end encryption enabled session: | ||
|
||
```javascript | ||
const opentok = new OpenTok(API_KEY, API_SECRET); | ||
const sessionId; | ||
opentok.createSession({ | ||
mediaMode: 'routed', | ||
e2ee: true, | ||
}, function(error, session) { | ||
if (error) { | ||
console.log('Error creating session:', error) | ||
} else { | ||
sessionId = session.sessionId; | ||
console.log('Session ID: ' + sessionId); | ||
} | ||
}); | ||
``` | ||
|
||
## 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.ee2evideo" | ||
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:2.27.0" | ||
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.basicvideochat" > | ||
|
||
<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> |
248 changes: 248 additions & 0 deletions
248
E2EE-Video-Chat-Kotlin/app/src/main/java/com/tokbox/sample/ee2evideo/MainActivity.kt
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,248 @@ | ||
package com.tokbox.sample.ee2evideo | ||
|
||
import android.Manifest | ||
import android.opengl.GLSurfaceView | ||
import android.os.Bundle | ||
import android.util.Log | ||
import android.widget.FrameLayout | ||
import android.widget.Toast | ||
import androidx.appcompat.app.AppCompatActivity | ||
import com.opentok.android.BaseVideoRenderer | ||
import com.opentok.android.OpentokError | ||
import com.opentok.android.Publisher | ||
import com.opentok.android.PublisherKit | ||
import com.opentok.android.PublisherKit.PublisherListener | ||
import com.opentok.android.Session | ||
import com.opentok.android.Session.SessionListener | ||
import com.opentok.android.Stream | ||
import com.opentok.android.Subscriber | ||
import com.opentok.android.SubscriberKit | ||
import com.opentok.android.SubscriberKit.SubscriberListener | ||
import com.tokbox.sample.basicvideochat.R | ||
import com.tokbox.sample.ee2evideo.MainActivity | ||
import com.tokbox.sample.ee2evideo.network.APIService | ||
import com.tokbox.sample.ee2evideo.network.GetSessionResponse | ||
import okhttp3.OkHttpClient | ||
import okhttp3.logging.HttpLoggingInterceptor | ||
import pub.devrel.easypermissions.AfterPermissionGranted | ||
import pub.devrel.easypermissions.EasyPermissions | ||
import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks | ||
import retrofit2.Call | ||
import retrofit2.Callback | ||
import retrofit2.Response | ||
import retrofit2.Retrofit | ||
import retrofit2.converter.moshi.MoshiConverterFactory | ||
|
||
class MainActivity : AppCompatActivity(), PermissionCallbacks { | ||
private var retrofit: Retrofit? = null | ||
private var apiService: APIService? = null | ||
private var session: Session? = null | ||
private var publisher: Publisher? = null | ||
private var subscriber: Subscriber? = null | ||
private lateinit var publisherViewContainer: FrameLayout | ||
private lateinit var subscriberViewContainer: FrameLayout | ||
private val publisherListener: PublisherListener = object : PublisherListener { | ||
override fun onStreamCreated(publisherKit: PublisherKit, stream: Stream) { | ||
Log.d(TAG, "onStreamCreated: Publisher Stream Created. Own stream ${stream.streamId}") | ||
} | ||
|
||
override fun onStreamDestroyed(publisherKit: PublisherKit, stream: Stream) { | ||
Log.d(TAG, "onStreamDestroyed: Publisher Stream Destroyed. Own stream ${stream.streamId}") | ||
} | ||
|
||
override fun onError(publisherKit: PublisherKit, opentokError: OpentokError) { | ||
if (opentokError.errorCode == OpentokError.ErrorCode.EncryptionInternalError) { | ||
// The application should communicate that the secret was not set. | ||
} | ||
finishWithMessage("PublisherKit onError: ${opentokError.message}") | ||
} | ||
} | ||
private val sessionListener: SessionListener = object : SessionListener { | ||
override fun onConnected(session: Session) { | ||
Log.d(TAG, "onConnected: Connected to session: ${session.sessionId}") | ||
publisher = Publisher.Builder(this@MainActivity).build() | ||
publisher?.setPublisherListener(publisherListener) | ||
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) | ||
} | ||
|
||
override fun onDisconnected(session: Session) { | ||
Log.d(TAG, "onDisconnected: Disconnected from session: ${session.sessionId}") | ||
} | ||
|
||
override fun onStreamReceived(session: Session, stream: Stream) { | ||
Log.d(TAG, "onStreamReceived: New Stream Received ${stream.streamId} in session: ${session.sessionId}") | ||
if (subscriber == null) { | ||
subscriber = Subscriber.Builder(this@MainActivity, stream).build().also { | ||
it.renderer?.setStyle( | ||
BaseVideoRenderer.STYLE_VIDEO_SCALE, | ||
BaseVideoRenderer.STYLE_VIDEO_FILL | ||
) | ||
|
||
it.setSubscriberListener(subscriberListener) | ||
} | ||
|
||
session.subscribe(subscriber) | ||
subscriberViewContainer.addView(subscriber?.view) | ||
} | ||
} | ||
|
||
override fun onStreamDropped(session: Session, stream: Stream) { | ||
Log.d(TAG, "onStreamDropped: Stream Dropped: ${stream.streamId} in session: ${session.sessionId}") | ||
if (subscriber != null) { | ||
subscriber = null | ||
subscriberViewContainer.removeAllViews() | ||
} | ||
} | ||
|
||
override fun onError(session: Session, opentokError: OpentokError) { | ||
if (opentokError.errorCode == OpentokError.ErrorCode.EncryptionSecretMissing) { | ||
// Notify the user that they cannot join the session | ||
} | ||
finishWithMessage("Session error: ${opentokError.message}") | ||
} | ||
} | ||
var subscriberListener: SubscriberListener = object : SubscriberListener { | ||
override fun onConnected(subscriberKit: SubscriberKit) { | ||
Log.d(TAG, "onConnected: Subscriber connected. Stream: ${subscriberKit.stream.streamId}") | ||
} | ||
|
||
override fun onDisconnected(subscriberKit: SubscriberKit) { | ||
Log.d(TAG, "onDisconnected: Subscriber disconnected. Stream: ${subscriberKit.stream.streamId}") | ||
} | ||
|
||
override fun onError(subscriberKit: SubscriberKit, opentokError: OpentokError) { | ||
if (opentokError.errorCode == OpentokError.ErrorCode.EncryptionSecretMismatch) { | ||
// Activate a UI element communicating that there's been an encryption secret mismatch. | ||
} | ||
finishWithMessage("SubscriberKit onError: ${opentokError.message}") | ||
} | ||
} | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
publisherViewContainer = findViewById(R.id.publisher_container) | ||
subscriberViewContainer = findViewById(R.id.subscriber_container) | ||
requestPermissions() | ||
} | ||
|
||
override fun onPause() { | ||
super.onPause() | ||
session?.onPause() | ||
} | ||
|
||
override fun onResume() { | ||
super.onResume() | ||
session?.onResume() | ||
} | ||
|
||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { | ||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) | ||
} | ||
|
||
override fun onPermissionsGranted(requestCode: Int, perms: List<String>) { | ||
Log.d(TAG, "onPermissionsGranted:$requestCode: $perms") | ||
} | ||
|
||
override fun onPermissionsDenied(requestCode: Int, perms: List<String>) { | ||
finishWithMessage("onPermissionsDenied: $requestCode: $perms") | ||
} | ||
|
||
@AfterPermissionGranted(PERMISSIONS_REQUEST_CODE) | ||
private fun requestPermissions() { | ||
val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) | ||
if (EasyPermissions.hasPermissions(this, *perms)) { | ||
if (ServerConfig.hasChatServerUrl()) { | ||
// Custom server URL exists - retrieve session config | ||
if (!ServerConfig.isValid) { | ||
finishWithMessage("Invalid chat server url: ${ServerConfig.CHAT_SERVER_URL}") | ||
return | ||
} | ||
initRetrofit() | ||
getSession() | ||
} else { | ||
// Use hardcoded session config | ||
if (!OpenTokConfig.isValid) { | ||
finishWithMessage("Invalid OpenTokConfig. ${OpenTokConfig.description}") | ||
return | ||
} | ||
initializeSession(OpenTokConfig.API_KEY, OpenTokConfig.SESSION_ID, OpenTokConfig.TOKEN, OpenTokConfig.SECRET) | ||
} | ||
} else { | ||
EasyPermissions.requestPermissions( | ||
this, | ||
getString(R.string.rationale_video_app), | ||
PERMISSIONS_REQUEST_CODE, | ||
*perms | ||
) | ||
} | ||
} | ||
|
||
/* Make a request for session data */ | ||
private fun getSession() { | ||
Log.i(TAG, "getSession") | ||
|
||
apiService?.session?.enqueue(object : Callback<GetSessionResponse?> { | ||
override fun onResponse(call: Call<GetSessionResponse?>, response: Response<GetSessionResponse?>) { | ||
response.body()?.also { | ||
initializeSession(it.apiKey, it.sessionId, it.token, it.secret) | ||
} | ||
} | ||
|
||
override fun onFailure(call: Call<GetSessionResponse?>, t: Throwable) { | ||
throw RuntimeException(t.message) | ||
} | ||
}) | ||
} | ||
|
||
private fun initializeSession(apiKey: String, sessionId: String, token: String, secret: String) { | ||
Log.i(TAG, "apiKey: $apiKey") | ||
Log.i(TAG, "sessionId: $sessionId") | ||
Log.i(TAG, "token: $token") | ||
|
||
/* | ||
The context used depends on the specific use case, but usually, it is desired for the session to | ||
live outside of the Activity e.g: live between activities. For a production applications, | ||
it's convenient to use Application context instead of Activity context. | ||
*/ | ||
session = Session.Builder(this, apiKey, sessionId).build().also { | ||
it.setSessionListener(sessionListener) | ||
//Encrypt the connection | ||
it.setEncryptionSecret(secret) | ||
it.connect(token) | ||
} | ||
} | ||
|
||
private fun initRetrofit() { | ||
val logging = HttpLoggingInterceptor() | ||
logging.setLevel(HttpLoggingInterceptor.Level.BODY) | ||
val client: OkHttpClient = OkHttpClient.Builder() | ||
.addInterceptor(logging) | ||
.build() | ||
|
||
retrofit = Retrofit.Builder() | ||
.baseUrl(ServerConfig.CHAT_SERVER_URL) | ||
.addConverterFactory(MoshiConverterFactory.create()) | ||
.client(client) | ||
.build().also { | ||
apiService = it.create(APIService::class.java) | ||
} | ||
} | ||
|
||
private fun finishWithMessage(message: String) { | ||
Log.e(TAG, message) | ||
Toast.makeText(this, message, Toast.LENGTH_LONG).show() | ||
finish() | ||
} | ||
|
||
companion object { | ||
private val TAG = MainActivity::class.java.simpleName | ||
private const val PERMISSIONS_REQUEST_CODE = 124 | ||
} | ||
} |
Oops, something went wrong.