Skip to content

Commit

Permalink
Add JVM Support
Browse files Browse the repository at this point in the history
  • Loading branch information
aschulz90 committed Feb 2, 2024
1 parent f8b4c44 commit a6b6df2
Show file tree
Hide file tree
Showing 62 changed files with 2,779 additions and 46 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
WebRTC Kotlin Multiplatform SDK

## API implementation map
API | Android | iOS | JS
:-: | :-----: | :-: | :---:
Audio/Video | :white_check_mark: | :white_check_mark: | :white_check_mark:
Data channel | :white_check_mark: | :white_check_mark: | :white_check_mark:
Screen Capture | | | :white_check_mark:
| API | Android | iOS | JS | JVM |
|:--------------:|:------------------:|:------------------:|:------------------:|:------------------:|
| Audio/Video | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Data channel | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Screen Capture | | | :white_check_mark: | :white_check_mark: |

## WebRTC revision
Current revision: M114
Expand Down
6 changes: 4 additions & 2 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ repositories {
gradlePluginPortal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

dependencies {
implementation("com.android.tools.build:gradle:8.0.2")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22")
implementation("com.android.tools.build:gradle:8.2.1")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21")
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0")
implementation("org.jetbrains.compose:compose-gradle-plugin:1.5.11")
}
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/AndroidConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object AndroidConfig {
const val minSdkVersion = 21
const val compileSdkVersion = 33
const val targetSdkVersion = 33
const val compileSdkVersion = 34
const val targetSdkVersion = 34
}
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ kotlin {
browser()
}

jvm()

sourceSets {
getByName("commonTest") {
dependencies {
Expand Down
19 changes: 13 additions & 6 deletions libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
[versions]

kotlinCoroutines = "1.6.4"
kotlinCoroutines = "1.7.3"
kotlinWrappers = "1.0.0-pre.343"
androidxCompose = "1.4.3"
decompose = "1.0.0-alpha-01"
androidxCompose = "1.6.0"
decompose = "2.0.1"

[libraries]
webrtcSdk = "io.github.webrtc-sdk:android:114.5735.02"
webrtc-android = "io.github.webrtc-sdk:android:114.5735.02"
webrtc-java = "dev.onvoid.webrtc:webrtc-java:0.8.0"

kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" }
kotlin-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinCoroutines" }
kotlin-coroutinesPlayServices = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinCoroutines" }
kotlin-wrappers-bom = { module = "org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom", version.ref = "kotlinWrappers" }
kotlin-wrappers-emotion = { module = "org.jetbrains.kotlin-wrappers:kotlin-emotion", version.ref = "kotlinWrappers" }
kotlin-wrappers-react = { module = "org.jetbrains.kotlin-wrappers:kotlin-react", version.ref = "kotlinWrappers" }
kotlin-wrappers-reactDom = { module = "org.jetbrains.kotlin-wrappers:kotlin-react-dom", version.ref = "kotlinWrappers" }
kotlin-wrappers-mui = { module = "org.jetbrains.kotlin-wrappers:kotlin-mui", version.ref = "kotlinWrappers" }
kotlin-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
kotlin-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0"

androidx-coreKtx = "androidx.core:core-ktx:1.10.1"
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
androidx-activity-activityKtx = "androidx.activity:activity-ktx:1.7.2"
androidx-compose-activity = "androidx.activity:activity-compose:1.7.2"
androidx-activity-activityKtx = "androidx.activity:activity-ktx:1.8.2"
androidx-compose-activity = "androidx.activity:activity-compose:1.8.2"
androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidxCompose" }
androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidxCompose" }
androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
Expand All @@ -32,8 +35,12 @@ accompanist-permissions = "com.google.accompanist:accompanist-permissions:0.25.0

decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" }
decompose-kmp = { module = "com.arkivanov.decompose:extensions-compose-jetbrains", version.ref = "decompose" }

firebase-bom = "com.google.firebase:firebase-bom:32.2.0"
firebase-firestore = { module = "com.google.firebase:firebase-firestore-ktx" }
firebase-firestore-jvm = "dev.gitlive:firebase-firestore:1.11.1"

kermit = "co.touchlab:kermit:1.1.2"

bouncyCastle = "org.bouncycastle:bcpkix-jdk18on:1.77"
6 changes: 6 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ Open `sample/app-ios/app-ios.xcworkspace` in XCode build and run
```bash
./gradlew browserRun
```

### JVM Desktop

```bash
./gradlew ":sample:app-jvm:jvmRun" -DmainClass="com.shepeliev.webrtckmp.MainKt" --quiet
```
2 changes: 1 addition & 1 deletion sample/app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ android {
}

composeOptions {
kotlinCompilerExtensionVersion = "1.4.8"
kotlinCompilerExtensionVersion = "1.5.8"
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.shepeliev.webrtckmp.sample

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
import com.shepeliev.webrtckmp.sample.shared.Room

Expand All @@ -21,10 +24,15 @@ fun App(room: Room) {

val roomModel by room.model.subscribeAsState()

Crossfade(targetState = roomModel) { model ->
when (model.localStream) {
null -> OpenMicrophoneAndCameraScreen(room)
else -> VideoScreen(room)
Box(
modifier = Modifier
.padding(it)
) {
Crossfade(targetState = roomModel) { model ->
when (model.localStream) {
null -> OpenMicrophoneAndCameraScreen(room)
else -> VideoScreen(room)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fun VideoScreen(room: Room) {

Column(horizontalAlignment = Alignment.CenterHorizontally) {

Crossfade(targetState = roomModel) {
Crossfade(targetState = roomModel, label = "") {
when {
it.isJoining -> CircularProgressIndicator()

Expand Down
38 changes: 38 additions & 0 deletions sample/app-jvm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}

kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "17"
}
withJava()
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(project(":sample:shared"))
implementation(compose.material)
implementation(compose.desktop.currentOs)
implementation(deps.decompose.kmp)
implementation(deps.kotlin.coroutines.swing)
}
}
val jvmTest by getting
}
}

compose.desktop {
application {
mainClass = "com.shepeliev.webrtckmp.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "KMPTemplate"
packageVersion = "1.0.0"
}
}
}
27 changes: 27 additions & 0 deletions sample/app-jvm/src/jvmMain/kotlin/com/shepeliev/webrtckmp/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.shepeliev.webrtckmp

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
import com.shepeliev.webrtckmp.sample.shared.Room

@Composable
fun App(room: Room) {
val roomModel by room.model.subscribeAsState()

Crossfade(targetState = roomModel) { model ->
when (model.localStream) {
null -> SelectMicrophoneCameraScreen(room)
else -> VideoScreen(room)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.shepeliev.webrtckmp

import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun JoinRoomButton(onJoin: (String) -> Unit, enabled: Boolean) {
var isJoinDialogVisible by remember { mutableStateOf(false) }

if (isJoinDialogVisible) {
var roomId by remember { mutableStateOf("") }

AlertDialog(
onDismissRequest = { isJoinDialogVisible = false },

confirmButton = {
TextButton(
onClick = {
onJoin(roomId)
isJoinDialogVisible = false
},
enabled = roomId.isNotBlank()
) {
Text("Join")
}
},

dismissButton = {
TextButton(onClick = { isJoinDialogVisible = false }) {
Text("Cancel")
}
},

title = { Text("Join into room") },

text = {
OutlinedTextField(
value = roomId,
onValueChange = { roomId = it },
placeholder = { Text("Room ID") }
)
}
)
}

Button(onClick = { isJoinDialogVisible = true }, enabled = enabled) {
Text("Join")
}
}
76 changes: 76 additions & 0 deletions sample/app-jvm/src/jvmMain/kotlin/com/shepeliev/webrtckmp/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.shepeliev.webrtckmp

import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.LocalWindowExceptionHandlerFactory
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowExceptionHandler
import androidx.compose.ui.window.WindowExceptionHandlerFactory
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.singleWindowApplication
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.shepeliev.webrtckmp.sample.shared.RoomComponent
import dev.onvoid.webrtc.logging.Logging
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import java.awt.event.WindowEvent
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.system.exitProcess


@OptIn(ExperimentalComposeUiApi::class)
fun main() {
var lastError: Throwable? by mutableStateOf(null)

WebRtc.configureBuilder {
loggingSeverity = Logging.Severity.INFO
}

application(exitProcessOnExit = false) {
System.setProperty("compose.interop.blending", "true")

val lifecycle = LifecycleRegistry()

val room = RoomComponent(
componentContext = DefaultComponentContext(lifecycle),
scope = CoroutineScope(EmptyCoroutineContext + CoroutineName("App")),
)

CompositionLocalProvider(
LocalWindowExceptionHandlerFactory provides WindowExceptionHandlerFactory { window ->
WindowExceptionHandler {
lastError = it
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
}
}
) {
Window(onCloseRequest = ::exitApplication) {
App(room)
}
}
}

if (lastError != null) {
lastError?.printStackTrace()
singleWindowApplication(
state = WindowState(width = 200.dp, height = Dp.Unspecified),
exitProcessOnExit = false
) {
Text(lastError?.message ?: "Unknown error", Modifier.padding(8.dp))
}

exitProcess(1)
} else {
exitProcess(0)
}
}
Loading

0 comments on commit a6b6df2

Please sign in to comment.