Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
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
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx4g

kotlin.code.style=official
kotlin.mpp.enableCInteropCommonization=true
Expand Down
820 changes: 301 additions & 519 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

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