Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JVM Support #103

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
77 changes: 52 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ It supports Android, iOS, JS. Other platforms - PRs are welcome.


## API implementation map
API | Android | iOS | JS/WasmJS
:-: |:------------------:| :-: | :---:
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: | | :white_check_mark:
| API | Android | iOS | JS/WasmJS | 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: | :white_check_mark: |

## WebRTC revision
Current revision: M125
Expand All @@ -34,31 +34,58 @@ the WebRTC SDK using CocoaPods in `build.gradle.kts`:

```kotlin
kotlin {
cocoapods {
version = "1.0.0"
summary = "Shared module"
homepage = "not published"
ios.deploymentTarget = "13.0"

pod("WebRTC-SDK") {
version = "125.6422.05"
moduleName = "WebRTC"
}

podfile = project.file("../iosApp/Podfile")

framework {
baseName = "shared"
isStatic = true
cocoapods {
version = "1.0.0"
summary = "Shared module"
homepage = "not published"
ios.deploymentTarget = "13.0"

pod("WebRTC-SDK") {
version = "125.6422.05"
moduleName = "WebRTC"
}

podfile = project.file("../iosApp/Podfile")

framework {
baseName = "shared"
isStatic = true
}

xcodeConfigurationToNativeBuildType["CUSTOM_DEBUG"] = NativeBuildType.DEBUG
xcodeConfigurationToNativeBuildType["CUSTOM_RELEASE"] = NativeBuildType.RELEASE
}

xcodeConfigurationToNativeBuildType["CUSTOM_DEBUG"] = NativeBuildType.DEBUG
xcodeConfigurationToNativeBuildType["CUSTOM_RELEASE"] = NativeBuildType.RELEASE
}

iosX64()
iosArm64()
iosSimulatorArm64()
jvm()

}

dependencies {
// Choose the right architecture for your system
// see https://github.com/devopvoid/webrtc-java for supported platforms
val osName = System.getProperty("os.name").lowercase()
val hostOS = if (osName.contains("mac")) {
"macos"
} else if (osName.contains("linux")) {
"linux"
} else if (osName.contains("windows")) {
"windows"
} else {
throw IllegalStateException("Unsupported OS: $osName")
}
val hostArch = when (val arch = System.getProperty("os.arch").lowercase()) {
"amd64" -> "x86_64"
else -> arch
}
jvmMainImplementation(
group = "dev.onvoid.webrtc",
name = "webrtc-java",
version = "0.8.0",
classifier = "$hostOS-$hostArch"
)
}
```

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx4g

# Kotlin
kotlin.code.style=official
Expand Down
7 changes: 6 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ androidx-test-runner = "1.6.2"
androidx-test-rules = "1.6.1"
accompanist-permision = "0.34.0"
kermit = "2.0.3"
bouncy-castle = "1.77"
kotlin-wrappers = "1.0.0-pre.732"
webrtc-android-sdk = "125.6422.05"
webrtc-ios-sdk = "125.6422.05"
webrtc-java-sdk = "0.8.0"

#Android
minSdk = "21"
Expand All @@ -28,10 +30,12 @@ nexus = "1.3.0"
compose-plugin = "1.6.11"

[libraries]
webrtc-sdk = { module = "io.github.webrtc-sdk:android", version.ref = "webrtc-android-sdk" }
webrtc-android = { module = "io.github.webrtc-sdk:android", version.ref = "webrtc-android-sdk" }
webrtc-java = { module = "dev.onvoid.webrtc:webrtc-java", version.ref = "webrtc-java-sdk" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" }
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
kotlin-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlin-coroutines" }
androidx-coreKtx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
Expand All @@ -40,6 +44,7 @@ androidx-material = { module = "com.google.android.material:material", version.r
androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permision" }
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
java-bouncycastle = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncy-castle" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" }
Expand Down
6 changes: 6 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ Open `sample/iosApp/iosApp.xcworkspace` in XCode build and run
```bash
./gradlew sample:composeApp:wasmJsBrowserRun
```

### JVM Desktop

```bash
./gradlew ":sample:composeApp:run" -DmainClass="MainKt" --quiet
```
37 changes: 37 additions & 0 deletions sample/composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
Expand Down Expand Up @@ -55,6 +57,12 @@ kotlin {
}
}

jvm {
compilations.all {
kotlinOptions.jvmTarget = "17"
}
}

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = "composeApp"
Expand Down Expand Up @@ -99,6 +107,24 @@ kotlin {
implementation(libs.kotlin.wrappers.reactDom)
implementation(libs.kotlin.wrappers.emotion)
}

jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlin.coroutines.swing)

val osName = System.getProperty("os.name")
val hostOS = when {
osName == "Mac OS X" -> "macos"
osName.startsWith("Win") -> "windows"
osName.startsWith("Linux") -> "linux"
else -> error("Unsupported OS: $osName")
}
val hostArch = when (val arch = System.getProperty("os.arch").lowercase()) {
"amd64" -> "x86_64"
else -> arch
}
implementation("${libs.webrtc.java.get()}:$hostOS-$hostArch")
}
}
}

Expand Down Expand Up @@ -135,3 +161,14 @@ android {
debugImplementation(compose.uiTooling)
}
}

compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "KMPTemplate"
packageVersion = "1.0.0"
}
}
}
2 changes: 1 addition & 1 deletion sample/composeApp/composeApp.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ Pod::Spec.new do |spec|
SCRIPT
}
]
spec.resources = ['build/compose/cocoapods/compose-resources']
spec.resources = ['build\compose\cocoapods\compose-resources']
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import com.shepeliev.webrtckmp.MediaStream
import com.shepeliev.webrtckmp.videoTracks
import kotlinx.coroutines.launch

@Composable
actual fun DeviceSelectButton(
modifier: Modifier,
localStream: MediaStream
) {
val scope = rememberCoroutineScope()

Button(
onClick = {
scope.launch { localStream.videoTracks.firstOrNull()?.switchCamera() }
},
modifier = modifier,
) {
Text("Switch Camera")
}
}
21 changes: 18 additions & 3 deletions sample/composeApp/src/androidMain/kotlin/StartButton.android.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@ import android.provider.Settings
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.edit
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.shepeliev.webrtckmp.MediaDevices
import com.shepeliev.webrtckmp.MediaStream
import kotlinx.coroutines.launch

@OptIn(ExperimentalPermissionsApi::class)
@Composable
actual fun StartButton(onClick: () -> Unit, modifier: Modifier) {
actual fun StartButton(setLocalStream: (MediaStream?) -> Unit, modifier: Modifier) {
val context = LocalContext.current
val scope = rememberCoroutineScope()

val openMediaStreams = remember {
{
scope.launch {
val stream = MediaDevices.getUserMedia(audio = true, video = true)
setLocalStream(stream)
}
}
}

val permissions = rememberMultiplePermissionsState(
listOf(
Expand All @@ -24,13 +39,13 @@ actual fun StartButton(onClick: () -> Unit, modifier: Modifier) {
)
) {
if (it.all { (_, granted) -> granted }) {
onClick()
openMediaStreams()
}
}

Button(onClick = {
if (permissions.allPermissionsGranted) {
onClick()
openMediaStreams()
} else {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val permissionsRequested = prefs.getBoolean("permissionsRequested", false)
Expand Down
22 changes: 7 additions & 15 deletions sample/composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ import androidx.compose.ui.unit.dp
import co.touchlab.kermit.Logger
import co.touchlab.kermit.platformLogWriter
import com.shepeliev.webrtckmp.AudioStreamTrack
import com.shepeliev.webrtckmp.MediaDevices
import com.shepeliev.webrtckmp.MediaStream
import com.shepeliev.webrtckmp.PeerConnection
import com.shepeliev.webrtckmp.VideoStreamTrack
import com.shepeliev.webrtckmp.videoTracks
import kotlinx.coroutines.launch
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
Expand Down Expand Up @@ -84,12 +82,7 @@ fun App() {

Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
if (localStream == null) {
StartButton(onClick = {
scope.launch {
val stream = MediaDevices.getUserMedia(audio = true, video = true)
setLocalStream(stream)
}
})
StartButton(setLocalStream = setLocalStream)
} else {
StopButton(
onClick = {
Expand All @@ -102,10 +95,8 @@ fun App() {
}
)

SwitchCameraButton(
onClick = {
scope.launch { localStream.videoTracks.firstOrNull()?.switchCamera() }
}
DeviceSelectButton(
localStream = localStream,
)
}
if (peerConnections == null) {
Expand All @@ -125,15 +116,16 @@ fun App() {
}
}


@Composable
private fun CallButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
internal fun CallButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(onClick, modifier = modifier) {
Text("Call")
}
}

@Composable
private fun HangupButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
internal fun HangupButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(onClick, modifier = modifier) {
Text("Hangup")
}
Expand All @@ -147,7 +139,7 @@ private fun SwitchCameraButton(onClick: () -> Unit, modifier: Modifier = Modifie
}

@Composable
private fun StopButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
internal fun StopButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(onClick = onClick, modifier = modifier) {
Text("Stop")
}
Expand Down
9 changes: 9 additions & 0 deletions sample/composeApp/src/commonMain/kotlin/DeviceSelectButton.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.shepeliev.webrtckmp.MediaStream

@Composable
expect fun DeviceSelectButton(
modifier: Modifier = Modifier,
localStream: MediaStream,
)
3 changes: 2 additions & 1 deletion sample/composeApp/src/commonMain/kotlin/StartButton.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.shepeliev.webrtckmp.MediaStream

@Composable
expect fun StartButton(onClick: () -> Unit, modifier: Modifier = Modifier)
expect fun StartButton(setLocalStream: (MediaStream?) -> Unit, modifier: Modifier = Modifier)
Loading
Loading