Skip to content

ktor-client-webrtc module common API + tests #4902

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

zibet27
Copy link
Collaborator

@zibet27 zibet27 commented Jun 3, 2025

Subsystem
Client

Motivation
KTOR-7958 WebRTC Client.

Solution
The solution contains a common KMP interface that allows

  • Create WebRTC Clients
  • Establish peer connections
  • Send audio and video tracks (tracks are tightly bound to the protocol, so they were also included)
  • Listen for ICE candidates, remote tracks, and connection status changes
  • Obtain continuous connection statistics, etc

The API is "up to Ktor standards" and utilises flows and coroutines rather than callbacks. It is unified across those platforms (configs are custom for all engines), but still allows access to native objects if some custom logic is needed. There are integration tests. A mock JS target has been added so that Gradle can include the module in the build. Code documentation contains links to MDN.

TODO:
Add datachannels to the API.

This is a part of a previously created pull request.

@zibet27 zibet27 self-assigned this Jun 3, 2025
@zibet27 zibet27 added the feature label Jun 3, 2025
Copy link
Contributor

coderabbitai bot commented Jun 3, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A new multiplatform WebRTC client API is introduced for Kotlin under the ktor-client-webrtc module, targeting JavaScript. The implementation defines abstractions for WebRTC engines, peer connections, media tracks, and configuration. Supporting test infrastructure, build scripts, and platform-specific dispatcher implementations are also included. The project is registered in the build settings.

Changes

Files / Grouped Files Change Summary
ktor-client/ktor-client-webrtc/api/ktor-client-webrtc.klib.api Introduced comprehensive multiplatform WebRTC client API, defining core abstractions, configuration, and types.
ktor-client/ktor-client-webrtc/build.gradle.kts
ktor-client/ktor-client-webrtc/gradle.properties
Added build script and properties for module configuration and platform targeting.
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTC.kt Added WebRTC object with enums, data classes, interfaces, and exceptions for protocol entities and states.
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCClient.kt Added WebRTCClient class, engine factory interface, and client factory function.
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCEngine.kt Added WebRTCConfig, MediaTrackFactory, WebRTCEngine interface, and base class with dispatcher logic.
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCMedia.kt Added WebRTCMedia object for media track abstractions, constraints, enums, and related exceptions.
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCPeerConnection.kt Added WebRtcPeerConnection abstract class and Operation sealed class for track operations.
ktor-client/ktor-client-webrtc/common/test/io/ktor/client/webrtc/WebRTCEngineTest.kt Added comprehensive coroutine-based test suite for WebRTC client features and peer connection management.
ktor-client/ktor-client-webrtc/js/src/io/ktor/client/webrtc/WebRTCEngine.js.kt Added JS-specific actual implementation for IO coroutine dispatcher.
ktor-client/ktor-client-webrtc/js/test/io/ktor/client/webrtc/WebRTCEngineTest.js.kt Added JS-specific stubs for test client creation and permission granting functions.
settings.gradle.kts Registered the new ktor-client-webrtc module in the project settings.
ktor-server/ktor-server-core/api/ktor-server-core.api Added new configuration value implementation and serializable config value interface.

Suggested reviewers

  • e5l

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (8)
ktor-server/ktor-server-core/api/ktor-server-core.api (1)

387-389: Interface declaration appears redundant.

The SerializableConfigValue interface declares getAs(TypeInfo): Object which is already declared in its parent interface ApplicationConfigValue (line 299). This creates potential confusion about the interface's purpose.

Consider either:

  1. Removing the redundant getAs declaration if the interface serves only as a marker
  2. Adding additional methods that differentiate this interface from ApplicationConfigValue
  3. Adding documentation to clarify the intended purpose
 public abstract interface class io/ktor/server/config/SerializableConfigValue : io/ktor/server/config/ApplicationConfigValue {
-	public abstract fun getAs (Lio/ktor/util/reflect/TypeInfo;)Ljava/lang/Object;
+	// Remove redundant declaration or add specific serialization-related methods
 }
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCClient.kt (2)

10-10: Documentation could be more precise.

The comment mentions "managing media tracks" but the WebRTCClient class doesn't directly manage tracks - it delegates all operations to the engine. Consider updating to reflect the delegation pattern.

-A multiplatform asynchronous WebRTC client for establishing peer-to-peer connections, managing media tracks.
+A multiplatform asynchronous WebRTC client that provides access to WebRTC engine functionality for establishing peer-to-peer connections and managing media tracks.

21-30: Fix variable name inconsistency in documentation example.

The example creates a client named rtcClient but the closing example references client.

 * val rtcClient = WebRTCClient(AndroidWebRTC) {
 *     iceServers = listOf(WebRTC.IceServer(urls = "stun:stun.l.google.com:19302"))
 *     statsRefreshRate = 10_000
 * }
 * ```
 *
 * # Closing the client
 * ```kotlin
-* client.close()
+* rtcClient.close()
 * ```
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCPeerConnection.kt (1)

170-172: Consider thread safety for callback assignment.

Direct assignment of the callback might have thread safety issues if called from multiple coroutines. Consider using atomic operations or synchronization.

+import kotlinx.atomicfu.atomic
+import kotlinx.atomicfu.AtomicRef

-protected var negotiationNeededCallback: () -> Unit = {}
+private val _negotiationNeededCallback: AtomicRef<() -> Unit> = atomic {}
+protected var negotiationNeededCallback: () -> Unit
+    get() = _negotiationNeededCallback.value
+    set(value) { _negotiationNeededCallback.value = value }
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCMedia.kt (1)

147-156: Consider improving exception formatting consistency.

The exception classes are well-designed, but there's inconsistent spacing in the PermissionException class declaration.

Apply this diff for consistent formatting:

-    public class PermissionException(mediaType: String?) :
-        RuntimeException("You should grant $mediaType permission for this operation to work.")
+    public class PermissionException(mediaType: String?) : RuntimeException(
+        "You should grant $mediaType permission for this operation to work."
+    )
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTC.kt (1)

220-228: Consider improving type safety for RTP parameters.

The RtpParameters interface uses Any type for codecs, rtcp, and encodings. While this provides flexibility for platform-specific implementations, it reduces type safety.

Consider defining more specific interfaces or using generic type parameters to improve type safety while maintaining flexibility:

public interface RtpCodec {
    val mimeType: String
    val clockRate: Int
    // other common codec properties
}

public interface RtpParameters {
    public val transactionId: String
    public val codecs: Iterable<RtpCodec>
    public val rtcp: RtcpParameters
    // ...
}

This would provide better compile-time type checking while still allowing platform-specific extensions.

ktor-client/ktor-client-webrtc/common/test/io/ktor/client/webrtc/WebRTCEngineTest.kt (1)

348-407: Thorough remote track management test!

This test excellently covers:

  • Remote track reception for both audio and video
  • Track removal and renegotiation
  • Replay behavior for late subscribers
  • Proper cleanup of coroutine jobs

Consider extracting the track verification logic into a helper function to reduce duplication:

private fun verifyTracks(tracks: Array<Operation<WebRTCMedia.Track>>, expectedAudio: Int, expectedVideo: Int) {
    assertEquals(expectedAudio, tracks.filter { it.item.kind === WebRTCMedia.TrackType.AUDIO }.size)
    assertEquals(expectedVideo, tracks.filter { it.item.kind === WebRTCMedia.TrackType.VIDEO }.size)
}
ktor-client/ktor-client-webrtc/api/ktor-client-webrtc.klib.api (1)

586-586: Remove incomplete line at end of file.

Line 586 appears to be incomplete with just the line number and no content. This should be removed to clean up the file.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd41f80 and 2b2acf3.

📒 Files selected for processing (13)
  • ktor-client/ktor-client-webrtc/api/ktor-client-webrtc.klib.api (1 hunks)
  • ktor-client/ktor-client-webrtc/build.gradle.kts (1 hunks)
  • ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTC.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCClient.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCEngine.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCMedia.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCPeerConnection.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/common/test/io/ktor/client/webrtc/WebRTCEngineTest.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/gradle.properties (1 hunks)
  • ktor-client/ktor-client-webrtc/js/src/io/ktor/client/webrtc/WebRTCEngine.js.kt (1 hunks)
  • ktor-client/ktor-client-webrtc/js/test/io/ktor/client/webrtc/WebRTCEngineTest.js.kt (1 hunks)
  • ktor-server/ktor-server-core/api/ktor-server-core.api (1 hunks)
  • settings.gradle.kts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCEngine.kt (1)
ktor-client/ktor-client-webrtc/js/src/io/ktor/client/webrtc/WebRTCEngine.js.kt (1)
  • ioDispatcher (10-10)
🔇 Additional comments (20)
ktor-client/ktor-client-webrtc/gradle.properties (1)

4-7:

❌ Incorrect review comment

Verify platform target configuration for WebRTC module.

Setting all platform targets to false seems counterintuitive for a WebRTC client module, especially target.js.nodeJs=false. WebRTC is primarily a web technology that should support JavaScript/browser platforms. This configuration might prevent the module from being built for the intended platforms.


🏁 Script executed:

#!/bin/bash
# Verify if other Ktor client modules have similar configurations
fd -t f "gradle.properties" ktor-client/ --exec grep -H "target\."

Length of output: 485


Gradle properties correctly constrain WebRTC module to browser JS

The ktor-client-webrtc/gradle.properties file intentionally disables target.js.nodeJs, target.wasmJs, target.jvm, and target.posix while leaving the js.browser target enabled by default. This ensures the module only compiles to browser JavaScript, which is exactly what a WebRTC client needs. No changes are necessary unless you specifically want to add Node.js, WASM, JVM, or POSIX support (in which case you would re-enable those targets).

Likely an incorrect or invalid review comment.

settings.gradle.kts (1)

129-129: LGTM! Proper integration of the new WebRTC module.

The addition correctly follows the established pattern for including client modules and is positioned appropriately within the client projects section.

ktor-client/ktor-client-webrtc/js/src/io/ktor/client/webrtc/WebRTCEngine.js.kt (1)

10-10:

✅ Verification successful

Consider dispatcher choice for WebRTC I/O operations.

Using Dispatchers.Default for WebRTC I/O operations might not be optimal. WebRTC typically involves network I/O, media processing, and async operations that would benefit from Dispatchers.IO. However, on JavaScript platforms, the distinction may be less significant due to the single-threaded nature.


🏁 Script executed:

#!/bin/bash
# Check how other Ktor client engines handle dispatchers on JS platform
rg -A 3 -B 3 "Dispatchers\." ktor-client/ --type kotlin | grep -A 5 -B 5 "\.js\."

Length of output: 2623


No changes needed for JS I/O dispatcher

Using Dispatchers.Default in WebRTCEngine.js.kt is consistent with other Ktor JS engines (e.g. HttpClientEngineBase.js.kt), and Kotlin/JS only provides Default and Unconfined dispatchers—there is no Dispatchers.IO on JS. Keeping Dispatchers.Default here is correct.

ktor-client/ktor-client-webrtc/build.gradle.kts (2)

8-8:

✅ Verification successful

Verify need for kotlinx-serialization plugin.

WebRTC protocols typically handle binary data streams rather than serialized objects. Consider whether the kotlinx-serialization plugin is necessary for this module.


🏁 Script executed:

#!/bin/bash
# Check if serialization is used in the WebRTC module source files
rg -r '$1' '@Serializable|serialization|kotlinx\.serialization' ktor-client/ktor-client-webrtc/

Length of output: 4345


kotlinx-serialization plugin is required

The WebRTC module defines @serializable types (e.g. SessionDescription, IceCandidate) and relies on generated serializers across multiplatform targets:

  • common/src/io/ktor/client/webrtc/WebRTC.kt: imports and uses @serializable annotations
  • Generated serializers in the .klib.api files (e.g. WebRTC.IceCandidate.$serializer)

No changes needed.


16-19:

❓ Verification inconclusive

Consider adding ktor-client-core dependency.

Most Ktor client modules depend on ktor-client-core. Verify if this WebRTC client module needs core client functionality.


🏁 Script executed:

#!/bin/bash
# Check if other client modules include ktor-client-core dependency
fd -t f "build.gradle.kts" ktor-client/ --exec grep -l "ktor-client-core"

Length of output: 71


To verify whether a ktor-client-core submodule exists and how it’s used elsewhere, let’s list the client modules and search their build files for any references:

#!/bin/bash
# 1. List direct submodules under ktor-client
echo "=== Submodules in ktor-client/ ==="
fd -t d --max-depth 1 '*' ktor-client

# 2. Look for project dependency on ":ktor-client-core"
echo -e "\n=== project(\":ktor-client-core\") references ==="
rg -n 'project\("(:ktor-client-core)"\)' -g 'ktor-client/**/build.gradle.kts' || echo "None found"

# 3. Look for external Maven coordinate usage of ktor-client-core
echo -e "\n=== io.ktor.client:ktor-client-core references ==="
rg -n 'io\.ktor\.client:ktor-client-core' -g 'ktor-client/**/build.gradle.kts' || echo "None found"

Verify ktor-client-core dependency

I didn’t find any other ktor-client modules pulling in ktor-client-core. Please confirm whether your WebRTC client needs core client APIs (e.g. HttpClient, call handling, engines). If it does, add it alongside your existing dependencies:

commonMain.dependencies {
    api(project(":ktor-io"))
    api(project(":ktor-utils"))
    api("io.ktor:ktor-client-core:$ktor_version")
}

Files to check:

  • ktor-client/ktor-client-webrtc/build.gradle.kts (lines 16–19)
ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCClient.kt (1)

12-37: Clean implementation using delegation pattern.

The WebRTCClient class properly delegates to the engine, and the factory pattern provides a clean API for client instantiation. Good use of the @KtorDsl annotation for configuration blocks.

ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCEngine.kt (1)

73-73:

✅ Verification successful

Verify platform-specific dispatcher implementations.

The expected function pattern is correctly used for multiplatform. The JS implementation returns Dispatchers.Default which is appropriate for JavaScript's single-threaded nature.


🏁 Script executed:

#!/bin/bash
# Description: Check if other platform implementations of ioDispatcher() exist

# Search for actual implementations of ioDispatcher
ast-grep --pattern 'internal actual fun ioDispatcher(): CoroutineDispatcher'

Length of output: 744


✅ Confirmed platform-specific IO dispatchers

All actual implementations of ioDispatcher() use the appropriate dispatcher for their platform:

  • JVM & POSIX: Dispatchers.IO
  • JavaScript & WASM: Dispatchers.Default

No changes required.

ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCPeerConnection.kt (1)

38-91: Excellent reactive API design using Kotlin flows.

The use of StateFlow and SharedFlow for WebRTC state management provides a clean, reactive API that aligns well with Kotlin coroutines. The replay parameters for flows are configurable, which is a thoughtful design choice.

ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTCMedia.kt (4)

7-13: Well-structured media abstraction design!

The WebRTCMedia object provides a clean encapsulation of media capture abstractions with appropriate documentation and references.


14-34: LGTM!

The VideoTrackConstraints data class is well-designed with appropriate nullable properties and clear documentation.


61-84: Excellent interface design with proper resource management!

The Track interface is well-designed with AutoCloseable for proper resource management and a flexible getNative() method for platform-specific implementations.


86-145: LGTM!

The track type enums and specialized track interfaces are well-structured and properly documented.

ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRTC.kt (2)

15-88: Excellent WebRTC protocol abstractions!

The WebRTC object provides comprehensive and well-documented protocol entities with appropriate MDN references. The isSuccessful() helper method on IceConnectionState is a nice touch for improving API usability.


144-176: LGTM!

The serializable data classes are well-designed for WebRTC signaling with appropriate use of Kotlin serialization.

ktor-client/ktor-client-webrtc/common/test/io/ktor/client/webrtc/WebRTCEngineTest.kt (3)

21-64: Excellent test setup with proper abstractions!

The test framework is well-designed with:

  • Platform-specific abstractions using expect functions
  • Proper permission handling
  • Clean lifecycle management with @BeforeTest and @AfterTest
  • Flexible test execution with real-time support

219-292: Comprehensive connection establishment test!

This test thoroughly validates the WebRTC connection lifecycle including:

  • State transitions for ICE, signaling, and connection states
  • ICE candidate exchange
  • Negotiation callbacks
  • ICE restart functionality

The use of channels and select for monitoring multiple state flows is an excellent pattern for async testing.


294-318: Good error handling test coverage!

The tests properly validate that the API throws appropriate exceptions for invalid inputs and ensures state remains unchanged after failed operations.

ktor-client/ktor-client-webrtc/api/ktor-client-webrtc.klib.api (3)

18-25: Well-designed coroutine-friendly interface.

The WebRTCEngine interface follows Kotlin best practices by:

  • Extending CoroutineScope for structured concurrency
  • Implementing Closeable for proper resource management
  • Using suspend functions for async operations
  • Providing access to the coroutine dispatcher

This design enables clean async/await patterns and proper lifecycle management.


45-72: Excellent reactive state management implementation.

The peer connection class demonstrates best practices for Kotlin Flow usage:

  • Internal MutableStateFlow properties for state management
  • External StateFlow properties for read-only observation
  • SharedFlow for event streams (ICE candidates, remote tracks)
  • Proper encapsulation preventing external state mutation

This reactive design enables clean integration with Kotlin coroutines and UI frameworks.


264-267: Well-implemented serialization support.

The selective application of kotlinx.serialization to data transfer objects (SessionDescription, IceCandidate, SessionDescriptionType) is appropriate. These are the entities most likely to be serialized for signaling server communication, while internal state objects correctly omit serialization support.

Also applies to: 340-351, 415-428

@zibet27 zibet27 requested review from osipxd, e5l, Mr3zee and bjhham June 3, 2025 11:13
@zibet27 zibet27 marked this pull request as draft June 3, 2025 11:47
Copy link
Member

@Mr3zee Mr3zee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have minor issues, as I'm not closely familiar with the protocol itself

Copy link
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, please check minor comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants