From fbc521ee4962343a7aa0f7cfd96fa9d37c4d7500 Mon Sep 17 00:00:00 2001 From: Goncalo Mendes Date: Fri, 4 Aug 2023 16:35:33 +0100 Subject: [PATCH] Video Transformers Kotlin sample --- Video-Transformers-Kotlin/.gitignore | 15 + Video-Transformers-Kotlin/README.md | 86 +++++ Video-Transformers-Kotlin/app/.gitignore | 4 + Video-Transformers-Kotlin/app/build.gradle | 49 +++ .../app/src/main/AndroidManifest.xml | 32 ++ .../sample/basicvideochat/MainActivity.kt | 325 ++++++++++++++++++ .../sample/basicvideochat/OpenTokConfig.kt | 41 +++ .../sample/basicvideochat/ServerConfig.kt | 35 ++ .../basicvideochat/network/APIService.kt | 10 + .../basicvideochat/network/EmptyCallback.kt | 11 + .../network/GetSessionResponse.kt | 15 + .../app/src/main/res/drawable/vonage_logo.png | Bin 0 -> 38656 bytes .../app/src/main/res/layout/activity_main.xml | 47 +++ .../app/src/main/res/menu/menu_chat.xml | 10 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2614 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3519 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6153 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9013 bytes .../app/src/main/res/values/strings.xml | 10 + .../app/src/main/res/values/styles.xml | 8 + Video-Transformers-Kotlin/build.gradle | 10 + Video-Transformers-Kotlin/gradle.properties | 20 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + Video-Transformers-Kotlin/gradlew | 164 +++++++++ Video-Transformers-Kotlin/gradlew.bat | 90 +++++ Video-Transformers-Kotlin/settings.gradle | 16 + 28 files changed, 1004 insertions(+) create mode 100644 Video-Transformers-Kotlin/.gitignore create mode 100644 Video-Transformers-Kotlin/README.md create mode 100644 Video-Transformers-Kotlin/app/.gitignore create mode 100644 Video-Transformers-Kotlin/app/build.gradle create mode 100644 Video-Transformers-Kotlin/app/src/main/AndroidManifest.xml create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/MainActivity.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/OpenTokConfig.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/ServerConfig.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/APIService.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/EmptyCallback.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/GetSessionResponse.kt create mode 100644 Video-Transformers-Kotlin/app/src/main/res/drawable/vonage_logo.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/layout/activity_main.xml create mode 100644 Video-Transformers-Kotlin/app/src/main/res/menu/menu_chat.xml create mode 100644 Video-Transformers-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 Video-Transformers-Kotlin/app/src/main/res/values/strings.xml create mode 100644 Video-Transformers-Kotlin/app/src/main/res/values/styles.xml create mode 100644 Video-Transformers-Kotlin/build.gradle create mode 100644 Video-Transformers-Kotlin/gradle.properties create mode 100644 Video-Transformers-Kotlin/gradle/wrapper/gradle-wrapper.jar create mode 100644 Video-Transformers-Kotlin/gradle/wrapper/gradle-wrapper.properties create mode 100755 Video-Transformers-Kotlin/gradlew create mode 100644 Video-Transformers-Kotlin/gradlew.bat create mode 100644 Video-Transformers-Kotlin/settings.gradle diff --git a/Video-Transformers-Kotlin/.gitignore b/Video-Transformers-Kotlin/.gitignore new file mode 100644 index 00000000..93e62f94 --- /dev/null +++ b/Video-Transformers-Kotlin/.gitignore @@ -0,0 +1,15 @@ +# intellij +*.iml + +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild +app/build + +.settings/ +app/jniLibs/ diff --git a/Video-Transformers-Kotlin/README.md b/Video-Transformers-Kotlin/README.md new file mode 100644 index 00000000..f3aaad91 --- /dev/null +++ b/Video-Transformers-Kotlin/README.md @@ -0,0 +1,86 @@ +Video Transformers +====================== + +The Video Transformers app is a very simple application created on top of Basic Video Chat meant to get a new developer +started using Media Processor APIs on OpenTok Android SDK. For a full description, see the [Video Transformers tutorial at the +OpenTok developer center](https://tokbox.com/developer/guides/vonage-media-processor/android). + +You can use pre-built transformers in the Vonage Media Processor library or create your own custom video transformer to apply to published video. + +You can use the PublisherKit.setAudioTransformers() and +PublisherKit.setVideoTransformers() +methods to apply audio and video transformers to a stream. + +For video, you can apply the background blur video transformer included in the Vonage Media Library. + +You can also create your own custom audio and video transformers. + +## Applying a video transformer from the Vonage Media Library + +Use the PublisherKit.VideoTransformer(String name, String properties) +constructor to create a video transformer that uses a named transformer from the Vonage Media Library. + +Currently, only one transformer is supported: background blur. Set the `name` parameter to `"BackgroundBlur"`. +Set the `properties` parameter to a JSON string defining properties for the transformer. +For the background blur transformer, this JSON includes one property -- `radius` -- which can be set +to `"High"`, `"Low"`, or `"None"`. + +```java +var videoTransformers: ArrayList = ArrayList() +val backgroundBlur = publisher!!.VideoTransformer("BackgroundBlur", "{\"radius\":\"High\"}") +videoTransformers.add(backgroundBlur) +publisher!!.setVideoTransformers(videoTransformers) +``` + +## Creating a custom video transformer + +Create a class that implements the PublisherKit.CustomVideoTransformer +interface. Implement the `PublisherKit.CustomVideoTransformer.onTransform​(BaseVideoRenderer.Frame frame)` method. The `PublisherKit.CustomVideoTransformer.onTransform​(BaseVideoRenderer.Frame frame)` method is triggered for each video frame. +In the implementation of the method, apply a transformation to the `frame` object passed into the method: + +```java +public class MyCustomTransformer implements PublisherKit.CustomVideoTransformer { + @Override + public void onTransform(BaseVideoRenderer.Frame frame) { + // Replace this with code to transform the frame: + frame.convertInPlace(frame.getYplane(), frame.getVplane(), frame.getUplane(), frame.getYstride(), frame.getUvStride()); + } +} +``` + +Then pass the object that implements the PublisherKit.CustomVideoTransformer interface into the `PublisherKit.setVideoTransformers()` method: + +```java +var videoTransformers: ArrayList = ArrayList() +val myCustomTransformer = publisher!!.VideoTransformer("myTransformer", logoTransformer) +videoTransformers.add(myCustomTransformer) +publisher!!.setVideoTransformers(videoTransformers) +``` + +You can combine the Vonage Media library transformer (see the previous section) with custom transformers or apply +multiple custom transformers by adding multiple PublisherKit.VideoTransformer objects to the ArrayList passed +into the `PublisherKit.setVideoTransformers()` method. + +Then pass the object that implements the PublisherKit.CustomAudioRransformer interface into the `PublisherKit.setAudioTransformers()` method: + +```java +var videoTransformers: ArrayList = ArrayList() +val backgroundBlur = publisher!!.VideoTransformer("BackgroundBlur", "{\"radius\":\"High\"}") +val myCustomTransformer = publisher!!.VideoTransformer("myTransformer", logoTransformer) +videoTransformers.add(backgroundBlur) +videoTransformers.add(myCustomTransformer) +publisher!!.setVideoTransformers(videoTransformers) +``` + +You can apply multiple custom transformers by adding multiple PublisherKit.VideoTransformer objects to the ArrayList +passed into the `PublisherKit.setVideoTransformers()` method. + +## Clearing video transformers for a publisher + +To clear video transformers for a publisher, pass an empty ArrayList into +into the `PublisherKit.setVideoTransformers()` method. + +```java +videoTransformers.clear(); +publisher!!.setVideoTransformers(videoTransformers) +``` diff --git a/Video-Transformers-Kotlin/app/.gitignore b/Video-Transformers-Kotlin/app/.gitignore new file mode 100644 index 00000000..e24e8082 --- /dev/null +++ b/Video-Transformers-Kotlin/app/.gitignore @@ -0,0 +1,4 @@ +/build +config.gradle +*.jar +*.so \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/build.gradle b/Video-Transformers-Kotlin/app/build.gradle new file mode 100644 index 00000000..145a429c --- /dev/null +++ b/Video-Transformers-Kotlin/app/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +apply { + from '../../commons.gradle' +} + +android { + compileSdkVersion extCompileSdkVersion + + defaultConfig { + applicationId "com.tokbox.sample.basicvideochat" + 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}" +} diff --git a/Video-Transformers-Kotlin/app/src/main/AndroidManifest.xml b/Video-Transformers-Kotlin/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ffe654ca --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/MainActivity.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/MainActivity.kt new file mode 100644 index 00000000..1375e3c8 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/MainActivity.kt @@ -0,0 +1,325 @@ +package com.tokbox.sample.basicvideochat + +import android.Manifest +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.opengl.GLSurfaceView +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Button +import android.widget.FrameLayout +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.opentok.android.* +import com.opentok.android.PublisherKit.* +import com.opentok.android.Session.SessionListener +import com.opentok.android.SubscriberKit.SubscriberListener +import com.tokbox.sample.basicvideochat.MainActivity +import com.tokbox.sample.basicvideochat.network.APIService +import com.tokbox.sample.basicvideochat.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 +import java.nio.ByteBuffer + + +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 lateinit var logoTransformer: logoWatermark + + //Button to toggle Video Transformers + private var buttonVideoTransformers: Button? = null + + // Array of Video Transformers + var videoTransformers: ArrayList = ArrayList() + + 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) { + 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) { + 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) { + 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() + + buttonVideoTransformers = findViewById(R.id.setvideotransformers) + + // Initialize the logoTransformer after the MainActivity is fully initialized. + logoTransformer = logoWatermark(resources) + } + + override fun onPause() { + super.onPause() + session?.onPause() + } + + override fun onResume() { + super.onResume() + session?.onResume() + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } + + override fun onPermissionsGranted(requestCode: Int, perms: List) { + Log.d(TAG, "onPermissionsGranted:$requestCode: $perms") + } + + override fun onPermissionsDenied(requestCode: Int, perms: List) { + 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) + } + } 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 { + override fun onResponse(call: Call, response: Response) { + response.body()?.also { + initializeSession(it.apiKey, it.sessionId, it.token) + } + } + + override fun onFailure(call: Call, t: Throwable) { + throw RuntimeException(t.message) + } + }) + } + + private fun initializeSession(apiKey: String, sessionId: String, token: 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) + 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 + } + + class logoWatermark(private val resources: Resources) : CustomVideoTransformer { + + // Get the image in bitmap format + private var image: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.vonage_logo) + + fun resizeImage(image: Bitmap?, width: Int, height: Int): Bitmap { + return Bitmap.createScaledBitmap(image!!, width, height, true) + } + + override fun onTransform(frame: BaseVideoRenderer.Frame) { + + // Obtain the Y plane of the video frame + val yPlane: ByteBuffer = frame.yplane + + // Get the dimensions of the video frame + val videoWidth = frame.width + val videoHeight = frame.height + + // Calculate the desired size of the image + val desiredWidth = videoWidth / 8 // Adjust this value as needed + val desiredHeight: Int = (image.getHeight() * (desiredWidth.toFloat() / image.getWidth())).toInt() + + // Resize the image to the desired size + image = resizeImage(image, desiredWidth, desiredHeight) + val logoWidth: Int = image.getWidth() + val logoHeight: Int = image.getHeight() + + // Location of the image (center of video) + val logoPositionX = videoWidth * 1 / 2 - logoWidth // Adjust this as needed for the desired position + val logoPositionY = videoHeight * 1 / 2 - logoHeight // Adjust this as needed for the desired position + + // Overlay the logo on the video frame + for (y in 0 until logoHeight) { + for (x in 0 until logoWidth) { + val frameOffset = (logoPositionY + y) * videoWidth + (logoPositionX + x) + + // Get the logo pixel color + val logoPixel: Int = image.getPixel(x, y) + + // Extract the color channels (ARGB) + val logoAlpha = logoPixel shr 24 and 0xFF + val logoRed = logoPixel shr 16 and 0xFF + + // Overlay the logo pixel on the video frame + val framePixel: Int = yPlane.get(frameOffset).toInt() and 0xFF + + // Calculate the blended pixel value + val blendedPixel = (logoAlpha * logoRed + (255 - logoAlpha) * framePixel) / 255 and 0xFF + + // Set the blended pixel value in the video frame + yPlane.put(frameOffset, blendedPixel.toByte()) + } + } + } + } + + private var isSet = false + fun setVideoTransformers(view: View?) { + if (!isSet) { + videoTransformers.clear() + val backgroundBlur = publisher!!.VideoTransformer("BackgroundBlur", "{\"radius\":\"High\"}") + val myCustomTransformer = publisher!!.VideoTransformer("myTransformer", logoTransformer) + videoTransformers.add(backgroundBlur) + videoTransformers.add(myCustomTransformer) + publisher!!.setVideoTransformers(videoTransformers) + isSet = true + buttonVideoTransformers?.text = "Reset" + } else { + videoTransformers.clear() + publisher!!.setVideoTransformers(videoTransformers) + isSet = false + buttonVideoTransformers?.text = "Set" + } + } +} diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/OpenTokConfig.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/OpenTokConfig.kt new file mode 100644 index 00000000..2845f868 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/OpenTokConfig.kt @@ -0,0 +1,41 @@ +package com.tokbox.sample.basicvideochat + +import android.text.TextUtils + +object OpenTokConfig { + /* + Fill the following variables using your own Project info from the OpenTok dashboard + https://dashboard.tokbox.com/projects + + Note that this application will ignore credentials in the `OpenTokConfig` file when `CHAT_SERVER_URL` contains a + valid URL. + */ + /* + Fill the following variables using your own Project info from the OpenTok dashboard + https://dashboard.tokbox.com/projects + + Note that this application will ignore credentials in the `OpenTokConfig` file when `CHAT_SERVER_URL` contains a + valid URL. + */ + + // Replace with a API key + const val API_KEY = "" + + // Replace with a generated Session ID + const val SESSION_ID = "" + + // Replace with a generated token (from the dashboard or using an OpenTok server SDK) + const val TOKEN = "" + + // *** The code below is to validate this configuration file. You do not need to modify it *** + val isValid: Boolean + get() = !(TextUtils.isEmpty(API_KEY) || TextUtils.isEmpty(SESSION_ID) || TextUtils.isEmpty(TOKEN)) + + val description: String + get() = """ + OpenTokConfig: + API_KEY: $API_KEY + SESSION_ID: $SESSION_ID + TOKEN: $TOKEN + """.trimIndent() +} \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/ServerConfig.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/ServerConfig.kt new file mode 100644 index 00000000..f6108969 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/ServerConfig.kt @@ -0,0 +1,35 @@ +package com.tokbox.sample.basicvideochat + +import android.text.TextUtils +import android.webkit.URLUtil + +object ServerConfig { + /* + You can set up a server to provide session information. To quickly set up a pre-made web service, see + https://github.com/opentok/learning-opentok-php + or + https://github.com/opentok/learning-opentok-node + + After deploying the server open the `ServerConfig` file in this project and configure the `CHAT_SERVER_URL` + with your domain to fetch credentials from the server. + + Note that this application will ignore credentials in the `OpenTokConfig` file when `CHAT_SERVER_URL` contains a + valid URL. + */ + const val CHAT_SERVER_URL: String = "" + + // *** The code below is to validate this configuration file. You do not need to modify it *** + fun hasChatServerUrl() = !TextUtils.isEmpty(CHAT_SERVER_URL) + + val isValid: Boolean + get() { + if (!URLUtil.isHttpsUrl(CHAT_SERVER_URL) && !URLUtil.isHttpUrl(CHAT_SERVER_URL)) { + return false + } else if (!URLUtil.isValidUrl(CHAT_SERVER_URL)) { + return false + } + return true + } + val description: String + get() = "ServerConfig. CHAT_SERVER_URL: $CHAT_SERVER_URL" +} \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/APIService.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/APIService.kt new file mode 100644 index 00000000..6f533463 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/APIService.kt @@ -0,0 +1,10 @@ +package com.tokbox.sample.basicvideochat.network + +import retrofit2.Call +import retrofit2.http.GET + +interface APIService { + + @get:GET("session") + val session: Call? +} \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/EmptyCallback.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/EmptyCallback.kt new file mode 100644 index 00000000..47f22d17 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/EmptyCallback.kt @@ -0,0 +1,11 @@ +package com.tokbox.sample.basicvideochat.network + +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class EmptyCallback : Callback { + + override fun onResponse(call: Call, response: Response) {} + override fun onFailure(call: Call, t: Throwable) {} +} \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/GetSessionResponse.kt b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/GetSessionResponse.kt new file mode 100644 index 00000000..81b2983c --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/java/com/tokbox/sample/basicvideochat/network/GetSessionResponse.kt @@ -0,0 +1,15 @@ +package com.tokbox.sample.basicvideochat.network + +import com.squareup.moshi.Json + +class GetSessionResponse { + + @Json(name = "apiKey") + var apiKey: String = "" + + @Json(name = "sessionId") + var sessionId: String = "" + + @Json(name = "token") + var token: String = "" +} \ No newline at end of file diff --git a/Video-Transformers-Kotlin/app/src/main/res/drawable/vonage_logo.png b/Video-Transformers-Kotlin/app/src/main/res/drawable/vonage_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2087713a8047a289115488e37061d0d6e2b85896 GIT binary patch literal 38656 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 KC!sB#U;H18QX`%K literal 0 HcmV?d00001 diff --git a/Video-Transformers-Kotlin/app/src/main/res/layout/activity_main.xml b/Video-Transformers-Kotlin/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..e47b06e5 --- /dev/null +++ b/Video-Transformers-Kotlin/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + +