Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/wacky-turtles-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"client-sdk-android": patch
---

Add support for RPC V2
28 changes: 27 additions & 1 deletion livekit-android-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ android {
main {
proto {
srcDir generated.protoSrc
exclude '*/*.proto' // only use top-level protos.
// Compile top-level protos plus logger/ (imported by livekit_models et al.).
// Skip server-only subdirs and top-level files that depend on them.
exclude 'agent/**', 'infra/**', 'rpc/**'
exclude 'livekit_agent_simulation.proto' // imports agent/livekit_agent_session.proto
// descriptor.proto extracted from protobuf-java at build time (see below).
srcDir layout.buildDirectory.dir("extracted-descriptor-proto")
}
java {
srcDir "${protobuf.generatedFilesBaseDir}/main/javalite"
Expand Down Expand Up @@ -74,6 +79,27 @@ android {

}

// Build-time only: resolve protobuf-java just so we can extract
// google/protobuf/descriptor.proto, needed because logger/options.proto extends
// google.protobuf.FieldOptions. protobuf-javalite does not bundle descriptor.proto.
// Keep this jar OFF compileClasspath / runtimeClasspath so its other .proto files
// don't collide with protobuf-javalite at packaging time.
configurations {
descriptorProtoSource
}
dependencies {
descriptorProtoSource "com.google.protobuf:protobuf-java:${libs.versions.protobuf.get()}"
}
def extractDescriptorProto = tasks.register("extractDescriptorProto", Copy) {
from { zipTree(configurations.descriptorProtoSource.singleFile) }
include "google/protobuf/descriptor.proto"
into layout.buildDirectory.dir("extracted-descriptor-proto")
}
afterEvaluate {
tasks.matching { it.name.startsWith("generate") && it.name.endsWith("Proto") }
.configureEach { dependsOn extractDescriptorProto }
}

protobuf {
protoc {
// for apple m1, please add protoc_platform=osx-x86_64 in $HOME/.gradle/gradle.properties
Expand Down
12 changes: 6 additions & 6 deletions livekit-android-sdk/detekt-baseline-release.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@
<ID>EmptyFunctionBlock:RTCEngine.kt$RTCEngine${ }</ID>
<ID>HasPlatformType:DataChannelManager.kt$DataChannelManager$@get:FlowObservable var state by flowDelegate(dataChannel.state()) private set</ID>
<ID>IgnoredReturnValue:BaseStreamReceiver.kt$BaseStreamReceiver$catch { }</ID>
<ID>InstanceOfCheckForException:LocalParticipant.kt$LocalParticipant$e is RpcError</ID>
<ID>IgnoredReturnValue:RpcServerManager.kt$RpcServerManager$publishRpcAck(callerIdentity, requestId)</ID>
<ID>InstanceOfCheckForException:RpcServerManager.kt$RpcServerManager$e is RpcError</ID>
<ID>LargeClass:LocalParticipant.kt$LocalParticipant : ParticipantOutgoingDataStreamManagerRpcManager</ID>
<ID>LargeClass:RTCEngine.kt$RTCEngine : Listener</ID>
<ID>LargeClass:Room.kt$Room : ListenerParticipantListenerRpcManagerIncomingDataStreamManager</ID>
<ID>LargeClass:SignalClient.kt$SignalClient : WebSocketListener</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$@Throws(TrackException.PublishException::class) private suspend fun publishTrackImpl( track: Track, options: TrackPublishOptions, requestConfig: AddTrackRequest.Builder.() -> Unit, encodings: List&lt;RtpParameters.Encoding> = emptyList(), publishListener: PublishListener? = null, ): LocalTrackPublication?</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$override suspend fun performRpc( destinationIdentity: Identity, method: String, payload: String, responseTimeout: Duration, ): String</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$private fun publishAdditionalCodecForTrack(track: LocalVideoTrack, codec: VideoCodec, options: VideoTrackPublishOptions)</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$private suspend fun handleIncomingRpcRequest( callerIdentity: Identity, requestId: String, method: String, payload: String, responseTimeout: Duration, version: Int, )</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$private suspend fun setTrackEnabled( source: Track.Source, enabled: Boolean, screenCaptureParams: ScreenCaptureParams? = null, ): Boolean</ID>
<ID>LongMethod:LocalParticipant.kt$LocalParticipant$suspend fun publishVideoTrack( track: LocalVideoTrack, options: VideoTrackPublishOptions = VideoTrackPublishOptions( null, if (track.options.isScreencast) screenShareTrackPublishDefaults else videoTrackPublishDefaults, ), publishListener: PublishListener? = null, ): Boolean</ID>
<ID>LongMethod:PeerConnectionTransport.kt$PeerConnectionTransport$private suspend fun createAndSendOffer(constraints: MediaConstraints = MediaConstraints())</ID>
Expand All @@ -49,13 +48,13 @@
<ID>LongMethod:Room.kt$Room$@Synchronized private fun getOrCreateRemoteParticipant( identity: Participant.Identity, info: LivekitModels.ParticipantInfo, ): RemoteParticipant</ID>
<ID>LongMethod:Room.kt$Room$@Throws(Exception::class) suspend fun connect(url: String, token: String, options: ConnectOptions = ConnectOptions())</ID>
<ID>LongMethod:Room.kt$Room$private fun setupLocalParticipantEventHandling()</ID>
<ID>LongMethod:RpcClientManager.kt$RpcClientManager$suspend fun performRpc( destinationIdentity: Identity, method: String, payload: String, responseTimeout: Duration, ): String</ID>
<ID>LongMethod:SignalClient.kt$SignalClient$private fun handleSignalResponseImpl(ws: WebSocket, response: LivekitRtc.SignalResponse)</ID>
<ID>LongParameterList:AudioBufferCallbackDispatcher.kt$AudioBufferCallback$(buffer: ByteBuffer, audioFormat: Int, channelCount: Int, sampleRate: Int, bytesRead: Int, captureTimeNs: Long)</ID>
<ID>LongParameterList:KeyProvider.kt$BaseKeyProvider$( ratchetSalt: String = defaultRatchetSalt, uncryptedMagicBytes: String = defaultMagicBytes, ratchetWindowSize: Int = defaultRatchetWindowSize, override var enableSharedKey: Boolean = true, failureTolerance: Int = defaultFailureTolerance, keyRingSize: Int = defaultKeyRingSize, discardFrameWhenCryptorNotReady: Boolean = defaultDiscardFrameWhenCryptorNotReady, keyDerivationAlgorithm: FrameCryptorKeyDerivationAlgorithm = defaultKeyDerivationAlgorithm, )</ID>
<ID>LongParameterList:LiveKitOverrides.kt$AudioOptions$( /** * Override the default output [AudioType]. * * This affects the audio routing and how the audio is handled. Default is [AudioType.CallAudioType]. * * Note: if [audioHandler] is also passed, the values from [audioOutputType] will not be reflected in it, * and must be set yourself. */ val audioOutputType: AudioType? = null, /** * Override the default [AudioHandler]. * * Default is [AudioSwitchHandler]. * * Use [NoAudioHandler] to turn off automatic audio handling or * [AudioFocusHandler] to get simple audio focus handling. */ val audioHandler: AudioHandler? = null, /** * Override the default [AudioDeviceModule]. * * If a non-null value is passed, the library does not * take ownership of the object and will not release it upon [Room.release]. * It is the responsibility of the owner to call [AudioDeviceModule.release] when finished * with it to prevent memory leaks. */ val audioDeviceModule: AudioDeviceModule? = null, /** * Called after default setup to allow for customizations on the [JavaAudioDeviceModule]. * * Not used if [audioDeviceModule] is provided. * * Note: We require setting the [JavaAudioDeviceModule.Builder.setSamplesReadyCallback] to provide * support for [LocalAudioTrack.addSink]. If you wish to grab the audio samples * from the local microphone track, use [LocalAudioTrack.addSink] instead of setting your own * callback. */ val javaAudioDeviceModuleCustomizer: ((builder: JavaAudioDeviceModule.Builder) -> Unit)? = null, /** * On Android 11+, the audio mode will reset itself from [AudioManager.MODE_IN_COMMUNICATION] if * there is no audio playback or capture for 6 seconds (for example when joining a room with * no speakers and the local mic is muted.) This mode reset will cause unexpected * behavior when trying to change the volume, causing it to not properly change the volume. * * We use a workaround by playing a silent audio track to keep the communication mode from * resetting. * * Setting this flag to true will disable the workaround. * * This flag is a no-op when the audio mode is set to anything other than * [AudioManager.MODE_IN_COMMUNICATION]. */ val disableCommunicationModeWorkaround: Boolean = false, /** * Options for processing the mic and incoming audio. */ val audioProcessorOptions: AudioProcessorOptions? = null, /** * Devices may take some time initializing the audio stack for recording. * Prewarming allows starting up the underlying audio recording prior to publish, letting * the audio device be ready immediately when the track is fully published. * * If set to true, disables audio recording prewarming (and the related * [LocalAudioTrack.prewarm] function), and audio resources are only used while the * track is connected and published. Defaults to false. */ val disableAudioPrewarming: Boolean = false, )</ID>
<ID>LongParameterList:LocalAudioTrack.kt$LocalAudioTrack$( @Assisted name: String, @Assisted mediaTrack: livekit.org.webrtc.AudioTrack, @Assisted private val options: LocalAudioTrackOptions, private val audioProcessingController: AudioProcessingController, @Named(InjectionNames.DISPATCHER_DEFAULT) private val dispatcher: CoroutineDispatcher, @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER) private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher, @Named(InjectionNames.LOCAL_AUDIO_BUFFER_CALLBACK_DISPATCHER) private val audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher, private val audioRecordPrewarmer: AudioRecordPrewarmer, rtcThreadToken: RTCThreadToken, )</ID>
<ID>LongParameterList:LocalParticipant.kt$LocalParticipant$( @Assisted internal var dynacast: Boolean, internal val engine: RTCEngine, private val peerConnectionFactory: PeerConnectionFactory, private val context: Context, private val eglBase: EglBase, private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory, private val videoTrackFactory: LocalVideoTrack.Factory, private val audioTrackFactory: LocalAudioTrack.Factory, private val defaultsManager: DefaultsManager, @Named(InjectionNames.DISPATCHER_DEFAULT) coroutineDispatcher: CoroutineDispatcher, @Named(InjectionNames.SENDER) private val capabilitiesGetter: CapabilitiesGetter, private val outgoingDataStreamManager: OutgoingDataStreamManager, )</ID>
<ID>LongParameterList:LocalParticipant.kt$LocalParticipant$( callerIdentity: Identity, requestId: String, method: String, payload: String, responseTimeout: Duration, version: Int, )</ID>
<ID>LongParameterList:LocalParticipant.kt$LocalParticipant$( @Assisted internal var dynacast: Boolean, internal val engine: RTCEngine, private val peerConnectionFactory: PeerConnectionFactory, private val context: Context, private val eglBase: EglBase, private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory, private val videoTrackFactory: LocalVideoTrack.Factory, private val audioTrackFactory: LocalAudioTrack.Factory, private val defaultsManager: DefaultsManager, @Named(InjectionNames.DISPATCHER_DEFAULT) coroutineDispatcher: CoroutineDispatcher, @Named(InjectionNames.SENDER) private val capabilitiesGetter: CapabilitiesGetter, private val outgoingDataStreamManager: OutgoingDataStreamManager, private val rpcClientManager: io.livekit.android.room.rpc.RpcClientManager, private val rpcServerManager: io.livekit.android.room.rpc.RpcServerManager, )</ID>
<ID>LongParameterList:LocalScreencastVideoTrack.kt$LocalScreencastVideoTrack$( @Assisted capturer: VideoCapturer, @Assisted source: VideoSource, @Assisted name: String, @Assisted options: LocalVideoTrackOptions, @Assisted rtcTrack: livekit.org.webrtc.VideoTrack, @Assisted mediaProjectionCallback: MediaProjectionCallback, peerConnectionFactory: PeerConnectionFactory, context: Context, eglBase: EglBase, defaultsManager: DefaultsManager, videoTrackFactory: LocalVideoTrack.Factory, rtcThreadToken: RTCThreadToken, )</ID>
<ID>LongParameterList:LocalScreencastVideoTrack.kt$LocalScreencastVideoTrack.Companion$( mediaProjectionPermissionResultData: Intent, peerConnectionFactory: PeerConnectionFactory, context: Context, name: String, options: LocalVideoTrackOptions, rootEglBase: EglBase, screencastVideoTrackFactory: Factory, videoProcessor: VideoProcessor?, onStop: (Track) -> Unit, )</ID>
<ID>LongParameterList:LocalScreencastVideoTrack.kt$LocalScreencastVideoTrack.Factory$( capturer: VideoCapturer, source: VideoSource, name: String, options: LocalVideoTrackOptions, rtcTrack: livekit.org.webrtc.VideoTrack, mediaProjectionCallback: MediaProjectionCallback, )</ID>
Expand All @@ -71,7 +70,7 @@
<ID>LongParameterList:RTCStatsExt.kt$( trackIdentifier: String, ssrcs: Set&lt;Long?>, codecIds: Set&lt;String?>, localCandidateId: String?, remoteCandidateId: String?, statsMap: Map&lt;String, RTCStats>, )</ID>
<ID>LongParameterList:RemoteParticipant.kt$RemoteParticipant$( mediaTrack: MediaStreamTrack, sid: String, statsGetter: RTCStatsGetter, receiver: RtpReceiver, autoManageVideo: Boolean = false, triesLeft: Int = 20, )</ID>
<ID>LongParameterList:RemoteParticipant.kt$RemoteParticipant$( sid: Sid, identity: Identity? = null, internal val signalClient: SignalClient, private val ioDispatcher: CoroutineDispatcher, defaultDispatcher: CoroutineDispatcher, private val audioTrackFactory: RemoteAudioTrack.Factory, private val videoTrackFactory: RemoteVideoTrack.Factory, )</ID>
<ID>LongParameterList:Room.kt$Room$( @Assisted private val context: Context, internal val engine: RTCEngine, private val eglBase: EglBase, localParticipantFactory: LocalParticipant.Factory, private val defaultsManager: DefaultsManager, @Named(InjectionNames.DISPATCHER_DEFAULT) private val defaultDispatcher: CoroutineDispatcher, @Named(InjectionNames.DISPATCHER_IO) private val ioDispatcher: CoroutineDispatcher, /** * The [AudioHandler] for setting up the audio as need. * * By default, this is an instance of [AudioSwitchHandler]. * * This can be substituted for your own custom implementation through * [LiveKitOverrides.audioOptions] when creating the room with [LiveKit.create]. * * @see [audioSwitchHandler] * @see [AudioSwitchHandler] */ val audioHandler: AudioHandler, private val closeableManager: CloseableManager, private val e2EEManagerFactory: E2EEManager.Factory, private val communicationWorkaround: CommunicationWorkaround, val audioProcessingController: AudioProcessingController, /** * A holder for objects that are used internally within LiveKit. */ val lkObjects: LKObjects, networkCallbackManagerFactory: NetworkCallbackManagerFactory, private val audioDeviceModule: AudioDeviceModule, private val regionUrlProviderFactory: RegionUrlProvider.Factory, private val connectionWarmer: ConnectionWarmer, private val audioRecordPrewarmer: AudioRecordPrewarmer, private val incomingDataStreamManager: IncomingDataStreamManager, private val remoteParticipantFactory: RemoteParticipant.Factory, )</ID>
<ID>LongParameterList:Room.kt$Room$( @Assisted private val context: Context, internal val engine: RTCEngine, private val eglBase: EglBase, localParticipantFactory: LocalParticipant.Factory, private val defaultsManager: DefaultsManager, @Named(InjectionNames.DISPATCHER_DEFAULT) private val defaultDispatcher: CoroutineDispatcher, @Named(InjectionNames.DISPATCHER_IO) private val ioDispatcher: CoroutineDispatcher, /** * The [AudioHandler] for setting up the audio as need. * * By default, this is an instance of [AudioSwitchHandler]. * * This can be substituted for your own custom implementation through * [LiveKitOverrides.audioOptions] when creating the room with [LiveKit.create]. * * @see [audioSwitchHandler] * @see [AudioSwitchHandler] */ val audioHandler: AudioHandler, private val closeableManager: CloseableManager, private val e2EEManagerFactory: E2EEManager.Factory, private val communicationWorkaround: CommunicationWorkaround, val audioProcessingController: AudioProcessingController, /** * A holder for objects that are used internally within LiveKit. */ val lkObjects: LKObjects, networkCallbackManagerFactory: NetworkCallbackManagerFactory, private val audioDeviceModule: AudioDeviceModule, private val regionUrlProviderFactory: RegionUrlProvider.Factory, private val connectionWarmer: ConnectionWarmer, private val audioRecordPrewarmer: AudioRecordPrewarmer, private val incomingDataStreamManager: IncomingDataStreamManager, private val rpcClientManager: RpcClientManager, private val rpcServerManager: RpcServerManager, private val remoteParticipantFactory: RemoteParticipant.Factory, )</ID>
<ID>MapGetWithNotNullAssertionOperator:LocalParticipant.kt$LocalParticipant$sourcePubLocks[source]!!</ID>
<ID>NestedBlockDepth:ByteStreamSender.kt$@CheckResult suspend fun ByteStreamSender.write(source: Source): Result&lt;Unit></ID>
<ID>NestedBlockDepth:LocalParticipant.kt$LocalParticipant$@Throws(TrackException.PublishException::class) private suspend fun publishTrackImpl( track: Track, options: TrackPublishOptions, requestConfig: AddTrackRequest.Builder.() -> Unit, encodings: List&lt;RtpParameters.Encoding> = emptyList(), publishListener: PublishListener? = null, ): LocalTrackPublication?</ID>
Expand Down Expand Up @@ -103,6 +102,7 @@
<ID>TooManyFunctions:RTCMetricsManager.kt$io.livekit.android.room.metrics.RTCMetricsManager.kt</ID>
<ID>TooManyFunctions:RTCModule.kt$RTCModule</ID>
<ID>TooManyFunctions:Room.kt$Room : ListenerParticipantListenerRpcManagerIncomingDataStreamManager</ID>
<ID>TooManyFunctions:RpcServerManager.kt$RpcServerManager</ID>
<ID>TooManyFunctions:SignalClient.kt$SignalClient : WebSocketListener</ID>
<ID>TooManyFunctions:SignalClient.kt$SignalClient$Listener</ID>
<ID>TooManyFunctions:SimulcastVideoEncoderFactoryWrapper.kt$SimulcastVideoEncoderFactoryWrapper$StreamEncoderWrapper : VideoEncoder</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 LiveKit, Inc.
* Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package io.livekit.android

import io.livekit.android.room.ClientProtocolVersion
import io.livekit.android.room.ProtocolVersion
import io.livekit.android.room.Room
import livekit.org.webrtc.PeerConnection
Expand Down Expand Up @@ -53,6 +54,13 @@ data class ConnectOptions(
* the protocol version to use with the server.
*/
val protocolVersion: ProtocolVersion = ProtocolVersion.v13,

/**
* The client protocol version to advertise to other participants in the room
* for peer-to-peer feature negotiation (RPC v2, etc.). Defaults to the latest
* version supported by this SDK build.
*/
val clientProtocol: ClientProtocolVersion = ClientProtocolVersion.DATA_STREAM_RPC,
) {
internal var reconnect: Boolean = false
internal var participantSid: String? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 LiveKit, Inc.
* Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -328,6 +328,7 @@ fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
LivekitModels.DisconnectReason.SIP_TRUNK_FAILURE -> DisconnectReason.SIP_TRUNK_FAILURE
LivekitModels.DisconnectReason.CONNECTION_TIMEOUT -> DisconnectReason.CONNECTION_TIMEOUT
LivekitModels.DisconnectReason.MEDIA_FAILURE -> DisconnectReason.MEDIA_FAILURE
LivekitModels.DisconnectReason.AGENT_ERROR,
LivekitModels.DisconnectReason.UNKNOWN_REASON,
LivekitModels.DisconnectReason.UNRECOGNIZED,
null,
Expand Down
Loading