From bbd712124c5ee7fbca4b2d129c27c6f9e76be81e Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 16:15:56 +0200 Subject: [PATCH 1/7] Remove dagger directory --- .../rtc/InternalMembraneRTC.kt | 1 - .../org/membraneframework/rtc/MembraneRTC.kt | 1 - .../rtc/{dagger => }/RTCModule.kt | 2 +- .../rtc/dagger/MembraneRTCComponent.kt | 18 ------------------ 4 files changed, 1 insertion(+), 21 deletions(-) rename JellyfishClient/src/main/java/org/membraneframework/rtc/{dagger => }/RTCModule.kt (98%) delete mode 100644 JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/MembraneRTCComponent.kt diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt index d90e4d5..c5089f3 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt @@ -6,7 +6,6 @@ import com.jellyfishdev.jellyfishclient.BuildConfig import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.membraneframework.rtc.dagger.RTCModule import org.membraneframework.rtc.events.OfferData import org.membraneframework.rtc.media.* import org.membraneframework.rtc.models.EncodingReason diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/MembraneRTC.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/MembraneRTC.kt index 4846e72..50262ea 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/MembraneRTC.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/MembraneRTC.kt @@ -3,7 +3,6 @@ package org.membraneframework.rtc import android.content.Context import android.content.Intent import kotlinx.coroutines.Dispatchers -import org.membraneframework.rtc.dagger.RTCModule import org.membraneframework.rtc.media.* import org.membraneframework.rtc.models.RTCStats import org.membraneframework.rtc.utils.Metadata diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/RTCModule.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt similarity index 98% rename from JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/RTCModule.kt rename to JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt index a5afbfa..b395b8f 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/RTCModule.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt @@ -1,4 +1,4 @@ -package org.membraneframework.rtc.dagger +package org.membraneframework.rtc import android.content.Context import org.webrtc.* diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/MembraneRTCComponent.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/MembraneRTCComponent.kt deleted file mode 100644 index 2627cbf..0000000 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/dagger/MembraneRTCComponent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.membraneframework.rtc.dagger - -import android.content.Context -import org.membraneframework.rtc.InternalMembraneRTC -import org.webrtc.EglBase -import org.webrtc.audio.AudioDeviceModule - -internal interface MembraneRTCComponent { - fun membraneRTCFactory(): InternalMembraneRTC - - fun eglBase(): EglBase - - fun audioDeviceModule(): AudioDeviceModule - - interface Factory { - fun create(appContext: Context): MembraneRTCComponent - } -} From 728649dff7e411f69bde98ec83ed554be07a69b8 Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 16:22:42 +0200 Subject: [PATCH 2/7] Remove org.webrtc.* where possible --- .../java/org/membraneframework/rtc/InternalMembraneRTC.kt | 4 +++- .../membraneframework/rtc/PeerConnectionFactoryWrapper.kt | 5 ++++- .../src/main/java/org/membraneframework/rtc/RTCModule.kt | 2 +- .../rtc/test/EndpointConnectionManagerTest.kt | 6 +++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt index c5089f3..f744ad8 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt @@ -18,8 +18,10 @@ import org.membraneframework.rtc.utils.ClosableCoroutineScope import org.membraneframework.rtc.utils.Metadata import org.membraneframework.rtc.utils.SerializedMediaEvent import org.membraneframework.rtc.utils.TimberDebugTree -import org.webrtc.* import org.webrtc.AudioTrack +import org.webrtc.EglBase +import org.webrtc.IceCandidate +import org.webrtc.MediaStreamTrack import org.webrtc.VideoTrack import timber.log.Timber import java.util.* diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt index e5ee6b7..dfe828a 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt @@ -2,7 +2,10 @@ package org.membraneframework.rtc import android.content.Context import org.membraneframework.rtc.media.SimulcastVideoEncoderFactoryWrapper -import org.webrtc.* +import org.webrtc.DefaultVideoDecoderFactory +import org.webrtc.EglBase +import org.webrtc.PeerConnection +import org.webrtc.PeerConnectionFactory import org.webrtc.audio.AudioDeviceModule internal class PeerConnectionFactoryWrapper diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt index b395b8f..1c8e0c7 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCModule.kt @@ -1,7 +1,7 @@ package org.membraneframework.rtc import android.content.Context -import org.webrtc.* +import org.webrtc.EglBase import org.webrtc.audio.AudioDeviceModule import org.webrtc.audio.JavaAudioDeviceModule import timber.log.Timber diff --git a/JellyfishClient/src/test/java/org/membraneframework/rtc/test/EndpointConnectionManagerTest.kt b/JellyfishClient/src/test/java/org/membraneframework/rtc/test/EndpointConnectionManagerTest.kt index ed55453..c3cc5f3 100644 --- a/JellyfishClient/src/test/java/org/membraneframework/rtc/test/EndpointConnectionManagerTest.kt +++ b/JellyfishClient/src/test/java/org/membraneframework/rtc/test/EndpointConnectionManagerTest.kt @@ -16,8 +16,12 @@ import org.membraneframework.rtc.utils.addTransceiver import org.membraneframework.rtc.utils.createOffer import org.membraneframework.rtc.utils.getEncodings import org.membraneframework.rtc.utils.setLocalDescription -import org.webrtc.* +import org.webrtc.MediaConstraints +import org.webrtc.PeerConnection import org.webrtc.RtpParameters.Encoding +import org.webrtc.RtpTransceiver +import org.webrtc.SessionDescription +import org.webrtc.VideoTrack class EndpointConnectionManagerTest { private lateinit var manager: PeerConnectionManager From b5e4893dba305dfdd3004d5e0609301258b9a567 Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 16:30:14 +0200 Subject: [PATCH 3/7] Run ktlint --format --- .editorconfig | 7 ++ .../jellyfishclient/JellyfishClient.kt | 35 +++++-- .../JellyfishClientInternal.kt | 92 +++++++++++-------- .../JellyfishClientListener.kt | 15 ++- .../jellyfishclient/JellyfishTrackContext.kt | 12 ++- .../src/main/java/jellyfish/PeerMessageKt.kt | 54 ++++++++--- .../jellyfishclient/JellyfishClientTest.kt | 38 ++++---- .../jellyfishandroidexample/MainActivity.kt | 59 ++++++------ .../jellyfishandroidexample/Participant.kt | 2 +- .../ParticipantVideoView.kt | 15 ++- .../jellyfishandroidexample/RoomActivity.kt | 59 ++++++------ .../jellyfishandroidexample/RoomViewModel.kt | 69 ++++++++------ .../jellyfishandroidexample/ui/theme/Theme.kt | 46 +++++----- .../jellyfishandroidexample/ui/theme/Type.kt | 24 ++--- 14 files changed, 322 insertions(+), 205 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..192ddef --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.{kt,kts}] +ktlint_standard_no-wildcard-imports = disbaled +ktlint_standard_filename = disabled +ktlint_standard_function-naming = disabled +ktlint_standard_property-naming = disabled +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false \ No newline at end of file diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt index 5b1df05..7c3a1ef 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt @@ -16,7 +16,7 @@ typealias TrackContext = JellyfishTrackContext data class Config( val websocketUrl: String, - val token: String, + val token: String ) class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { @@ -73,7 +73,7 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { fun createVideoTrack( videoParameters: VideoParameters, metadata: Metadata, - captureDeviceName: String? = null, + captureDeviceName: String? = null ): LocalVideoTrack { return client.webrtcClient.createVideoTrack(videoParameters, metadata, captureDeviceName) } @@ -105,13 +105,13 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { mediaProjectionPermission: Intent, videoParameters: VideoParameters, metadata: Metadata, - onEnd: (() -> Unit)? = null, + onEnd: (() -> Unit)? = null ): LocalScreencastTrack? { return client.webrtcClient.createScreencastTrack( mediaProjectionPermission, videoParameters, metadata, - onEnd, + onEnd ) } @@ -135,7 +135,10 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { * @param trackId an id of a remote track * @param encoding an encoding to receive */ - fun setTargetTrackEncoding(trackId: String, encoding: TrackEncoding) { + fun setTargetTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { client.webrtcClient.setTargetTrackEncoding(trackId, encoding) } @@ -145,7 +148,10 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { * @param trackId an id of a local track * @param encoding an encoding that will be enabled */ - fun enableTrackEncoding(trackId: String, encoding: TrackEncoding) { + fun enableTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { client.webrtcClient.enableTrackEncoding(trackId, encoding) } @@ -155,7 +161,10 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { * @param trackId and id of a local track * @param encoding an encoding that will be disabled */ - fun disableTrackEncoding(trackId: String, encoding: TrackEncoding) { + fun disableTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { client.webrtcClient.disableTrackEncoding(trackId, encoding) } @@ -178,7 +187,10 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { * If the metadata is different from what is already tracked in the room, the optional * callback `onTrackUpdated` will be triggered for other peers in the room. */ - fun updateTrackMetadata(trackId: String, trackMetadata: Metadata) { + fun updateTrackMetadata( + trackId: String, + trackMetadata: Metadata + ) { client.webrtcClient.updateTrackMetadata(trackId, trackMetadata) } @@ -189,7 +201,10 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { * @param trackId track id of a video track * @param bandwidthLimit bandwidth in kbps */ - fun setTrackBandwidth(trackId: String, bandwidthLimit: TrackBandwidthLimit.BandwidthLimit) { + fun setTrackBandwidth( + trackId: String, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit + ) { client.webrtcClient.setTrackBandwidth(trackId, bandwidthLimit) } @@ -202,7 +217,7 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { fun setEncodingBandwidth( trackId: String, encoding: String, - bandwidthLimit: TrackBandwidthLimit.BandwidthLimit, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit ) { client.webrtcClient.setEncodingBandwidth(trackId, encoding, bandwidthLimit) } diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt index f462dc1..0e62db2 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt @@ -22,7 +22,7 @@ typealias Peer = Endpoint internal class JellyfishClientInternal( appContext: Context, - private val listener: JellyfishClientListener, + private val listener: JellyfishClientListener ) : MembraneRTCListener { private var webSocket: WebSocket? = null @@ -36,42 +36,58 @@ internal class JellyfishClientInternal( fun connect(config: Config) { val request = Request.Builder().url(config.websocketUrl).build() - val webSocket = OkHttpClient().newWebSocket( - request, - object : WebSocketListener() { - override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - listener.onSocketClose(code, reason) - } + val webSocket = + OkHttpClient().newWebSocket( + request, + object : WebSocketListener() { + override fun onClosed( + webSocket: WebSocket, + code: Int, + reason: String + ) { + listener.onSocketClose(code, reason) + } - override fun onMessage(webSocket: WebSocket, bytes: ByteString) { - try { - val peerMessage = PeerMessage.parseFrom(bytes.toByteArray()) - if (peerMessage.hasAuthenticated()) { - listener.onAuthSuccess() - } else if (peerMessage.hasMediaEvent()) { - receiveEvent(peerMessage.mediaEvent.data) - } else { - Timber.w("Received unexpected websocket message: $peerMessage") + override fun onMessage( + webSocket: WebSocket, + bytes: ByteString + ) { + try { + val peerMessage = PeerMessage.parseFrom(bytes.toByteArray()) + if (peerMessage.hasAuthenticated()) { + listener.onAuthSuccess() + } else if (peerMessage.hasMediaEvent()) { + receiveEvent(peerMessage.mediaEvent.data) + } else { + Timber.w("Received unexpected websocket message: $peerMessage") + } + } catch (e: Exception) { + Timber.e("Received invalid websocket message", e) } - } catch (e: Exception) { - Timber.e("Received invalid websocket message", e) } - } - override fun onOpen(webSocket: WebSocket, response: Response) { - listener.onSocketOpen() - val authRequest = PeerMessage - .newBuilder() - .setAuthRequest(PeerMessage.AuthRequest.newBuilder().setToken(config.token)) - .build() - sendEvent(authRequest) - } + override fun onOpen( + webSocket: WebSocket, + response: Response + ) { + listener.onSocketOpen() + val authRequest = + PeerMessage + .newBuilder() + .setAuthRequest(PeerMessage.AuthRequest.newBuilder().setToken(config.token)) + .build() + sendEvent(authRequest) + } - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - listener.onSocketError(t, response) + override fun onFailure( + webSocket: WebSocket, + t: Throwable, + response: Response? + ) { + listener.onSocketError(t, response) + } } - }, - ) + ) this.webSocket = webSocket } @@ -108,10 +124,11 @@ internal class JellyfishClientInternal( } override fun onSendMediaEvent(event: SerializedMediaEvent) { - val mediaEvent = PeerMessage - .newBuilder() - .setMediaEvent(MediaEvent.newBuilder().setData(event)) - .build() + val mediaEvent = + PeerMessage + .newBuilder() + .setMediaEvent(MediaEvent.newBuilder().setData(event)) + .build() sendEvent(mediaEvent) } @@ -143,7 +160,10 @@ internal class JellyfishClientInternal( listener.onJoinError(metadata) } - override fun onConnected(peerID: String, peersInRoom: List) { + override fun onConnected( + peerID: String, + peersInRoom: List + ) { listener.onJoined(peerID, peersInRoom) } diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientListener.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientListener.kt index 9b5551a..894fdbb 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientListener.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientListener.kt @@ -7,14 +7,20 @@ interface JellyfishClientListener { /** * Emitted when the websocket connection is closed */ - fun onSocketClose(code: Int, reason: String) { + fun onSocketClose( + code: Int, + reason: String + ) { Timber.i("Socket was closed with code $code reason: $reason") } /** * Emitted when occurs an error in the websocket connection */ - fun onSocketError(t: Throwable, response: Response?) { + fun onSocketError( + t: Throwable, + response: Response? + ) { Timber.w("Socket error:", t, response) } @@ -38,7 +44,10 @@ interface JellyfishClientListener { /** * Emitted when local user is connected to jellyfish. */ - fun onJoined(peerID: String, peersInRoom: List) + fun onJoined( + peerID: String, + peersInRoom: List + ) /** * Emitted when there was an error while connecting to the jellyfish. diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishTrackContext.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishTrackContext.kt index cf4db18..a9b5bde 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishTrackContext.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishTrackContext.kt @@ -54,12 +54,20 @@ class JellyfishTrackContext(private val trackContext: TrackContext) { } fun setOnEncodingChangedListener(listener: JellyfishOnEncodingChangedListener) { - val rtcListener = OnEncodingChangedListener { trackContext -> listener.onEncodingChangedListener(JellyfishTrackContext(trackContext)) } + val rtcListener = + OnEncodingChangedListener { + trackContext -> + listener.onEncodingChangedListener(JellyfishTrackContext(trackContext)) + } trackContext.setOnEncodingChangedListener(rtcListener) } fun setOnVoiceActivityChangedListener(listener: JellyfishOnVoiceActivityChangedListener) { - val rtcListener = OnVoiceActivityChangedListener { trackContext -> listener.onVoiceActivityChanged(JellyfishTrackContext(trackContext)) } + val rtcListener = + OnVoiceActivityChangedListener { + trackContext -> + listener.onVoiceActivityChanged(JellyfishTrackContext(trackContext)) + } trackContext.setOnVoiceActivityChangedListener(rtcListener) } } diff --git a/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt b/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt index 11db2d2..c6652db 100644 --- a/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt +++ b/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt @@ -6,11 +6,12 @@ package jellyfish @kotlin.jvm.JvmName("-initializepeerMessage") public inline fun peerMessage(block: jellyfish.PeerMessageKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage = jellyfish.PeerMessageKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.newBuilder()).apply { block() }._build() + public object PeerMessageKt { @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.Builder, + private val _builder: jellyfish.PeerNotifications.PeerMessage.Builder ) { public companion object { @kotlin.jvm.JvmSynthetic @@ -102,6 +103,7 @@ public object PeerMessageKt { public fun hasMediaEvent(): kotlin.Boolean { return _builder.hasMediaEvent() } + public val contentCase: jellyfish.PeerNotifications.PeerMessage.ContentCase @JvmName("getContentCase") get() = _builder.getContentCase() @@ -112,13 +114,18 @@ public object PeerMessageKt { } @kotlin.jvm.JvmName("-initializeauthenticated") - public inline fun authenticated(block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.Authenticated = - jellyfish.PeerMessageKt.AuthenticatedKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.Authenticated.newBuilder()).apply { block() }._build() + public inline fun authenticated( + block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit + ): jellyfish.PeerNotifications.PeerMessage.Authenticated = + jellyfish.PeerMessageKt.AuthenticatedKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.Authenticated.newBuilder()).apply { + block() + }._build() + public object AuthenticatedKt { @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.Authenticated.Builder, + private val _builder: jellyfish.PeerNotifications.PeerMessage.Authenticated.Builder ) { public companion object { @kotlin.jvm.JvmSynthetic @@ -133,13 +140,18 @@ public object PeerMessageKt { } @kotlin.jvm.JvmName("-initializeauthRequest") - public inline fun authRequest(block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.AuthRequest = - jellyfish.PeerMessageKt.AuthRequestKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.AuthRequest.newBuilder()).apply { block() }._build() + public inline fun authRequest( + block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit + ): jellyfish.PeerNotifications.PeerMessage.AuthRequest = + jellyfish.PeerMessageKt.AuthRequestKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.AuthRequest.newBuilder()).apply { + block() + }._build() + public object AuthRequestKt { @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.AuthRequest.Builder, + private val _builder: jellyfish.PeerNotifications.PeerMessage.AuthRequest.Builder ) { public companion object { @kotlin.jvm.JvmSynthetic @@ -173,13 +185,18 @@ public object PeerMessageKt { } @kotlin.jvm.JvmName("-initializemediaEvent") - public inline fun mediaEvent(block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.MediaEvent = - jellyfish.PeerMessageKt.MediaEventKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.MediaEvent.newBuilder()).apply { block() }._build() + public inline fun mediaEvent( + block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit + ): jellyfish.PeerNotifications.PeerMessage.MediaEvent = + jellyfish.PeerMessageKt.MediaEventKt.Dsl._create( + jellyfish.PeerNotifications.PeerMessage.MediaEvent.newBuilder() + ).apply { block() }._build() + public object MediaEventKt { @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.MediaEvent.Builder, + private val _builder: jellyfish.PeerNotifications.PeerMessage.MediaEvent.Builder ) { public companion object { @kotlin.jvm.JvmSynthetic @@ -214,19 +231,26 @@ public object PeerMessageKt { } @kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.copy(block: jellyfish.PeerMessageKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage = - jellyfish.PeerMessageKt.Dsl._create(this.toBuilder()).apply { block() }._build() +public inline fun jellyfish.PeerNotifications.PeerMessage.copy( + block: jellyfish.PeerMessageKt.Dsl.() -> kotlin.Unit +): jellyfish.PeerNotifications.PeerMessage = jellyfish.PeerMessageKt.Dsl._create(this.toBuilder()).apply { block() }._build() @kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.Authenticated.copy(block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.Authenticated = +public inline fun jellyfish.PeerNotifications.PeerMessage.Authenticated.copy( + block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit +): jellyfish.PeerNotifications.PeerMessage.Authenticated = jellyfish.PeerMessageKt.AuthenticatedKt.Dsl._create(this.toBuilder()).apply { block() }._build() @kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.AuthRequest.copy(block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.AuthRequest = +public inline fun jellyfish.PeerNotifications.PeerMessage.AuthRequest.copy( + block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit +): jellyfish.PeerNotifications.PeerMessage.AuthRequest = jellyfish.PeerMessageKt.AuthRequestKt.Dsl._create(this.toBuilder()).apply { block() }._build() @kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.MediaEvent.copy(block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage.MediaEvent = +public inline fun jellyfish.PeerNotifications.PeerMessage.MediaEvent.copy( + block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit +): jellyfish.PeerNotifications.PeerMessage.MediaEvent = jellyfish.PeerMessageKt.MediaEventKt.Dsl._create(this.toBuilder()).apply { block() }._build() public val jellyfish.PeerNotifications.PeerMessageOrBuilder.authenticatedOrNull: jellyfish.PeerNotifications.PeerMessage.Authenticated? diff --git a/JellyfishClient/src/test/java/jellyfishdev/jellyfishclient/JellyfishClientTest.kt b/JellyfishClient/src/test/java/jellyfishdev/jellyfishclient/JellyfishClientTest.kt index 2aad8ce..1cf0082 100644 --- a/JellyfishClient/src/test/java/jellyfishdev/jellyfishclient/JellyfishClientTest.kt +++ b/JellyfishClient/src/test/java/jellyfishdev/jellyfishclient/JellyfishClientTest.kt @@ -28,15 +28,17 @@ class JellyfishClientTest { private val url = "ws://localhost:4000/socket/peer/websocket" private val token = "auth" - private val authRequest = PeerMessage - .newBuilder() - .setAuthRequest(PeerMessage.AuthRequest.newBuilder().setToken(token)) - .build() + private val authRequest = + PeerMessage + .newBuilder() + .setAuthRequest(PeerMessage.AuthRequest.newBuilder().setToken(token)) + .build() - private val authenticated = PeerMessage - .newBuilder() - .setAuthenticated(PeerMessage.Authenticated.newBuilder()) - .build() + private val authenticated = + PeerMessage + .newBuilder() + .setAuthenticated(PeerMessage.Authenticated.newBuilder()) + .build() init { mockkObject(MembraneRTC) @@ -67,15 +69,17 @@ class JellyfishClientTest { } @Test fun receivesAndSendsMediaEvents() { - val sdpOfferMediaEvent = PeerMessage - .newBuilder() - .setMediaEvent(MediaEvent.newBuilder().setData("sdpOffer")) - .build() - - val joinMediaEvent = PeerMessage - .newBuilder() - .setMediaEvent(MediaEvent.newBuilder().setData("join")) - .build() + val sdpOfferMediaEvent = + PeerMessage + .newBuilder() + .setMediaEvent(MediaEvent.newBuilder().setData("sdpOffer")) + .build() + + val joinMediaEvent = + PeerMessage + .newBuilder() + .setMediaEvent(MediaEvent.newBuilder().setData("join")) + .build() websocketMock.sendToClient(authenticated) diff --git a/app/src/main/java/com/example/jellyfishandroidexample/MainActivity.kt b/app/src/main/java/com/example/jellyfishandroidexample/MainActivity.kt index c1cc89a..33c7340 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/MainActivity.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/MainActivity.kt @@ -48,7 +48,7 @@ class MainActivity : ComponentActivity() { Intent(this@MainActivity, RoomActivity::class.java).apply { putExtra( RoomActivity.ARGS, - RoomActivity.BundleArgs(token), + RoomActivity.BundleArgs(token) ) }.let { startActivity(it) @@ -62,19 +62,20 @@ class MainActivity : ComponentActivity() { Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, + color = MaterialTheme.colorScheme.background ) { Column( - modifier = Modifier - .fillMaxSize(), + modifier = + Modifier + .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.Center ) { OutlinedTextField( value = roomToken.value, onValueChange = { roomToken.value = it }, placeholder = { Text("Enter room token...") }, - label = { Text("Room token") }, + label = { Text("Room token") } ) ConnectWithPermissions { @@ -83,8 +84,9 @@ class MainActivity : ComponentActivity() { connect(roomToken.value.text.trim()) }, enabled = !(roomToken.value.text.isEmpty()), - modifier = Modifier - .width(200.dp), + modifier = + Modifier + .width(200.dp) ) { Text("Join room") } @@ -96,12 +98,13 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalPermissionsApi::class) @Composable fun ConnectWithPermissions(content: @Composable () -> Unit) { - val multiplePermissionsState = rememberMultiplePermissionsState( - listOf( - Manifest.permission.RECORD_AUDIO, - Manifest.permission.CAMERA, - ), - ) + val multiplePermissionsState = + rememberMultiplePermissionsState( + listOf( + Manifest.permission.RECORD_AUDIO, + Manifest.permission.CAMERA + ) + ) val alreadyRequested = remember { mutableStateOf(false) } @@ -109,21 +112,23 @@ class MainActivity : ComponentActivity() { content() } else { Column( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - val textToShow = when { - multiplePermissionsState.shouldShowRationale -> - "Application requires an access to a microphone and camera for it to work" + val textToShow = + when { + multiplePermissionsState.shouldShowRationale -> + "Application requires an access to a microphone and camera for it to work" - !multiplePermissionsState.shouldShowRationale && alreadyRequested.value -> - "You need to explicitly grant the access to the camera and microphone in system settings..." + !multiplePermissionsState.shouldShowRationale && alreadyRequested.value -> + "You need to explicitly grant the access to the camera and microphone in system settings..." - else -> - null - } + else -> + null + } Button( onClick = { @@ -142,7 +147,7 @@ class MainActivity : ComponentActivity() { } alreadyRequested.value = true - }, + } ) { Text("Request permissions") } diff --git a/app/src/main/java/com/example/jellyfishandroidexample/Participant.kt b/app/src/main/java/com/example/jellyfishandroidexample/Participant.kt index 087aebc..d5c7d32 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/Participant.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/Participant.kt @@ -4,5 +4,5 @@ import org.membraneframework.rtc.media.VideoTrack data class Participant( val id: String, - val videoTrack: VideoTrack? = null, + val videoTrack: VideoTrack? = null ) diff --git a/app/src/main/java/com/example/jellyfishandroidexample/ParticipantVideoView.kt b/app/src/main/java/com/example/jellyfishandroidexample/ParticipantVideoView.kt index 04aa3c5..9386b64 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/ParticipantVideoView.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/ParticipantVideoView.kt @@ -15,7 +15,7 @@ import org.webrtc.RendererCommon enum class VideoViewLayout { FIT, - FILL, + FILL ; internal fun toScalingType(): RendererCommon.ScalingType { @@ -27,11 +27,18 @@ enum class VideoViewLayout { } @Composable -fun ParticipantVideoView(participant: Participant, videoViewLayout: VideoViewLayout, modifier: Modifier = Modifier) { +fun ParticipantVideoView( + participant: Participant, + videoViewLayout: VideoViewLayout, + modifier: Modifier = Modifier +) { var activeVideoTrack by remember { mutableStateOf(null) } var view: VideoTextureViewRenderer? by remember { mutableStateOf(null) } - fun setupTrack(videoTrack: VideoTrack, view: VideoTextureViewRenderer) { + fun setupTrack( + videoTrack: VideoTrack, + view: VideoTextureViewRenderer + ) { if (activeVideoTrack == videoTrack) return activeVideoTrack?.removeRenderer(view) @@ -69,6 +76,6 @@ fun ParticipantVideoView(participant: Participant, videoViewLayout: VideoViewLay update = { updatedView -> setupTrack(participant.videoTrack!!, updatedView) }, - modifier = modifier, + modifier = modifier ) } diff --git a/app/src/main/java/com/example/jellyfishandroidexample/RoomActivity.kt b/app/src/main/java/com/example/jellyfishandroidexample/RoomActivity.kt index 32f264f..5866bbb 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/RoomActivity.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/RoomActivity.kt @@ -48,8 +48,9 @@ class RoomActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val (roomToken) = intent.getParcelableExtra(ARGS) - ?: throw NullPointerException("Failed to decode intent's parcelable") + val (roomToken) = + intent.getParcelableExtra(ARGS) + ?: throw NullPointerException("Failed to decode intent's parcelable") viewModel.connect(roomToken) setContent { @@ -57,7 +58,7 @@ class RoomActivity : ComponentActivity() { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, + color = MaterialTheme.colorScheme.background ) { Content() } @@ -77,22 +78,23 @@ class RoomActivity : ComponentActivity() { val participants = viewModel.participants.collectAsState() Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() ) { Box { Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(10.dp), + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(10.dp), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, + horizontalAlignment = Alignment.CenterHorizontally ) { participants.value.forEach { ParticipantCard( participant = it, videoViewLayout = VideoViewLayout.FILL, - size = Size(100f, 100f), + size = Size(100f, 100f) ) } @@ -100,8 +102,9 @@ class RoomActivity : ComponentActivity() { onClick = { finish() }, - modifier = Modifier - .width(200.dp), + modifier = + Modifier + .width(200.dp) ) { Text("Disconnect") } @@ -115,27 +118,29 @@ class RoomActivity : ComponentActivity() { participant: Participant, videoViewLayout: VideoViewLayout, size: Size, - onClick: (() -> Unit)? = null, + onClick: (() -> Unit)? = null ) { Box( - modifier = Modifier - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() }, - ) { - onClick?.invoke() - } - .clip(RoundedCornerShape(10.dp)) - .height(size.height.dp) - .width(size.width.dp), + modifier = + Modifier + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onClick?.invoke() + } + .clip(RoundedCornerShape(10.dp)) + .height(size.height.dp) + .width(size.width.dp) ) { ParticipantVideoView( participant = participant, videoViewLayout = videoViewLayout, - modifier = Modifier - .align(Alignment.Center) - .fillMaxWidth() - .fillMaxHeight(), + modifier = + Modifier + .align(Alignment.Center) + .fillMaxWidth() + .fillMaxHeight() ) } } diff --git a/app/src/main/java/com/example/jellyfishandroidexample/RoomViewModel.kt b/app/src/main/java/com/example/jellyfishandroidexample/RoomViewModel.kt index 2ba21e5..4b20fa6 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/RoomViewModel.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/RoomViewModel.kt @@ -25,16 +25,17 @@ class RoomViewModel(application: Application) : val participants = MutableStateFlow>(emptyList()) private val globalToLocalTrackId = HashMap() - private val videoSimulcastConfig = SimulcastConfig( - enabled = false, - ) + private val videoSimulcastConfig = + SimulcastConfig( + enabled = false + ) fun connect(roomToken: String) { client.connect( Config( websocketUrl = BuildConfig.JELLYFISH_SOCKET_URL, - token = roomToken, - ), + token = roomToken + ) ) setupTracks() } @@ -48,10 +49,11 @@ class RoomViewModel(application: Application) : private fun setupTracks() { var videoParameters = VideoParameters.presetHD169 - videoParameters = videoParameters.copy( - dimensions = videoParameters.dimensions, - simulcastConfig = videoSimulcastConfig, - ) + videoParameters = + videoParameters.copy( + dimensions = videoParameters.dimensions, + simulcastConfig = videoSimulcastConfig + ) localVideoTrack = client.createVideoTrack(videoParameters, emptyMap()) } @@ -62,11 +64,15 @@ class RoomViewModel(application: Application) : override fun onAuthError() {} - override fun onJoined(peerID: String, peersInRoom: List) { + override fun onJoined( + peerID: String, + peersInRoom: List + ) { peersInRoom.forEach { - mutableParticipants[it.id] = Participant( - it.id, - ) + mutableParticipants[it.id] = + Participant( + it.id + ) } emitParticipants() } @@ -75,9 +81,10 @@ class RoomViewModel(application: Application) : } override fun onPeerJoined(peer: Peer) { - mutableParticipants[peer.id] = Participant( - id = peer.id, - ) + mutableParticipants[peer.id] = + Participant( + id = peer.id + ) emitParticipants() } @@ -92,17 +99,18 @@ class RoomViewModel(application: Application) : viewModelScope.launch { val participant = mutableParticipants[ctx.peer.id] ?: return@launch - val (id, newParticipant) = when (ctx.track) { - is RemoteVideoTrack -> { - globalToLocalTrackId[ctx.trackId] = (ctx.track as RemoteVideoTrack).id() + val (id, newParticipant) = + when (ctx.track) { + is RemoteVideoTrack -> { + globalToLocalTrackId[ctx.trackId] = (ctx.track as RemoteVideoTrack).id() - val p = participant.copy(videoTrack = ctx.track as RemoteVideoTrack) - Pair(ctx.peer.id, p) - } + val p = participant.copy(videoTrack = ctx.track as RemoteVideoTrack) + Pair(ctx.peer.id, p) + } - else -> - throw IllegalArgumentException("invalid type of incoming remote track") - } + else -> + throw IllegalArgumentException("invalid type of incoming remote track") + } mutableParticipants[id] = newParticipant @@ -117,11 +125,12 @@ class RoomViewModel(application: Application) : val localTrackId = globalToLocalTrackId[ctx.trackId] val videoTrackId = participant.videoTrack?.id() - val newParticipant = if (localTrackId == videoTrackId) { - participant.copy(videoTrack = null) - } else { - throw IllegalArgumentException("track has not been found for given endpoint") - } + val newParticipant = + if (localTrackId == videoTrackId) { + participant.copy(videoTrack = null) + } else { + throw IllegalArgumentException("track has not been found for given endpoint") + } globalToLocalTrackId.remove(ctx.trackId) diff --git a/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Theme.kt b/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Theme.kt index c963b54..9e0ca8f 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Theme.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Theme.kt @@ -15,17 +15,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80, -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40, +private val DarkColorScheme = + darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 + ) +private val LightColorScheme = + lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 /* Other default colors to override background = Color(0xFFFFFBFE), surface = Color(0xFFFFFBFE), @@ -34,25 +35,26 @@ private val LightColorScheme = lightColorScheme( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ -) + */ + ) @Composable fun JellyfishAndroidExampleTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable () -> Unit, + content: @Composable () -> Unit ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } + val colorScheme = + when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } val view = LocalView.current if (!view.isInEditMode) { SideEffect { @@ -65,6 +67,6 @@ fun JellyfishAndroidExampleTheme( MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content, + content = content ) } diff --git a/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Type.kt b/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Type.kt index 04e23ef..31edb2c 100644 --- a/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Type.kt +++ b/app/src/main/java/com/example/jellyfishandroidexample/ui/theme/Type.kt @@ -7,15 +7,17 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp, - ), - /* Other default text styles to override +val Typography = + Typography( + bodyLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, @@ -30,5 +32,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ -) + */ + ) From 2167936d83f51621ea31dbd37f864b8ba4b114bf Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 16:42:01 +0200 Subject: [PATCH 4/7] Add ktlint as CI step --- .editorconfig | 3 ++- .github/workflows/build_lint_test.yaml | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 192ddef..6ad5110 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,6 @@ [*.{kt,kts}] -ktlint_standard_no-wildcard-imports = disbaled +ktlint_code_style = ktlint_official +ktlint_standard_no-wildcard-imports = disabled ktlint_standard_filename = disabled ktlint_standard_function-naming = disabled ktlint_standard_property-naming = disabled diff --git a/.github/workflows/build_lint_test.yaml b/.github/workflows/build_lint_test.yaml index b29a427..59c1a78 100644 --- a/.github/workflows/build_lint_test.yaml +++ b/.github/workflows/build_lint_test.yaml @@ -14,8 +14,11 @@ jobs: java-version: 17 distribution: zulu - - name: Lint - run: ./gradlew lintKotlin + - name: Download ktlint + run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.1.1/ktlint && chmod a+x ktlint && sudo mv ktlint /usr/local/bin/ + + - name: Run linter + run: ktlint **/*.kt - name: Build run: ./gradlew assembleRelease From cf07d5fbf01ca7b71e5816f4835372cb57e56d53 Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 16:42:24 +0200 Subject: [PATCH 5/7] Remove all unused code --- .../jellyfishclient/JellyfishClient.kt | 2 +- .../src/main/java/jellyfish/PeerMessageKt.kt | 206 ++--- .../java/jellyfish/PeerNotifications.java | 98 +- .../java/jellyfish/PeerNotificationsKt.kt | 0 .../rtc/InternalMembraneRTC.kt | 861 +++++++++-------- .../rtc/PeerConnectionFactoryWrapper.kt | 59 +- .../rtc/PeerConnectionManager.kt | 871 +++++++++--------- .../rtc/RTCEngineCommunication.kt | 219 +++-- .../rtc/media/LocalAudioTrack.kt | 2 +- .../rtc/media/LocalScreencastTrack.kt | 211 +++-- .../rtc/media/LocalVideoTrack.kt | 125 ++- .../rtc/media/RemoteAudioTrack.kt | 2 +- .../rtc/transport/PhoenixTransport.kt | 2 +- 13 files changed, 1320 insertions(+), 1338 deletions(-) delete mode 100644 JellyfishClient/src/main/java/jellyfish/PeerNotificationsKt.kt diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt index 7c3a1ef..20b423a 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClient.kt @@ -106,7 +106,7 @@ class JellyfishClient(appContext: Context, listener: JellyfishClientListener) { videoParameters: VideoParameters, metadata: Metadata, onEnd: (() -> Unit)? = null - ): LocalScreencastTrack? { + ): LocalScreencastTrack { return client.webrtcClient.createScreencastTrack( mediaProjectionPermission, videoParameters, diff --git a/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt b/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt index c6652db..d44d024 100644 --- a/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt +++ b/JellyfishClient/src/main/java/jellyfish/PeerMessageKt.kt @@ -3,32 +3,32 @@ package jellyfish -@kotlin.jvm.JvmName("-initializepeerMessage") -public inline fun peerMessage(block: jellyfish.PeerMessageKt.Dsl.() -> kotlin.Unit): jellyfish.PeerNotifications.PeerMessage = - jellyfish.PeerMessageKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.newBuilder()).apply { block() }._build() +@JvmName("-initializepeerMessage") +inline fun peerMessage(block: PeerMessageKt.Dsl.() -> Unit): PeerNotifications.PeerMessage = + PeerMessageKt.Dsl._create(PeerNotifications.PeerMessage.newBuilder()).apply { block() }._build() -public object PeerMessageKt { - @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) +object PeerMessageKt { + @OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker - public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.Builder + class Dsl private constructor( + private val _builder: PeerNotifications.PeerMessage.Builder ) { - public companion object { - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _create(builder: jellyfish.PeerNotifications.PeerMessage.Builder): Dsl = Dsl(builder) + companion object { + @JvmSynthetic + @PublishedApi + internal fun _create(builder: PeerNotifications.PeerMessage.Builder): Dsl = Dsl(builder) } - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _build(): jellyfish.PeerNotifications.PeerMessage = _builder.build() + @JvmSynthetic + @PublishedApi + internal fun _build(): PeerNotifications.PeerMessage = _builder.build() /** * .jellyfish.PeerMessage.Authenticated authenticated = 1; */ - public var authenticated: jellyfish.PeerNotifications.PeerMessage.Authenticated + var authenticated: PeerNotifications.PeerMessage.Authenticated @JvmName("getAuthenticated") - get() = _builder.getAuthenticated() + get() = _builder.authenticated @JvmName("setAuthenticated") set(value) { @@ -38,7 +38,7 @@ public object PeerMessageKt { /** * .jellyfish.PeerMessage.Authenticated authenticated = 1; */ - public fun clearAuthenticated() { + fun clearAuthenticated() { _builder.clearAuthenticated() } @@ -46,16 +46,16 @@ public object PeerMessageKt { * .jellyfish.PeerMessage.Authenticated authenticated = 1; * @return Whether the authenticated field is set. */ - public fun hasAuthenticated(): kotlin.Boolean { + fun hasAuthenticated(): Boolean { return _builder.hasAuthenticated() } /** * .jellyfish.PeerMessage.AuthRequest auth_request = 2; */ - public var authRequest: jellyfish.PeerNotifications.PeerMessage.AuthRequest + var authRequest: PeerNotifications.PeerMessage.AuthRequest @JvmName("getAuthRequest") - get() = _builder.getAuthRequest() + get() = _builder.authRequest @JvmName("setAuthRequest") set(value) { @@ -65,7 +65,7 @@ public object PeerMessageKt { /** * .jellyfish.PeerMessage.AuthRequest auth_request = 2; */ - public fun clearAuthRequest() { + fun clearAuthRequest() { _builder.clearAuthRequest() } @@ -73,16 +73,16 @@ public object PeerMessageKt { * .jellyfish.PeerMessage.AuthRequest auth_request = 2; * @return Whether the authRequest field is set. */ - public fun hasAuthRequest(): kotlin.Boolean { + fun hasAuthRequest(): Boolean { return _builder.hasAuthRequest() } /** * .jellyfish.PeerMessage.MediaEvent media_event = 3; */ - public var mediaEvent: jellyfish.PeerNotifications.PeerMessage.MediaEvent + var mediaEvent: PeerNotifications.PeerMessage.MediaEvent @JvmName("getMediaEvent") - get() = _builder.getMediaEvent() + get() = _builder.mediaEvent @JvmName("setMediaEvent") set(value) { @@ -92,7 +92,7 @@ public object PeerMessageKt { /** * .jellyfish.PeerMessage.MediaEvent media_event = 3; */ - public fun clearMediaEvent() { + fun clearMediaEvent() { _builder.clearMediaEvent() } @@ -100,73 +100,69 @@ public object PeerMessageKt { * .jellyfish.PeerMessage.MediaEvent media_event = 3; * @return Whether the mediaEvent field is set. */ - public fun hasMediaEvent(): kotlin.Boolean { + fun hasMediaEvent(): Boolean { return _builder.hasMediaEvent() } - public val contentCase: jellyfish.PeerNotifications.PeerMessage.ContentCase + val contentCase: PeerNotifications.PeerMessage.ContentCase @JvmName("getContentCase") - get() = _builder.getContentCase() + get() = _builder.contentCase - public fun clearContent() { + fun clearContent() { _builder.clearContent() } } - @kotlin.jvm.JvmName("-initializeauthenticated") - public inline fun authenticated( - block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit - ): jellyfish.PeerNotifications.PeerMessage.Authenticated = - jellyfish.PeerMessageKt.AuthenticatedKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.Authenticated.newBuilder()).apply { + @JvmName("-initializeauthenticated") + inline fun authenticated(block: AuthenticatedKt.Dsl.() -> Unit): PeerNotifications.PeerMessage.Authenticated = + AuthenticatedKt.Dsl._create(PeerNotifications.PeerMessage.Authenticated.newBuilder()).apply { block() }._build() - public object AuthenticatedKt { - @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) + object AuthenticatedKt { + @OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker - public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.Authenticated.Builder + class Dsl private constructor( + private val _builder: PeerNotifications.PeerMessage.Authenticated.Builder ) { - public companion object { - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _create(builder: jellyfish.PeerNotifications.PeerMessage.Authenticated.Builder): Dsl = Dsl(builder) + companion object { + @JvmSynthetic + @PublishedApi + internal fun _create(builder: PeerNotifications.PeerMessage.Authenticated.Builder): Dsl = Dsl(builder) } - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _build(): jellyfish.PeerNotifications.PeerMessage.Authenticated = _builder.build() + @JvmSynthetic + @PublishedApi + internal fun _build(): PeerNotifications.PeerMessage.Authenticated = _builder.build() } } - @kotlin.jvm.JvmName("-initializeauthRequest") - public inline fun authRequest( - block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit - ): jellyfish.PeerNotifications.PeerMessage.AuthRequest = - jellyfish.PeerMessageKt.AuthRequestKt.Dsl._create(jellyfish.PeerNotifications.PeerMessage.AuthRequest.newBuilder()).apply { + @JvmName("-initializeauthRequest") + inline fun authRequest(block: AuthRequestKt.Dsl.() -> Unit): PeerNotifications.PeerMessage.AuthRequest = + AuthRequestKt.Dsl._create(PeerNotifications.PeerMessage.AuthRequest.newBuilder()).apply { block() }._build() - public object AuthRequestKt { - @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) + object AuthRequestKt { + @OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker - public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.AuthRequest.Builder + class Dsl private constructor( + private val _builder: PeerNotifications.PeerMessage.AuthRequest.Builder ) { - public companion object { - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _create(builder: jellyfish.PeerNotifications.PeerMessage.AuthRequest.Builder): Dsl = Dsl(builder) + companion object { + @JvmSynthetic + @PublishedApi + internal fun _create(builder: PeerNotifications.PeerMessage.AuthRequest.Builder): Dsl = Dsl(builder) } - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _build(): jellyfish.PeerNotifications.PeerMessage.AuthRequest = _builder.build() + @JvmSynthetic + @PublishedApi + internal fun _build(): PeerNotifications.PeerMessage.AuthRequest = _builder.build() /** * string token = 1; */ - public var token: kotlin.String + var token: String @JvmName("getToken") get() = _builder.getToken() @@ -178,40 +174,38 @@ public object PeerMessageKt { /** * string token = 1; */ - public fun clearToken() { + fun clearToken() { _builder.clearToken() } } } - @kotlin.jvm.JvmName("-initializemediaEvent") - public inline fun mediaEvent( - block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit - ): jellyfish.PeerNotifications.PeerMessage.MediaEvent = - jellyfish.PeerMessageKt.MediaEventKt.Dsl._create( - jellyfish.PeerNotifications.PeerMessage.MediaEvent.newBuilder() + @JvmName("-initializemediaEvent") + inline fun mediaEvent(block: MediaEventKt.Dsl.() -> Unit): PeerNotifications.PeerMessage.MediaEvent = + MediaEventKt.Dsl._create( + PeerNotifications.PeerMessage.MediaEvent.newBuilder() ).apply { block() }._build() - public object MediaEventKt { - @kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) + object MediaEventKt { + @OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class) @com.google.protobuf.kotlin.ProtoDslMarker - public class Dsl private constructor( - private val _builder: jellyfish.PeerNotifications.PeerMessage.MediaEvent.Builder + class Dsl private constructor( + private val _builder: PeerNotifications.PeerMessage.MediaEvent.Builder ) { - public companion object { - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _create(builder: jellyfish.PeerNotifications.PeerMessage.MediaEvent.Builder): Dsl = Dsl(builder) + companion object { + @JvmSynthetic + @PublishedApi + internal fun _create(builder: PeerNotifications.PeerMessage.MediaEvent.Builder): Dsl = Dsl(builder) } - @kotlin.jvm.JvmSynthetic - @kotlin.PublishedApi - internal fun _build(): jellyfish.PeerNotifications.PeerMessage.MediaEvent = _builder.build() + @JvmSynthetic + @PublishedApi + internal fun _build(): PeerNotifications.PeerMessage.MediaEvent = _builder.build() /** * string data = 1; */ - public var data: kotlin.String + var data: String @JvmName("getData") get() = _builder.getData() @@ -223,41 +217,37 @@ public object PeerMessageKt { /** * string data = 1; */ - public fun clearData() { + fun clearData() { _builder.clearData() } } } } -@kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.copy( - block: jellyfish.PeerMessageKt.Dsl.() -> kotlin.Unit -): jellyfish.PeerNotifications.PeerMessage = jellyfish.PeerMessageKt.Dsl._create(this.toBuilder()).apply { block() }._build() - -@kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.Authenticated.copy( - block: jellyfish.PeerMessageKt.AuthenticatedKt.Dsl.() -> kotlin.Unit -): jellyfish.PeerNotifications.PeerMessage.Authenticated = - jellyfish.PeerMessageKt.AuthenticatedKt.Dsl._create(this.toBuilder()).apply { block() }._build() - -@kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.AuthRequest.copy( - block: jellyfish.PeerMessageKt.AuthRequestKt.Dsl.() -> kotlin.Unit -): jellyfish.PeerNotifications.PeerMessage.AuthRequest = - jellyfish.PeerMessageKt.AuthRequestKt.Dsl._create(this.toBuilder()).apply { block() }._build() - -@kotlin.jvm.JvmSynthetic -public inline fun jellyfish.PeerNotifications.PeerMessage.MediaEvent.copy( - block: jellyfish.PeerMessageKt.MediaEventKt.Dsl.() -> kotlin.Unit -): jellyfish.PeerNotifications.PeerMessage.MediaEvent = - jellyfish.PeerMessageKt.MediaEventKt.Dsl._create(this.toBuilder()).apply { block() }._build() - -public val jellyfish.PeerNotifications.PeerMessageOrBuilder.authenticatedOrNull: jellyfish.PeerNotifications.PeerMessage.Authenticated? +@JvmSynthetic +inline fun PeerNotifications.PeerMessage.copy(block: PeerMessageKt.Dsl.() -> Unit): PeerNotifications.PeerMessage = + PeerMessageKt.Dsl._create(this.toBuilder()).apply { block() }._build() + +@JvmSynthetic +inline fun PeerNotifications.PeerMessage.Authenticated.copy( + block: PeerMessageKt.AuthenticatedKt.Dsl.() -> Unit +): PeerNotifications.PeerMessage.Authenticated = PeerMessageKt.AuthenticatedKt.Dsl._create(this.toBuilder()).apply { block() }._build() + +@JvmSynthetic +inline fun PeerNotifications.PeerMessage.AuthRequest.copy( + block: PeerMessageKt.AuthRequestKt.Dsl.() -> Unit +): PeerNotifications.PeerMessage.AuthRequest = PeerMessageKt.AuthRequestKt.Dsl._create(this.toBuilder()).apply { block() }._build() + +@JvmSynthetic +inline fun PeerNotifications.PeerMessage.MediaEvent.copy( + block: PeerMessageKt.MediaEventKt.Dsl.() -> Unit +): PeerNotifications.PeerMessage.MediaEvent = PeerMessageKt.MediaEventKt.Dsl._create(this.toBuilder()).apply { block() }._build() + +val PeerNotifications.PeerMessageOrBuilder.authenticatedOrNull: PeerNotifications.PeerMessage.Authenticated? get() = if (hasAuthenticated()) getAuthenticated() else null -public val jellyfish.PeerNotifications.PeerMessageOrBuilder.authRequestOrNull: jellyfish.PeerNotifications.PeerMessage.AuthRequest? +val PeerNotifications.PeerMessageOrBuilder.authRequestOrNull: PeerNotifications.PeerMessage.AuthRequest? get() = if (hasAuthRequest()) getAuthRequest() else null -public val jellyfish.PeerNotifications.PeerMessageOrBuilder.mediaEventOrNull: jellyfish.PeerNotifications.PeerMessage.MediaEvent? +val PeerNotifications.PeerMessageOrBuilder.mediaEventOrNull: PeerNotifications.PeerMessage.MediaEvent? get() = if (hasMediaEvent()) getMediaEvent() else null diff --git a/JellyfishClient/src/main/java/jellyfish/PeerNotifications.java b/JellyfishClient/src/main/java/jellyfish/PeerNotifications.java index 2372746..3a30ba2 100644 --- a/JellyfishClient/src/main/java/jellyfish/PeerNotifications.java +++ b/JellyfishClient/src/main/java/jellyfish/PeerNotifications.java @@ -63,7 +63,7 @@ public interface PeerMessageOrBuilder extends */ jellyfish.PeerNotifications.PeerMessage.MediaEventOrBuilder getMediaEventOrBuilder(); - public jellyfish.PeerNotifications.PeerMessage.ContentCase getContentCase(); + jellyfish.PeerNotifications.PeerMessage.ContentCase getContentCase(); } /** * Protobuf type {@code jellyfish.PeerMessage} @@ -88,11 +88,11 @@ protected java.lang.Object newInstance( } @java.lang.Override - public final com.google.protobuf.UnknownFieldSet + public com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_descriptor; } @@ -132,11 +132,11 @@ protected java.lang.Object newInstance( } @java.lang.Override - public final com.google.protobuf.UnknownFieldSet + public com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_Authenticated_descriptor; } @@ -151,7 +151,7 @@ protected java.lang.Object newInstance( private byte memoizedIsInitialized = -1; @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; @@ -187,8 +187,7 @@ public boolean equals(final java.lang.Object obj) { } jellyfish.PeerNotifications.PeerMessage.Authenticated other = (jellyfish.PeerNotifications.PeerMessage.Authenticated) obj; - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + return getUnknownFields().equals(other.getUnknownFields()); } @java.lang.Override @@ -300,7 +299,7 @@ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:jellyfish.PeerMessage.Authenticated) jellyfish.PeerNotifications.PeerMessage.AuthenticatedOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_Authenticated_descriptor; } @@ -406,7 +405,7 @@ public Builder mergeFrom(jellyfish.PeerNotifications.PeerMessage.Authenticated o } @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { return true; } @@ -442,14 +441,14 @@ public Builder mergeFrom( return this; } @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } @@ -545,11 +544,11 @@ protected java.lang.Object newInstance( } @java.lang.Override - public final com.google.protobuf.UnknownFieldSet + public com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_AuthRequest_descriptor; } @@ -603,7 +602,7 @@ public java.lang.String getToken() { private byte memoizedIsInitialized = -1; @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; @@ -646,9 +645,10 @@ public boolean equals(final java.lang.Object obj) { jellyfish.PeerNotifications.PeerMessage.AuthRequest other = (jellyfish.PeerNotifications.PeerMessage.AuthRequest) obj; if (!getToken() - .equals(other.getToken())) return false; - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + .equals(other.getToken())) { + return false; + } + return getUnknownFields().equals(other.getUnknownFields()); } @java.lang.Override @@ -762,7 +762,7 @@ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:jellyfish.PeerMessage.AuthRequest) jellyfish.PeerNotifications.PeerMessage.AuthRequestOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_AuthRequest_descriptor; } @@ -883,7 +883,7 @@ public Builder mergeFrom(jellyfish.PeerNotifications.PeerMessage.AuthRequest oth } @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { return true; } @@ -997,14 +997,14 @@ public Builder setTokenBytes( return this; } @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } @@ -1100,11 +1100,11 @@ protected java.lang.Object newInstance( } @java.lang.Override - public final com.google.protobuf.UnknownFieldSet + public com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_MediaEvent_descriptor; } @@ -1158,7 +1158,7 @@ public java.lang.String getData() { private byte memoizedIsInitialized = -1; @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; @@ -1202,8 +1202,7 @@ public boolean equals(final java.lang.Object obj) { if (!getData() .equals(other.getData())) return false; - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + return getUnknownFields().equals(other.getUnknownFields()); } @java.lang.Override @@ -1317,7 +1316,7 @@ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:jellyfish.PeerMessage.MediaEvent) jellyfish.PeerNotifications.PeerMessage.MediaEventOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_MediaEvent_descriptor; } @@ -1438,7 +1437,7 @@ public Builder mergeFrom(jellyfish.PeerNotifications.PeerMessage.MediaEvent othe } @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { return true; } @@ -1552,14 +1551,14 @@ public Builder setDataBytes( return this; } @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } @@ -1625,7 +1624,7 @@ public enum ContentCase MEDIA_EVENT(3), CONTENT_NOT_SET(0); private final int value; - private ContentCase(int value) { + ContentCase(int value) { this.value = value; } /** @@ -1650,9 +1649,9 @@ public static ContentCase forNumber(int value) { public int getNumber() { return this.value; } - }; + } - public ContentCase + public ContentCase getContentCase() { return ContentCase.forNumber( contentCase_); @@ -1753,7 +1752,7 @@ public jellyfish.PeerNotifications.PeerMessage.MediaEventOrBuilder getMediaEvent private byte memoizedIsInitialized = -1; @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; @@ -1827,8 +1826,7 @@ public boolean equals(final java.lang.Object obj) { case 0: default: } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + return getUnknownFields().equals(other.getUnknownFields()); } @java.lang.Override @@ -1956,7 +1954,7 @@ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:jellyfish.PeerMessage) jellyfish.PeerNotifications.PeerMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor + public static com.google.protobuf.Descriptors.Descriptor getDescriptor() { return jellyfish.PeerNotifications.internal_static_jellyfish_PeerMessage_descriptor; } @@ -2114,7 +2112,7 @@ public Builder mergeFrom(jellyfish.PeerNotifications.PeerMessage other) { } @java.lang.Override - public final boolean isInitialized() { + public boolean isInitialized() { return true; } @@ -2613,14 +2611,14 @@ public jellyfish.PeerNotifications.PeerMessage.MediaEventOrBuilder getMediaEvent return mediaEventBuilder_; } @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { + public Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } @@ -2701,7 +2699,7 @@ public jellyfish.PeerNotifications.PeerMessage getDefaultInstanceForType() { getDescriptor() { return descriptor; } - private static com.google.protobuf.Descriptors.FileDescriptor + private static final com.google.protobuf.Descriptors.FileDescriptor descriptor; static { java.lang.String[] descriptorData = { diff --git a/JellyfishClient/src/main/java/jellyfish/PeerNotificationsKt.kt b/JellyfishClient/src/main/java/jellyfish/PeerNotificationsKt.kt deleted file mode 100644 index e69de29..0000000 diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt index f744ad8..71df822 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt @@ -26,370 +26,221 @@ import org.webrtc.VideoTrack import timber.log.Timber import java.util.* -internal class InternalMembraneRTC - constructor( - private val createOptions: CreateOptions, - private val listener: MembraneRTCListener, - private val defaultDispatcher: CoroutineDispatcher, - private val eglBase: EglBase, - private val context: Context - ) : RTCEngineListener, PeerConnectionListener { - private val rtcEngineCommunication = RTCEngineCommunication(this) - private val peerConnectionFactoryWrapper = - PeerConnectionFactoryWrapper(createOptions, RTCModule.audioDeviceModule(context), eglBase, context) - private val peerConnectionManager = PeerConnectionManager(this, peerConnectionFactoryWrapper) - - private var localEndpoint: Endpoint = - Endpoint(id = "", type = "webrtc", metadata = mapOf(), tracks = mapOf()) - - // mapping from endpoint's id to the endpoint himself - private val remoteEndpoints = HashMap() - - // mapping from remote track's id to its context - private val trackContexts = HashMap() - - private val localTracks = mutableListOf() - private val localTracksMutex = Mutex() - - private val coroutineScope: CoroutineScope = - ClosableCoroutineScope(SupervisorJob() + defaultDispatcher) - - init { - if (BuildConfig.DEBUG) { - Timber.plant(TimberDebugTree()) - } - } +internal class InternalMembraneRTC( + private val createOptions: CreateOptions, + private val listener: MembraneRTCListener, + private val defaultDispatcher: CoroutineDispatcher, + private val eglBase: EglBase, + private val context: Context +) : RTCEngineListener, PeerConnectionListener { + private val rtcEngineCommunication = RTCEngineCommunication(this) + private val peerConnectionFactoryWrapper = + PeerConnectionFactoryWrapper(createOptions, RTCModule.audioDeviceModule(context), eglBase, context) + private val peerConnectionManager = PeerConnectionManager(this, peerConnectionFactoryWrapper) - interface Factory { - fun create( - createOptions: CreateOptions, - listener: MembraneRTCListener, - defaultDispatcher: CoroutineDispatcher - ): InternalMembraneRTC - } + private var localEndpoint: Endpoint = + Endpoint(id = "", type = "webrtc", metadata = mapOf(), tracks = mapOf()) - fun disconnect() { - coroutineScope.launch { - rtcEngineCommunication.disconnect() - localTracksMutex.withLock { - localTracks.forEach { it.stop() } - } - peerConnectionManager.close() - } - } + // mapping from endpoint's id to the endpoint himself + private val remoteEndpoints = HashMap() - fun receiveMediaEvent(event: SerializedMediaEvent) { - rtcEngineCommunication.onEvent(event) - } + // mapping from remote track's id to its context + private val trackContexts = HashMap() - fun connect(endpointMetadata: Metadata? = mapOf()) { - coroutineScope.launch { - localEndpoint = localEndpoint.copy(metadata = endpointMetadata) - rtcEngineCommunication.connect(endpointMetadata ?: mapOf()) - } - } + private val localTracks = mutableListOf() + private val localTracksMutex = Mutex() - fun createLocalVideoTrack( - videoParameters: VideoParameters, - metadata: Metadata = mapOf(), - captureDeviceName: String? = null - ): LocalVideoTrack { - val videoTrack = - LocalVideoTrack.create( - context, - peerConnectionFactoryWrapper.peerConnectionFactory, - eglBase, - videoParameters, - captureDeviceName - ).also { - it.start() - } + private val coroutineScope: CoroutineScope = + ClosableCoroutineScope(SupervisorJob() + defaultDispatcher) - localTracks.add(videoTrack) - localEndpoint = localEndpoint.withTrack(videoTrack.id(), metadata) - - coroutineScope.launch { - peerConnectionManager.addTrack(videoTrack) - rtcEngineCommunication.renegotiateTracks() - } - - return videoTrack + init { + if (BuildConfig.DEBUG) { + Timber.plant(TimberDebugTree()) } + } - fun createLocalAudioTrack(metadata: Metadata = mapOf()): LocalAudioTrack { - val audioTrack = - LocalAudioTrack.create( - context, - peerConnectionFactoryWrapper.peerConnectionFactory - ).also { - it.start() - } - - localTracks.add(audioTrack) - localEndpoint = localEndpoint.withTrack(audioTrack.id(), metadata) + interface Factory { + fun create( + createOptions: CreateOptions, + listener: MembraneRTCListener, + defaultDispatcher: CoroutineDispatcher + ): InternalMembraneRTC + } - coroutineScope.launch { - peerConnectionManager.addTrack(audioTrack) - rtcEngineCommunication.renegotiateTracks() + fun disconnect() { + coroutineScope.launch { + rtcEngineCommunication.disconnect() + localTracksMutex.withLock { + localTracks.forEach { it.stop() } } - - return audioTrack + peerConnectionManager.close() } + } - fun setTrackBandwidth( - trackId: String, - bandwidthLimit: TrackBandwidthLimit.BandwidthLimit - ) { - coroutineScope.launch { - peerConnectionManager.setTrackBandwidth(trackId, bandwidthLimit) - } - } + fun receiveMediaEvent(event: SerializedMediaEvent) { + rtcEngineCommunication.onEvent(event) + } - fun setEncodingBandwidth( - trackId: String, - encoding: String, - bandwidthLimit: TrackBandwidthLimit.BandwidthLimit - ) { - coroutineScope.launch { - peerConnectionManager.setEncodingBandwidth(trackId, encoding, bandwidthLimit) - } + fun connect(endpointMetadata: Metadata? = mapOf()) { + coroutineScope.launch { + localEndpoint = localEndpoint.copy(metadata = endpointMetadata) + rtcEngineCommunication.connect(endpointMetadata ?: mapOf()) } + } - fun createScreencastTrack( - mediaProjectionPermission: Intent, - videoParameters: VideoParameters, - metadata: Metadata = mapOf(), - onEnd: (() -> Unit)? - ): LocalScreencastTrack { - val screencastTrack = - LocalScreencastTrack.create( - context, - peerConnectionFactoryWrapper.peerConnectionFactory, - eglBase, - mediaProjectionPermission, - videoParameters - ) { track -> - if (onEnd != null) { - onEnd() - } - } - - localTracks.add(screencastTrack) - localEndpoint = localEndpoint.withTrack(screencastTrack.id(), metadata) - - coroutineScope.launch { - screencastTrack.startForegroundService(null, null) - screencastTrack.start() + fun createLocalVideoTrack( + videoParameters: VideoParameters, + metadata: Metadata = mapOf(), + captureDeviceName: String? = null + ): LocalVideoTrack { + val videoTrack = + LocalVideoTrack.create( + context, + peerConnectionFactoryWrapper.peerConnectionFactory, + eglBase, + videoParameters, + captureDeviceName + ).also { + it.start() } - coroutineScope.launch { - peerConnectionManager.addTrack(screencastTrack) - rtcEngineCommunication.renegotiateTracks() - } + localTracks.add(videoTrack) + localEndpoint = localEndpoint.withTrack(videoTrack.id(), metadata) - return screencastTrack + coroutineScope.launch { + peerConnectionManager.addTrack(videoTrack) + rtcEngineCommunication.renegotiateTracks() } - fun removeTrack(trackId: String): Boolean { - return runBlocking(Dispatchers.Default) { - localTracksMutex.withLock { - val track = - localTracks.find { it.id() == trackId } ?: run { - Timber.e("removeTrack: Can't find track to remove") - return@runBlocking false - } - - peerConnectionManager.removeTrack(track.id()) + return videoTrack + } - localTracks.remove(track) - localEndpoint = localEndpoint.withoutTrack(trackId) - track.stop() - } - rtcEngineCommunication.renegotiateTracks() - return@runBlocking true + fun createLocalAudioTrack(metadata: Metadata = mapOf()): LocalAudioTrack { + val audioTrack = + LocalAudioTrack.create( + context, + peerConnectionFactoryWrapper.peerConnectionFactory + ).also { + it.start() } - } - fun updateEndpointMetadata(endpointMetadata: Metadata) { - coroutineScope.launch { - rtcEngineCommunication.updateEndpointMetadata(endpointMetadata) - localEndpoint = localEndpoint.copy(metadata = endpointMetadata) - } - } + localTracks.add(audioTrack) + localEndpoint = localEndpoint.withTrack(audioTrack.id(), metadata) - fun updateTrackMetadata( - trackId: String, - trackMetadata: Metadata - ) { - coroutineScope.launch { - rtcEngineCommunication.updateTrackMetadata(trackId, trackMetadata) - localEndpoint = localEndpoint.withTrack(trackId, trackMetadata) - } + coroutineScope.launch { + peerConnectionManager.addTrack(audioTrack) + rtcEngineCommunication.renegotiateTracks() } - override fun onConnected( - endpointID: String, - otherEndpoints: List - ) { - this.localEndpoint = localEndpoint.copy(id = endpointID) - listener.onConnected(endpointID, otherEndpoints) - - otherEndpoints.forEach { - this.remoteEndpoints[it.id] = it - - for ((trackId, trackData) in it.tracks) { - val context = - TrackContext( - track = null, - endpoint = it, - trackId = trackId, - metadata = trackData.metadata ?: mapOf(), - simulcastConfig = trackData.simulcastConfig - ) - - this.trackContexts[trackId] = context - - this.listener.onTrackAdded(context) - } - } - } + return audioTrack + } - override fun onSendMediaEvent(event: SerializedMediaEvent) { - listener.onSendMediaEvent(event) + fun setTrackBandwidth( + trackId: String, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit + ) { + coroutineScope.launch { + peerConnectionManager.setTrackBandwidth(trackId, bandwidthLimit) } + } - override fun onEndpointAdded(endpoint: Endpoint) { - if (endpoint.id == this.localEndpoint.id) { - return - } - - remoteEndpoints[endpoint.id] = endpoint - - listener.onEndpointAdded(endpoint) + fun setEncodingBandwidth( + trackId: String, + encoding: String, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit + ) { + coroutineScope.launch { + peerConnectionManager.setEncodingBandwidth(trackId, encoding, bandwidthLimit) } + } - override fun onEndpointRemoved(endpointId: String) { - if (endpointId == localEndpoint.id) { - listener.onDisconnected() - return - } - val endpoint = - remoteEndpoints.remove(endpointId) ?: run { - Timber.e("Failed to process EndpointLeft event: Endpoint not found: $endpointId") - return - } - - val trackIds: List = endpoint.tracks.keys.toList() - - trackIds.forEach { - trackContexts.remove(it)?.let { ctx -> - listener.onTrackRemoved(ctx) + fun createScreencastTrack( + mediaProjectionPermission: Intent, + videoParameters: VideoParameters, + metadata: Metadata = mapOf(), + onEnd: (() -> Unit)? + ): LocalScreencastTrack { + val screencastTrack = + LocalScreencastTrack.create( + context, + peerConnectionFactoryWrapper.peerConnectionFactory, + eglBase, + mediaProjectionPermission, + videoParameters + ) { track -> + if (onEnd != null) { + onEnd() } } - listener.onEndpointRemoved(endpoint) - } + localTracks.add(screencastTrack) + localEndpoint = localEndpoint.withTrack(screencastTrack.id(), metadata) - override fun onEndpointUpdated( - endpointId: String, - endpointMetadata: Metadata? - ) { - val endpoint = - remoteEndpoints.remove(endpointId) ?: run { - Timber.e("Failed to process EndpointUpdated event: Endpoint not found: $endpointId") - return - } - - remoteEndpoints[endpoint.id] = endpoint.copy(metadata = endpointMetadata) + coroutineScope.launch { + screencastTrack.startForegroundService(null, null) + screencastTrack.start() } - override fun onOfferData( - integratedTurnServers: List, - tracksTypes: Map - ) { - coroutineScope.launch { - try { - val offer = - localTracksMutex.withLock { - peerConnectionManager.getSdpOffer(integratedTurnServers, tracksTypes, localTracks) - } - rtcEngineCommunication.sdpOffer( - offer.description, - localEndpoint.tracks.mapValues { it.value.metadata }, - offer.midToTrackIdMapping - ) - } catch (e: Exception) { - Timber.e(e, "Failed to create an sdp offer") - } - } + coroutineScope.launch { + peerConnectionManager.addTrack(screencastTrack) + rtcEngineCommunication.renegotiateTracks() } - override fun onSdpAnswer( - type: String, - sdp: String, - midToTrackId: Map - ) { - coroutineScope.launch { - peerConnectionManager.onSdpAnswer(sdp, midToTrackId) - - localTracksMutex.withLock { - // temporary workaround, the backend doesn't add ~ in sdp answer - localTracks.forEach { localTrack -> - if (localTrack.rtcTrack().kind() != "video") return@forEach - var config: SimulcastConfig? = null - if (localTrack is LocalVideoTrack) { - config = localTrack.videoParameters.simulcastConfig - } else if (localTrack is LocalScreencastTrack) { - config = localTrack.videoParameters.simulcastConfig - } - listOf(TrackEncoding.L, TrackEncoding.M, TrackEncoding.H).forEach { - if (config?.activeEncodings?.contains(it) == false) { - peerConnectionManager.setTrackEncoding(localTrack.id(), it, false) - } - } + return screencastTrack + } + + fun removeTrack(trackId: String): Boolean { + return runBlocking(Dispatchers.Default) { + localTracksMutex.withLock { + val track = + localTracks.find { it.id() == trackId } ?: run { + Timber.e("removeTrack: Can't find track to remove") + return@runBlocking false } - } - } - } - override fun onRemoteCandidate( - candidate: String, - sdpMLineIndex: Int, - sdpMid: String? - ) { - coroutineScope.launch { - val iceCandidate = - IceCandidate( - sdpMid ?: "", - sdpMLineIndex, - candidate - ) + peerConnectionManager.removeTrack(track.id()) - peerConnectionManager.onRemoteCandidate(iceCandidate) + localTracks.remove(track) + localEndpoint = localEndpoint.withoutTrack(trackId) + track.stop() } + rtcEngineCommunication.renegotiateTracks() + return@runBlocking true } + } - override fun onTracksAdded( - endpointId: String, - tracks: Map - ) { - if (localEndpoint.id == endpointId) return + fun updateEndpointMetadata(endpointMetadata: Metadata) { + coroutineScope.launch { + rtcEngineCommunication.updateEndpointMetadata(endpointMetadata) + localEndpoint = localEndpoint.copy(metadata = endpointMetadata) + } + } - val endpoint = - remoteEndpoints.remove(endpointId) ?: run { - Timber.e("Failed to process TracksAdded event: Endpoint not found: $endpointId") - return - } + fun updateTrackMetadata( + trackId: String, + trackMetadata: Metadata + ) { + coroutineScope.launch { + rtcEngineCommunication.updateTrackMetadata(trackId, trackMetadata) + localEndpoint = localEndpoint.withTrack(trackId, trackMetadata) + } + } - val updatedEndpoint = endpoint.copy(tracks = tracks) + override fun onConnected( + endpointID: String, + otherEndpoints: List + ) { + this.localEndpoint = localEndpoint.copy(id = endpointID) + listener.onConnected(endpointID, otherEndpoints) - remoteEndpoints[updatedEndpoint.id] = updatedEndpoint + otherEndpoints.forEach { + this.remoteEndpoints[it.id] = it - for ((trackId, trackData) in updatedEndpoint.tracks) { + for ((trackId, trackData) in it.tracks) { val context = TrackContext( track = null, - endpoint = endpoint, + endpoint = it, trackId = trackId, metadata = trackData.metadata ?: mapOf(), simulcastConfig = trackData.simulcastConfig @@ -400,163 +251,311 @@ internal class InternalMembraneRTC this.listener.onTrackAdded(context) } } + } - override fun onTracksRemoved( - endpointId: String, - trackIds: List - ) { - val endpoint = - remoteEndpoints[endpointId] ?: run { - Timber.e("Failed to process TracksRemoved event: Endpoint not found: $endpointId") - return - } + override fun onSendMediaEvent(event: SerializedMediaEvent) { + listener.onSendMediaEvent(event) + } - trackIds.forEach { - val context = trackContexts.remove(it) ?: return@forEach + override fun onEndpointAdded(endpoint: Endpoint) { + if (endpoint.id == this.localEndpoint.id) { + return + } - this.listener.onTrackRemoved(context) - } + remoteEndpoints[endpoint.id] = endpoint - val updatedEndpoint = - trackIds.fold(endpoint) { acc, trackId -> - acc.withoutTrack(trackId) - } + listener.onEndpointAdded(endpoint) + } - remoteEndpoints[endpointId] = updatedEndpoint + override fun onEndpointRemoved(endpointId: String) { + if (endpointId == localEndpoint.id) { + listener.onDisconnected() + return } + val endpoint = + remoteEndpoints.remove(endpointId) ?: run { + Timber.e("Failed to process EndpointLeft event: Endpoint not found: $endpointId") + return + } - override fun onTrackUpdated( - endpointId: String, - trackId: String, - metadata: Metadata? - ) { - val endpoint = - remoteEndpoints[endpointId] ?: run { - Timber.e("Failed to process TrackUpdated event: Endpoint not found: $endpointId") - return - } + val trackIds: List = endpoint.tracks.keys.toList() - val context = - trackContexts[trackId] ?: run { - Timber.e("Failed to process TrackUpdated event: Track context not found: $trackId") - return - } + trackIds.forEach { + trackContexts.remove(it)?.let { ctx -> + listener.onTrackRemoved(ctx) + } + } - context.metadata = metadata ?: mapOf() + listener.onEndpointRemoved(endpoint) + } - val updatedEndpoint = - endpoint - .withoutTrack(trackId) - .withTrack(trackId, metadata) + override fun onEndpointUpdated( + endpointId: String, + endpointMetadata: Metadata? + ) { + val endpoint = + remoteEndpoints.remove(endpointId) ?: run { + Timber.e("Failed to process EndpointUpdated event: Endpoint not found: $endpointId") + return + } - remoteEndpoints[endpointId] = updatedEndpoint + remoteEndpoints[endpoint.id] = endpoint.copy(metadata = endpointMetadata) + } - this.listener.onTrackUpdated(context) + override fun onOfferData( + integratedTurnServers: List, + tracksTypes: Map + ) { + coroutineScope.launch { + try { + val offer = + localTracksMutex.withLock { + peerConnectionManager.getSdpOffer(integratedTurnServers, tracksTypes, localTracks) + } + rtcEngineCommunication.sdpOffer( + offer.description, + localEndpoint.tracks.mapValues { it.value.metadata }, + offer.midToTrackIdMapping + ) + } catch (e: Exception) { + Timber.e(e, "Failed to create an sdp offer") + } } + } - override fun onTrackEncodingChanged( - endpointId: String, - trackId: String, - encoding: String, - encodingReason: String - ) { - val encodingReasonEnum = EncodingReason.fromString(encodingReason) - if (encodingReasonEnum == null) { - Timber.e("Invalid encoding reason: $encodingReason") - return + override fun onSdpAnswer( + type: String, + sdp: String, + midToTrackId: Map + ) { + coroutineScope.launch { + peerConnectionManager.onSdpAnswer(sdp, midToTrackId) + + localTracksMutex.withLock { + // temporary workaround, the backend doesn't add ~ in sdp answer + localTracks.forEach { localTrack -> + if (localTrack.rtcTrack().kind() != "video") return@forEach + var config: SimulcastConfig? = null + if (localTrack is LocalVideoTrack) { + config = localTrack.videoParameters.simulcastConfig + } else if (localTrack is LocalScreencastTrack) { + config = localTrack.videoParameters.simulcastConfig + } + listOf(TrackEncoding.L, TrackEncoding.M, TrackEncoding.H).forEach { + if (config?.activeEncodings?.contains(it) == false) { + peerConnectionManager.setTrackEncoding(localTrack.id(), it, false) + } + } + } } - val trackContext = trackContexts[trackId] - if (trackContext == null) { - Timber.e("Invalid trackId: $trackId") + } + } + + override fun onRemoteCandidate( + candidate: String, + sdpMLineIndex: Int, + sdpMid: String? + ) { + coroutineScope.launch { + val iceCandidate = + IceCandidate( + sdpMid ?: "", + sdpMLineIndex, + candidate + ) + + peerConnectionManager.onRemoteCandidate(iceCandidate) + } + } + + override fun onTracksAdded( + endpointId: String, + tracks: Map + ) { + if (localEndpoint.id == endpointId) return + + val endpoint = + remoteEndpoints.remove(endpointId) ?: run { + Timber.e("Failed to process TracksAdded event: Endpoint not found: $endpointId") return } - val encodingEnum = TrackEncoding.fromString(encoding) - if (encodingEnum == null) { - Timber.e("Invalid encoding: $encoding") + + val updatedEndpoint = endpoint.copy(tracks = tracks) + + remoteEndpoints[updatedEndpoint.id] = updatedEndpoint + + for ((trackId, trackData) in updatedEndpoint.tracks) { + val context = + TrackContext( + track = null, + endpoint = endpoint, + trackId = trackId, + metadata = trackData.metadata ?: mapOf(), + simulcastConfig = trackData.simulcastConfig + ) + + this.trackContexts[trackId] = context + + this.listener.onTrackAdded(context) + } + } + + override fun onTracksRemoved( + endpointId: String, + trackIds: List + ) { + val endpoint = + remoteEndpoints[endpointId] ?: run { + Timber.e("Failed to process TracksRemoved event: Endpoint not found: $endpointId") return } - trackContext.setEncoding(encodingEnum, encodingReasonEnum) + + trackIds.forEach { + val context = trackContexts.remove(it) ?: return@forEach + + this.listener.onTrackRemoved(context) } - override fun onVadNotification( - trackId: String, - status: String - ) { - val trackContext = trackContexts[trackId] - if (trackContext == null) { - Timber.e("Invalid track id = $trackId") + val updatedEndpoint = + trackIds.fold(endpoint) { acc, trackId -> + acc.withoutTrack(trackId) + } + + remoteEndpoints[endpointId] = updatedEndpoint + } + + override fun onTrackUpdated( + endpointId: String, + trackId: String, + metadata: Metadata? + ) { + val endpoint = + remoteEndpoints[endpointId] ?: run { + Timber.e("Failed to process TrackUpdated event: Endpoint not found: $endpointId") return } - val vadStatus = VadStatus.fromString(status) - if (vadStatus == null) { - Timber.e("Invalid vad status = $status") + + val context = + trackContexts[trackId] ?: run { + Timber.e("Failed to process TrackUpdated event: Track context not found: $trackId") return } - trackContext.vadStatus = vadStatus - } - override fun onBandwidthEstimation(estimation: Long) { - listener.onBandwidthEstimationChanged(estimation) - } + context.metadata = metadata ?: mapOf() - fun setTargetTrackEncoding( - trackId: String, - encoding: TrackEncoding - ) { - coroutineScope.launch { - rtcEngineCommunication.setTargetTrackEncoding(trackId, encoding) - } - } + val updatedEndpoint = + endpoint + .withoutTrack(trackId) + .withTrack(trackId, metadata) - fun enableTrackEncoding( - trackId: String, - encoding: TrackEncoding - ) { - coroutineScope.launch { - peerConnectionManager.setTrackEncoding(trackId, encoding, true) - } + remoteEndpoints[endpointId] = updatedEndpoint + + this.listener.onTrackUpdated(context) + } + + override fun onTrackEncodingChanged( + endpointId: String, + trackId: String, + encoding: String, + encodingReason: String + ) { + val encodingReasonEnum = EncodingReason.fromString(encodingReason) + if (encodingReasonEnum == null) { + Timber.e("Invalid encoding reason: $encodingReason") + return + } + val trackContext = trackContexts[trackId] + if (trackContext == null) { + Timber.e("Invalid trackId: $trackId") + return + } + val encodingEnum = TrackEncoding.fromString(encoding) + if (encodingEnum == null) { + Timber.e("Invalid encoding: $encoding") + return + } + trackContext.setEncoding(encodingEnum, encodingReasonEnum) + } + + override fun onVadNotification( + trackId: String, + status: String + ) { + val trackContext = trackContexts[trackId] + if (trackContext == null) { + Timber.e("Invalid track id = $trackId") + return + } + val vadStatus = VadStatus.fromString(status) + if (vadStatus == null) { + Timber.e("Invalid vad status = $status") + return + } + trackContext.vadStatus = vadStatus + } + + override fun onBandwidthEstimation(estimation: Long) { + listener.onBandwidthEstimationChanged(estimation) + } + + fun setTargetTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { + coroutineScope.launch { + rtcEngineCommunication.setTargetTrackEncoding(trackId, encoding) } + } - fun disableTrackEncoding( - trackId: String, - encoding: TrackEncoding - ) { - coroutineScope.launch { - peerConnectionManager.setTrackEncoding(trackId, encoding, false) - } + fun enableTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { + coroutineScope.launch { + peerConnectionManager.setTrackEncoding(trackId, encoding, true) } + } - override fun onLocalIceCandidate(candidate: IceCandidate) { - coroutineScope.launch { - rtcEngineCommunication.localCandidate(candidate.sdp, candidate.sdpMLineIndex) - } + fun disableTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { + coroutineScope.launch { + peerConnectionManager.setTrackEncoding(trackId, encoding, false) } + } - override fun onAddTrack( - trackId: String, - track: MediaStreamTrack - ) { - val trackContext = - trackContexts[trackId] ?: run { - Timber.e("onAddTrack: Track context with trackId=$trackId not found") - return - } + override fun onLocalIceCandidate(candidate: IceCandidate) { + coroutineScope.launch { + rtcEngineCommunication.localCandidate(candidate.sdp, candidate.sdpMLineIndex) + } + } - when (track) { - is VideoTrack -> - trackContext.track = RemoteVideoTrack(track, eglBase) + override fun onAddTrack( + trackId: String, + track: MediaStreamTrack + ) { + val trackContext = + trackContexts[trackId] ?: run { + Timber.e("onAddTrack: Track context with trackId=$trackId not found") + return + } - is AudioTrack -> - trackContext.track = RemoteAudioTrack(track) + when (track) { + is VideoTrack -> + trackContext.track = RemoteVideoTrack(track, eglBase) - else -> - throw IllegalStateException("invalid type of incoming track") - } + is AudioTrack -> + trackContext.track = RemoteAudioTrack(track) - listener.onTrackReady(trackContext) + else -> + throw IllegalStateException("invalid type of incoming track") } - fun getStats(): Map { - return peerConnectionManager.getStats() - } + listener.onTrackReady(trackContext) + } + + fun getStats(): Map { + return peerConnectionManager.getStats() } +} diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt index dfe828a..546b1c4 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt @@ -8,38 +8,37 @@ import org.webrtc.PeerConnection import org.webrtc.PeerConnectionFactory import org.webrtc.audio.AudioDeviceModule -internal class PeerConnectionFactoryWrapper - constructor( - private val createOptions: CreateOptions, - audioDeviceModule: AudioDeviceModule, - eglBase: EglBase, - appContext: Context - ) { - interface PeerConnectionFactoryWrapperFactory { - fun create(createOptions: CreateOptions): PeerConnectionFactoryWrapper - } +internal class PeerConnectionFactoryWrapper( + private val createOptions: CreateOptions, + audioDeviceModule: AudioDeviceModule, + eglBase: EglBase, + appContext: Context +) { + interface PeerConnectionFactoryWrapperFactory { + fun create(createOptions: CreateOptions): PeerConnectionFactoryWrapper + } - val peerConnectionFactory: PeerConnectionFactory + val peerConnectionFactory: PeerConnectionFactory - init { - PeerConnectionFactory.initialize( - PeerConnectionFactory.InitializationOptions.builder(appContext).createInitializationOptions() - ) + init { + PeerConnectionFactory.initialize( + PeerConnectionFactory.InitializationOptions.builder(appContext).createInitializationOptions() + ) - peerConnectionFactory = - PeerConnectionFactory.builder().setAudioDeviceModule(audioDeviceModule).setVideoEncoderFactory( - SimulcastVideoEncoderFactoryWrapper( - eglBase.eglBaseContext, - createOptions.encoderOptions - ) - ).setVideoDecoderFactory(DefaultVideoDecoderFactory(eglBase.eglBaseContext)) - .createPeerConnectionFactory() - } + peerConnectionFactory = + PeerConnectionFactory.builder().setAudioDeviceModule(audioDeviceModule).setVideoEncoderFactory( + SimulcastVideoEncoderFactoryWrapper( + eglBase.eglBaseContext, + createOptions.encoderOptions + ) + ).setVideoDecoderFactory(DefaultVideoDecoderFactory(eglBase.eglBaseContext)) + .createPeerConnectionFactory() + } - fun createPeerConnection( - rtcConfig: PeerConnection.RTCConfiguration, - observer: PeerConnection.Observer - ): PeerConnection? { - return peerConnectionFactory.createPeerConnection(rtcConfig, observer) - } + fun createPeerConnection( + rtcConfig: PeerConnection.RTCConfiguration, + observer: PeerConnection.Observer + ): PeerConnection? { + return peerConnectionFactory.createPeerConnection(rtcConfig, observer) } +} diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt index a02adbc..ebd7636 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt @@ -21,550 +21,549 @@ import java.math.BigInteger import java.util.* import kotlin.math.pow -internal class PeerConnectionManager - constructor( - private val peerConnectionListener: PeerConnectionListener, - private val peerConnectionFactory: PeerConnectionFactoryWrapper - ) : PeerConnection.Observer { - interface PeerConnectionManagerFactory { - fun create( - listener: PeerConnectionListener, - peerConnectionFactory: PeerConnectionFactoryWrapper - ): PeerConnectionManager - } +internal class PeerConnectionManager( + private val peerConnectionListener: PeerConnectionListener, + private val peerConnectionFactory: PeerConnectionFactoryWrapper +) : PeerConnection.Observer { + interface PeerConnectionManagerFactory { + fun create( + listener: PeerConnectionListener, + peerConnectionFactory: PeerConnectionFactoryWrapper + ): PeerConnectionManager + } - private var peerConnection: PeerConnection? = null - private val peerConnectionMutex = Mutex() - private val peerConnectionStats = mutableMapOf() + private var peerConnection: PeerConnection? = null + private val peerConnectionMutex = Mutex() + private val peerConnectionStats = mutableMapOf() - private var iceServers: List? = null - private var config: PeerConnection.RTCConfiguration? = null - private var queuedRemoteCandidates: MutableList? = null - private val qrcMutex = Mutex() - private var midToTrackId: Map = HashMap() + private var iceServers: List? = null + private var config: PeerConnection.RTCConfiguration? = null + private var queuedRemoteCandidates: MutableList? = null + private val qrcMutex = Mutex() + private var midToTrackId: Map = HashMap() - private val coroutineScope: CoroutineScope = - ClosableCoroutineScope(SupervisorJob()) + private val coroutineScope: CoroutineScope = + ClosableCoroutineScope(SupervisorJob()) - private var streamIds: List = listOf(UUID.randomUUID().toString()) + private var streamIds: List = listOf(UUID.randomUUID().toString()) - private fun getSendEncodingsFromConfig(simulcastConfig: SimulcastConfig): List { - val sendEncodings = Constants.simulcastEncodings() - simulcastConfig.activeEncodings.forEach { - sendEncodings[it.ordinal].active = true - } - return sendEncodings + private fun getSendEncodingsFromConfig(simulcastConfig: SimulcastConfig): List { + val sendEncodings = Constants.simulcastEncodings() + simulcastConfig.activeEncodings.forEach { + sendEncodings[it.ordinal].active = true } + return sendEncodings + } - suspend fun addTrack(track: LocalTrack) { - addTrack(track, streamIds) - } + suspend fun addTrack(track: LocalTrack) { + addTrack(track, streamIds) + } - private suspend fun addTrack( - track: LocalTrack, - streamIds: List - ) { - val videoParameters = - (track as? LocalVideoTrack)?.videoParameters ?: (track as? LocalScreencastTrack)?.videoParameters - - val simulcastConfig = videoParameters?.simulcastConfig - val sendEncodings = - if (track.rtcTrack().kind() == "video" && simulcastConfig != null && simulcastConfig.enabled) { - getSendEncodingsFromConfig(simulcastConfig) - } else { - listOf(RtpParameters.Encoding(null, true, null)) - } + private suspend fun addTrack( + track: LocalTrack, + streamIds: List + ) { + val videoParameters = + (track as? LocalVideoTrack)?.videoParameters ?: (track as? LocalScreencastTrack)?.videoParameters + + val simulcastConfig = videoParameters?.simulcastConfig + val sendEncodings = + if (track.rtcTrack().kind() == "video" && simulcastConfig != null && simulcastConfig.enabled) { + getSendEncodingsFromConfig(simulcastConfig) + } else { + listOf(RtpParameters.Encoding(null, true, null)) + } - peerConnectionMutex.withLock { - val pc = - peerConnection ?: run { - Timber.e("addTrack: Peer connection not yet established") - return - } - - if (videoParameters?.maxBitrate != null) { - applyBitrate(sendEncodings, videoParameters.maxBitrate) + peerConnectionMutex.withLock { + val pc = + peerConnection ?: run { + Timber.e("addTrack: Peer connection not yet established") + return } - pc.addTransceiver( - track.rtcTrack(), - RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, - streamIds, - sendEncodings - ) - pc.enforceSendOnlyDirection() + if (videoParameters?.maxBitrate != null) { + applyBitrate(sendEncodings, videoParameters.maxBitrate) } + + pc.addTransceiver( + track.rtcTrack(), + RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, + streamIds, + sendEncodings + ) + pc.enforceSendOnlyDirection() } + } - private fun applyBitrate( - encodings: List, - maxBitrate: TrackBandwidthLimit - ) { - when (maxBitrate) { - is TrackBandwidthLimit.BandwidthLimit -> splitBitrate(encodings, maxBitrate) - is TrackBandwidthLimit.SimulcastBandwidthLimit -> - encodings.forEach { - val encodingLimit = maxBitrate.limit[it.rid]?.limit ?: 0 - it.maxBitrateBps = if (encodingLimit == 0) null else encodingLimit * 1024 - } - } + private fun applyBitrate( + encodings: List, + maxBitrate: TrackBandwidthLimit + ) { + when (maxBitrate) { + is TrackBandwidthLimit.BandwidthLimit -> splitBitrate(encodings, maxBitrate) + is TrackBandwidthLimit.SimulcastBandwidthLimit -> + encodings.forEach { + val encodingLimit = maxBitrate.limit[it.rid]?.limit ?: 0 + it.maxBitrateBps = if (encodingLimit == 0) null else encodingLimit * 1024 + } } + } - private fun splitBitrate( - encodings: List, - maxBitrate: TrackBandwidthLimit.BandwidthLimit - ) { - if (encodings.isEmpty()) { - Timber.e("splitBitrate: Attempted to limit bandwidth of the track that doesn't have any encodings") - return - } - if (maxBitrate.limit == 0) { - encodings.forEach { it.maxBitrateBps = null } - return + private fun splitBitrate( + encodings: List, + maxBitrate: TrackBandwidthLimit.BandwidthLimit + ) { + if (encodings.isEmpty()) { + Timber.e("splitBitrate: Attempted to limit bandwidth of the track that doesn't have any encodings") + return + } + if (maxBitrate.limit == 0) { + encodings.forEach { it.maxBitrateBps = null } + return + } + + val k0 = encodings.minByOrNull { it.scaleResolutionDownBy ?: 1.0 } + + val bitrateParts = + encodings.sumOf { + ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow( + 2 + ) } - val k0 = encodings.minByOrNull { it.scaleResolutionDownBy ?: 1.0 } + val x = maxBitrate.limit / bitrateParts - val bitrateParts = - encodings.sumOf { - ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow( - 2 - ) + encodings.forEach { + it.maxBitrateBps = + (x * ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow(2) * 1024).toInt() + } + } + + suspend fun setTrackBandwidth( + trackId: String, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit + ) { + peerConnectionMutex.withLock { + val pc = + peerConnection ?: run { + Timber.e("setTrackBandwidth: Peer connection not yet established") + return + } + val sender = + pc.senders.find { it.track()?.id() == trackId } ?: run { + Timber.e("setTrackBandwidth: Invalid trackId: track sender not found") + return } + val params = sender.parameters - val x = maxBitrate.limit / bitrateParts + applyBitrate(params.getEncodings(), bandwidthLimit) - encodings.forEach { - it.maxBitrateBps = - (x * ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow(2) * 1024).toInt() - } + sender.parameters = params } + } - suspend fun setTrackBandwidth( - trackId: String, - bandwidthLimit: TrackBandwidthLimit.BandwidthLimit - ) { - peerConnectionMutex.withLock { - val pc = - peerConnection ?: run { - Timber.e("setTrackBandwidth: Peer connection not yet established") - return - } - val sender = - pc.senders.find { it.track()?.id() == trackId } ?: run { - Timber.e("setTrackBandwidth: Invalid trackId: track sender not found") - return - } - val params = sender.parameters - - applyBitrate(params.getEncodings(), bandwidthLimit) - - sender.parameters = params - } - } + suspend fun setEncodingBandwidth( + trackId: String, + encoding: String, + bandwidthLimit: TrackBandwidthLimit.BandwidthLimit + ) { + peerConnectionMutex.withLock { + val pc = + peerConnection ?: run { + Timber.e("setEncodingBandwidth: Peer connection not yet established") + return + } + val sender = + pc.senders.find { it.track()?.id() == trackId } ?: run { + Timber.e("setEncodingBandwidth: Invalid trackId: track sender not found") + return + } - suspend fun setEncodingBandwidth( - trackId: String, - encoding: String, - bandwidthLimit: TrackBandwidthLimit.BandwidthLimit - ) { - peerConnectionMutex.withLock { - val pc = - peerConnection ?: run { - Timber.e("setEncodingBandwidth: Peer connection not yet established") - return - } - val sender = - pc.senders.find { it.track()?.id() == trackId } ?: run { - Timber.e("setEncodingBandwidth: Invalid trackId: track sender not found") - return - } - - val params = sender.parameters - val encodingParameters = - params.encodings.find { it.rid == encoding } ?: run { - Timber.e("setEncodingBandwidth: Invalid encoding: encoding not found") - return - } - - encodingParameters.maxBitrateBps = bandwidthLimit.limit * 1024 - - sender.parameters = params - } + val params = sender.parameters + val encodingParameters = + params.encodings.find { it.rid == encoding } ?: run { + Timber.e("setEncodingBandwidth: Invalid encoding: encoding not found") + return + } + + encodingParameters.maxBitrateBps = bandwidthLimit.limit * 1024 + + sender.parameters = params } + } - suspend fun removeTrack(trackId: String): Boolean { - peerConnectionMutex.withLock { - val pc = - peerConnection ?: run { - Timber.e("removeTrack: Peer connection not yet established") - return false - } - pc.transceivers.find { it.sender.track()?.id() == trackId }?.sender?.let { - pc.removeTrack(it) - return true + suspend fun removeTrack(trackId: String): Boolean { + peerConnectionMutex.withLock { + val pc = + peerConnection ?: run { + Timber.e("removeTrack: Peer connection not yet established") + return false } - return false + pc.transceivers.find { it.sender.track()?.id() == trackId }?.sender?.let { + pc.removeTrack(it) + return true } + return false } + } - private suspend fun setupPeerConnection(localTracks: List) { - if (peerConnection != null) { - Timber.e("setupPeerConnection: Peer connection already established!") - return - } + private suspend fun setupPeerConnection(localTracks: List) { + if (peerConnection != null) { + Timber.e("setupPeerConnection: Peer connection already established!") + return + } - assert(config != null) - val config = this.config!! + assert(config != null) + val config = this.config!! - config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN - config.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY - config.candidateNetworkPolicy = PeerConnection.CandidateNetworkPolicy.ALL - config.disableIpv6 = true - config.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED + config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN + config.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY + config.candidateNetworkPolicy = PeerConnection.CandidateNetworkPolicy.ALL + config.disableIpv6 = true + config.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED - val pc = - peerConnectionFactory.createPeerConnection(config, this) - ?: throw IllegalStateException("Failed to create a peerConnection") + val pc = + peerConnectionFactory.createPeerConnection(config, this) + ?: throw IllegalStateException("Failed to create a peerConnection") - peerConnectionMutex.withLock { - this@PeerConnectionManager.peerConnection = pc - } + peerConnectionMutex.withLock { + this@PeerConnectionManager.peerConnection = pc + } - localTracks.forEach { - addTrack(it, streamIds) - } + localTracks.forEach { + addTrack(it, streamIds) + } - peerConnectionMutex.withLock { - pc.enforceSendOnlyDirection() - } + peerConnectionMutex.withLock { + pc.enforceSendOnlyDirection() } + } - private suspend fun drainCandidates() { - qrcMutex.withLock { - this.queuedRemoteCandidates?.let { - for (candidate in it) { - this.peerConnection?.addIceCandidate(candidate) - } - this.queuedRemoteCandidates = null + private suspend fun drainCandidates() { + qrcMutex.withLock { + this.queuedRemoteCandidates?.let { + for (candidate in it) { + this.peerConnection?.addIceCandidate(candidate) } + this.queuedRemoteCandidates = null } } + } - private fun prepareIceServers(integratedTurnServers: List) { - if (config != null || iceServers != null) { - Timber.e("prepareIceServers: Config or ice servers are already initialized, skipping the preparation") - return + private fun prepareIceServers(integratedTurnServers: List) { + if (config != null || iceServers != null) { + Timber.e("prepareIceServers: Config or ice servers are already initialized, skipping the preparation") + return + } + + this.iceServers = + integratedTurnServers.map { + val url = + listOf( + "turn", + ":", + it.serverAddr, + ":", + it.serverPort.toString(), + "?transport=", + it.transport + ).joinToString("") + + PeerConnection.IceServer.builder(url) + .setUsername(it.username) + .setPassword(it.password) + .createIceServer() } - this.iceServers = - integratedTurnServers.map { - val url = - listOf( - "turn", - ":", - it.serverAddr, - ":", - it.serverPort.toString(), - "?transport=", - it.transport - ).joinToString("") - - PeerConnection.IceServer.builder(url) - .setUsername(it.username) - .setPassword(it.password) - .createIceServer() - } - - val config = PeerConnection.RTCConfiguration(iceServers) - config.iceTransportsType = PeerConnection.IceTransportsType.RELAY - this.config = config - } + val config = PeerConnection.RTCConfiguration(iceServers) + config.iceTransportsType = PeerConnection.IceTransportsType.RELAY + this.config = config + } - private fun addNecessaryTransceivers(tracksTypes: Map) { - val pc = peerConnection ?: return + private fun addNecessaryTransceivers(tracksTypes: Map) { + val pc = peerConnection ?: return - val necessaryAudio = tracksTypes["audio"] ?: 0 - val necessaryVideo = tracksTypes["video"] ?: 0 + val necessaryAudio = tracksTypes["audio"] ?: 0 + val necessaryVideo = tracksTypes["video"] ?: 0 - var lackingAudio = necessaryAudio - var lackingVideo = necessaryVideo + var lackingAudio = necessaryAudio + var lackingVideo = necessaryVideo - pc.transceivers.filter { - it.direction == RtpTransceiver.RtpTransceiverDirection.RECV_ONLY - }.forEach { - val track = it.receiver.track() ?: return@forEach + pc.transceivers.filter { + it.direction == RtpTransceiver.RtpTransceiverDirection.RECV_ONLY + }.forEach { + val track = it.receiver.track() ?: return@forEach - when (track.kind()) { - "audio" -> lackingAudio -= 1 - "video" -> lackingVideo -= 1 - } + when (track.kind()) { + "audio" -> lackingAudio -= 1 + "video" -> lackingVideo -= 1 } + } - Timber.d("peerConnection adding $lackingAudio audio and $lackingVideo video lacking transceivers") + Timber.d("peerConnection adding $lackingAudio audio and $lackingVideo video lacking transceivers") - repeat(lackingAudio) { - pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_AUDIO).direction = - RtpTransceiver.RtpTransceiverDirection.RECV_ONLY - } + repeat(lackingAudio) { + pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_AUDIO).direction = + RtpTransceiver.RtpTransceiverDirection.RECV_ONLY + } - repeat(lackingVideo) { - pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO).direction = - RtpTransceiver.RtpTransceiverDirection.RECV_ONLY - } + repeat(lackingVideo) { + pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO).direction = + RtpTransceiver.RtpTransceiverDirection.RECV_ONLY } + } - suspend fun onSdpAnswer( - sdp: String, - midToTrackId: Map - ) { - peerConnectionMutex.withLock { - val pc = peerConnection ?: return + suspend fun onSdpAnswer( + sdp: String, + midToTrackId: Map + ) { + peerConnectionMutex.withLock { + val pc = peerConnection ?: return - val answer = - SessionDescription( - SessionDescription.Type.ANSWER, - sdp - ) + val answer = + SessionDescription( + SessionDescription.Type.ANSWER, + sdp + ) - this@PeerConnectionManager.midToTrackId = midToTrackId + this@PeerConnectionManager.midToTrackId = midToTrackId - pc.setRemoteDescription(answer).onSuccess { - drainCandidates() - } + pc.setRemoteDescription(answer).onSuccess { + drainCandidates() } } + } - private fun midToTrackIdMapping(localTracks: List): Map { - val pc = peerConnection ?: return emptyMap() + private fun midToTrackIdMapping(localTracks: List): Map { + val pc = peerConnection ?: return emptyMap() - val mapping = mutableMapOf() + val mapping = mutableMapOf() - pc.transceivers.forEach { - val trackId = it.sender.track()?.id() ?: return@forEach + pc.transceivers.forEach { + val trackId = it.sender.track()?.id() ?: return@forEach - if (!localTracks.map { track -> track.id() }.contains(trackId)) return@forEach + if (!localTracks.map { track -> track.id() }.contains(trackId)) return@forEach - mapping[it.mid] = trackId - } - - return mapping + mapping[it.mid] = trackId } - data class SdpOffer( - val description: String, - val midToTrackIdMapping: Map - ) + return mapping + } - suspend fun getSdpOffer( - integratedTurnServers: List, - tracksTypes: Map, - localTracks: List - ): SdpOffer { - qrcMutex.withLock { - this@PeerConnectionManager.queuedRemoteCandidates = mutableListOf() - } - prepareIceServers(integratedTurnServers) + data class SdpOffer( + val description: String, + val midToTrackIdMapping: Map + ) - var needsRestart = true - if (peerConnection == null) { - setupPeerConnection(localTracks) - needsRestart = false - } - peerConnectionMutex.withLock { - val pc = peerConnection!! + suspend fun getSdpOffer( + integratedTurnServers: List, + tracksTypes: Map, + localTracks: List + ): SdpOffer { + qrcMutex.withLock { + this@PeerConnectionManager.queuedRemoteCandidates = mutableListOf() + } + prepareIceServers(integratedTurnServers) - if (needsRestart) { - pc.restartIce() - } + var needsRestart = true + if (peerConnection == null) { + setupPeerConnection(localTracks) + needsRestart = false + } + peerConnectionMutex.withLock { + val pc = peerConnection!! + + if (needsRestart) { + pc.restartIce() + } - addNecessaryTransceivers(tracksTypes) + addNecessaryTransceivers(tracksTypes) - pc.transceivers.forEach { - if (it.direction == RtpTransceiver.RtpTransceiverDirection.SEND_RECV) { - it.direction = RtpTransceiver.RtpTransceiverDirection.SEND_ONLY - } + pc.transceivers.forEach { + if (it.direction == RtpTransceiver.RtpTransceiverDirection.SEND_RECV) { + it.direction = RtpTransceiver.RtpTransceiverDirection.SEND_ONLY } + } - val constraints = MediaConstraints() + val constraints = MediaConstraints() - Timber.i("Creating offer") - val offer = pc.createOffer(constraints).getOrThrow() + Timber.i("Creating offer") + val offer = pc.createOffer(constraints).getOrThrow() - Timber.i("Setting local description") - pc.setLocalDescription(offer).getOrThrow() + Timber.i("Setting local description") + pc.setLocalDescription(offer).getOrThrow() - return SdpOffer(offer.description, midToTrackIdMapping(localTracks)) - } + return SdpOffer(offer.description, midToTrackIdMapping(localTracks)) } + } - suspend fun setTrackEncoding( - trackId: String, - trackEncoding: TrackEncoding, - enabled: Boolean - ) { - peerConnectionMutex.withLock { - val sender = - peerConnection?.senders?.find { it -> it.track()?.id() == trackId } ?: run { - Timber.e("setTrackEncoding: Invalid trackId $trackId, no track sender found") - return - } - val params = sender.parameters - val encoding = - params?.encodings?.find { it.rid == trackEncoding.rid } ?: run { - Timber.e( - "setTrackEncoding: Invalid encoding $trackEncoding," + - "no such encoding found in peer connection" - ) - return - } - encoding.active = enabled - sender.parameters = params - } + suspend fun setTrackEncoding( + trackId: String, + trackEncoding: TrackEncoding, + enabled: Boolean + ) { + peerConnectionMutex.withLock { + val sender = + peerConnection?.senders?.find { it -> it.track()?.id() == trackId } ?: run { + Timber.e("setTrackEncoding: Invalid trackId $trackId, no track sender found") + return + } + val params = sender.parameters + val encoding = + params?.encodings?.find { it.rid == trackEncoding.rid } ?: run { + Timber.e( + "setTrackEncoding: Invalid encoding $trackEncoding," + + "no such encoding found in peer connection" + ) + return + } + encoding.active = enabled + sender.parameters = params } + } - suspend fun onRemoteCandidate(iceCandidate: IceCandidate) { - peerConnectionMutex.withLock { - val pc = peerConnection ?: return - qrcMutex.withLock { - if (this@PeerConnectionManager.queuedRemoteCandidates == null) { - pc.addIceCandidate(iceCandidate) - } else { - this@PeerConnectionManager.queuedRemoteCandidates!!.add(iceCandidate) - } + suspend fun onRemoteCandidate(iceCandidate: IceCandidate) { + peerConnectionMutex.withLock { + val pc = peerConnection ?: return + qrcMutex.withLock { + if (this@PeerConnectionManager.queuedRemoteCandidates == null) { + pc.addIceCandidate(iceCandidate) + } else { + this@PeerConnectionManager.queuedRemoteCandidates!!.add(iceCandidate) } } } + } - suspend fun close() { - peerConnectionMutex.withLock { - peerConnection?.close() - } + suspend fun close() { + peerConnectionMutex.withLock { + peerConnection?.close() } + } - override fun onSignalingChange(state: PeerConnection.SignalingState?) { - Timber.d("Changed signalling state to $state") - } + override fun onSignalingChange(state: PeerConnection.SignalingState?) { + Timber.d("Changed signalling state to $state") + } - override fun onIceConnectionChange(state: PeerConnection.IceConnectionState?) { - Timber.d("Changed ice connection state to $state") - } + override fun onIceConnectionChange(state: PeerConnection.IceConnectionState?) { + Timber.d("Changed ice connection state to $state") + } - override fun onIceConnectionReceivingChange(receiving: Boolean) { - Timber.d("Changed ice connection receiving status to: $receiving") - } + override fun onIceConnectionReceivingChange(receiving: Boolean) { + Timber.d("Changed ice connection receiving status to: $receiving") + } - override fun onIceGatheringChange(state: PeerConnection.IceGatheringState?) { - Timber.d("Change ice gathering state to $state") - } + override fun onIceGatheringChange(state: PeerConnection.IceGatheringState?) { + Timber.d("Change ice gathering state to $state") + } - override fun onIceCandidate(candidate: IceCandidate?) { - if (candidate != null) { - peerConnectionListener.onLocalIceCandidate(candidate) - } + override fun onIceCandidate(candidate: IceCandidate?) { + if (candidate != null) { + peerConnectionListener.onLocalIceCandidate(candidate) } + } - override fun onIceCandidatesRemoved(candidates: Array?) { - Timber.d("Removed ice candidates from connection") - } + override fun onIceCandidatesRemoved(candidates: Array?) { + Timber.d("Removed ice candidates from connection") + } - override fun onAddStream(stream: MediaStream?) { - Timber.d("Added media stream") - } + override fun onAddStream(stream: MediaStream?) { + Timber.d("Added media stream") + } - override fun onRemoveStream(stream: MediaStream?) { - Timber.d("Removed media stream") - } + override fun onRemoveStream(stream: MediaStream?) { + Timber.d("Removed media stream") + } + + override fun onAddTrack( + receiver: RtpReceiver?, + mediaStreams: Array? + ) { + var trackId: String? = null + coroutineScope.launch { + peerConnectionMutex.withLock { + val pc = peerConnection ?: return@launch + + val transceiver = + pc.transceivers.find { + it.receiver.id() == receiver?.id() + } ?: return@launch - override fun onAddTrack( - receiver: RtpReceiver?, - mediaStreams: Array? - ) { - var trackId: String? = null - coroutineScope.launch { - peerConnectionMutex.withLock { - val pc = peerConnection ?: return@launch - - val transceiver = - pc.transceivers.find { - it.receiver.id() == receiver?.id() - } ?: return@launch - - val mid = transceiver.mid - - trackId = midToTrackId[mid] ?: run { - Timber.e("onAddTrack: Track with mid=$mid not found") - return@launch - } + val mid = transceiver.mid + + trackId = midToTrackId[mid] ?: run { + Timber.e("onAddTrack: Track with mid=$mid not found") + return@launch } - peerConnectionListener.onAddTrack(trackId!!, receiver!!.track()!!) } + peerConnectionListener.onAddTrack(trackId!!, receiver!!.track()!!) } + } - override fun onRemoveTrack(receiver: RtpReceiver?) { - super.onRemoveTrack(receiver) - } + override fun onRemoveTrack(receiver: RtpReceiver?) { + super.onRemoveTrack(receiver) + } - override fun onDataChannel(dataChannel: DataChannel?) { - Timber.d("New data channel") - } + override fun onDataChannel(dataChannel: DataChannel?) { + Timber.d("New data channel") + } - override fun onRenegotiationNeeded() { - Timber.d("Renegotiation needed") - } + override fun onRenegotiationNeeded() { + Timber.d("Renegotiation needed") + } - fun getStats(): Map { - peerConnection?.getStats { rtcStatsReport -> extractRelevantStats(rtcStatsReport) } - return peerConnectionStats.toMap() - } + fun getStats(): Map { + peerConnection?.getStats { rtcStatsReport -> extractRelevantStats(rtcStatsReport) } + return peerConnectionStats.toMap() + } - private fun extractRelevantStats(rp: RTCStatsReport) { - rp.statsMap.values.forEach { - if (it.type == "outbound-rtp") { - val durations = it.members["qualityLimitationDurations"] as? Map<*, *> - val qualityLimitation = - QualityLimitationDurations( - durations?.get("bandwidth") as? Double ?: 0.0, - durations?.get("cpu") as? Double ?: 0.0, - durations?.get("none") as? Double ?: 0.0, - durations?.get("other") as? Double ?: 0.0 - ) - - val tmp = - RTCOutboundStats( - it.members["kind"] as? String, - it.members["rid"] as? String, - it.members["bytesSent"] as? BigInteger, - it.members["targetBitrate"] as? Double, - it.members["packetsSent"] as? Long, - it.members["framesEncoded"] as? Long, - it.members["framesPerSecond"] as? Double, - it.members["frameWidth"] as? Long, - it.members["frameHeight"] as? Long, - qualityLimitation - ) - - peerConnectionStats[it.id as String] = tmp - } else if (it.type == "inbound-rtp") { - val tmp = - RTCInboundStats( - it.members["kind"] as? String, - it.members["jitter"] as? Double, - it.members["packetsLost"] as? Int, - it.members["packetsReceived"] as? Long, - it.members["bytesReceived"] as? BigInteger, - it.members["framesReceived"] as? Int, - it.members["frameWidth"] as? Long, - it.members["frameHeight"] as? Long, - it.members["framesPerSecond"] as? Double, - it.members["framesDropped"] as? Long - ) - - peerConnectionStats[it.id as String] = tmp - } + private fun extractRelevantStats(rp: RTCStatsReport) { + rp.statsMap.values.forEach { + if (it.type == "outbound-rtp") { + val durations = it.members["qualityLimitationDurations"] as? Map<*, *> + val qualityLimitation = + QualityLimitationDurations( + durations?.get("bandwidth") as? Double ?: 0.0, + durations?.get("cpu") as? Double ?: 0.0, + durations?.get("none") as? Double ?: 0.0, + durations?.get("other") as? Double ?: 0.0 + ) + + val tmp = + RTCOutboundStats( + it.members["kind"] as? String, + it.members["rid"] as? String, + it.members["bytesSent"] as? BigInteger, + it.members["targetBitrate"] as? Double, + it.members["packetsSent"] as? Long, + it.members["framesEncoded"] as? Long, + it.members["framesPerSecond"] as? Double, + it.members["frameWidth"] as? Long, + it.members["frameHeight"] as? Long, + qualityLimitation + ) + + peerConnectionStats[it.id as String] = tmp + } else if (it.type == "inbound-rtp") { + val tmp = + RTCInboundStats( + it.members["kind"] as? String, + it.members["jitter"] as? Double, + it.members["packetsLost"] as? Int, + it.members["packetsReceived"] as? Long, + it.members["bytesReceived"] as? BigInteger, + it.members["framesReceived"] as? Int, + it.members["frameWidth"] as? Long, + it.members["frameHeight"] as? Long, + it.members["framesPerSecond"] as? Double, + it.members["framesDropped"] as? Long + ) + + peerConnectionStats[it.id as String] = tmp } } } +} /** * Enforces `SEND_ONLY` direction in case of `SEND_RECV` transceivers. diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt index 9aca2f9..e780704 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt @@ -8,132 +8,131 @@ import org.membraneframework.rtc.utils.SerializedMediaEvent import timber.log.Timber import kotlin.math.roundToLong -internal class RTCEngineCommunication - constructor( - private val engineListener: RTCEngineListener - ) { - interface RTCEngineCommunicationFactory { - fun create(listener: RTCEngineListener): RTCEngineCommunication - } +internal class RTCEngineCommunication( + private val engineListener: RTCEngineListener +) { + interface RTCEngineCommunicationFactory { + fun create(listener: RTCEngineListener): RTCEngineCommunication + } - fun connect(endpointMetadata: Metadata) { - sendEvent(Connect(endpointMetadata)) - } + fun connect(endpointMetadata: Metadata) { + sendEvent(Connect(endpointMetadata)) + } - fun updateEndpointMetadata(endpointMetadata: Metadata) { - sendEvent(UpdateEndpointMetadata(endpointMetadata)) - } + fun updateEndpointMetadata(endpointMetadata: Metadata) { + sendEvent(UpdateEndpointMetadata(endpointMetadata)) + } - fun updateTrackMetadata( - trackId: String, - trackMetadata: Metadata - ) { - sendEvent(UpdateTrackMetadata(trackId, trackMetadata)) - } + fun updateTrackMetadata( + trackId: String, + trackMetadata: Metadata + ) { + sendEvent(UpdateTrackMetadata(trackId, trackMetadata)) + } - fun setTargetTrackEncoding( - trackId: String, - encoding: TrackEncoding - ) { - sendEvent( - SelectEncoding( - trackId, - encoding.rid - ) + fun setTargetTrackEncoding( + trackId: String, + encoding: TrackEncoding + ) { + sendEvent( + SelectEncoding( + trackId, + encoding.rid ) - } + ) + } - fun renegotiateTracks() { - sendEvent(RenegotiateTracks()) - } + fun renegotiateTracks() { + sendEvent(RenegotiateTracks()) + } - fun localCandidate( - sdp: String, - sdpMLineIndex: Int - ) { - sendEvent( - LocalCandidate( - sdp, - sdpMLineIndex - ) + fun localCandidate( + sdp: String, + sdpMLineIndex: Int + ) { + sendEvent( + LocalCandidate( + sdp, + sdpMLineIndex ) - } + ) + } - fun sdpOffer( - sdp: String, - trackIdToTrackMetadata: Map, - midToTrackId: Map - ) { - sendEvent( - SdpOffer( - sdp, - trackIdToTrackMetadata, - midToTrackId - ) + fun sdpOffer( + sdp: String, + trackIdToTrackMetadata: Map, + midToTrackId: Map + ) { + sendEvent( + SdpOffer( + sdp, + trackIdToTrackMetadata, + midToTrackId ) - } + ) + } - fun disconnect() { - sendEvent(Disconnect()) - } + fun disconnect() { + sendEvent(Disconnect()) + } - private fun sendEvent(event: SendableEvent) { - val serializedMediaEvent = gson.toJson(event.serializeToMap()) - engineListener.onSendMediaEvent(serializedMediaEvent) - } + private fun sendEvent(event: SendableEvent) { + val serializedMediaEvent = gson.toJson(event.serializeToMap()) + engineListener.onSendMediaEvent(serializedMediaEvent) + } - private fun decodeEvent(event: SerializedMediaEvent): ReceivableEvent? { - val type = object : TypeToken>() {}.type + private fun decodeEvent(event: SerializedMediaEvent): ReceivableEvent? { + val type = object : TypeToken>() {}.type - val rawMessage: Map = gson.fromJson(event, type) + val rawMessage: Map = gson.fromJson(event, type) - ReceivableEvent.decode(rawMessage)?.let { - return it - } ?: run { - Timber.d("Failed to decode event $rawMessage") - return null - } + ReceivableEvent.decode(rawMessage)?.let { + return it + } ?: run { + Timber.d("Failed to decode event $rawMessage") + return null } + } - fun onEvent(serializedEvent: SerializedMediaEvent) { - when (val event = decodeEvent(serializedEvent)) { - is Connected -> engineListener.onConnected(event.data.id, event.data.otherEndpoints) - is OfferData -> engineListener.onOfferData(event.data.integratedTurnServers, event.data.tracksTypes) - is EndpointRemoved -> engineListener.onEndpointRemoved(event.data.id) - is EndpointAdded -> - engineListener.onEndpointAdded( - Endpoint(event.data.id, event.data.type, event.data.metadata, mapOf()) - ) - is EndpointUpdated -> engineListener.onEndpointUpdated(event.data.id, event.data.metadata) - is RemoteCandidate -> - engineListener.onRemoteCandidate( - event.data.candidate, - event.data.sdpMLineIndex, - event.data.sdpMid - ) - is SdpAnswer -> engineListener.onSdpAnswer(event.data.type, event.data.sdp, event.data.midToTrackId) - is TrackUpdated -> - engineListener.onTrackUpdated( - event.data.endpointId, - event.data.trackId, - event.data.metadata - ) - is TracksAdded -> - engineListener.onTracksAdded( - event.data.endpointId, - event.data.tracks - ) - is TracksRemoved -> engineListener.onTracksRemoved(event.data.endpointId, event.data.trackIds) - is EncodingSwitched -> - engineListener.onTrackEncodingChanged( - event.data.endpointId, - event.data.trackId, - event.data.encoding, - event.data.reason - ) - is VadNotification -> engineListener.onVadNotification(event.data.trackId, event.data.status) - is BandwidthEstimation -> engineListener.onBandwidthEstimation(event.data.estimation.roundToLong()) - else -> Timber.e("Failed to process unknown event: $event") - } + fun onEvent(serializedEvent: SerializedMediaEvent) { + when (val event = decodeEvent(serializedEvent)) { + is Connected -> engineListener.onConnected(event.data.id, event.data.otherEndpoints) + is OfferData -> engineListener.onOfferData(event.data.integratedTurnServers, event.data.tracksTypes) + is EndpointRemoved -> engineListener.onEndpointRemoved(event.data.id) + is EndpointAdded -> + engineListener.onEndpointAdded( + Endpoint(event.data.id, event.data.type, event.data.metadata, mapOf()) + ) + is EndpointUpdated -> engineListener.onEndpointUpdated(event.data.id, event.data.metadata) + is RemoteCandidate -> + engineListener.onRemoteCandidate( + event.data.candidate, + event.data.sdpMLineIndex, + event.data.sdpMid + ) + is SdpAnswer -> engineListener.onSdpAnswer(event.data.type, event.data.sdp, event.data.midToTrackId) + is TrackUpdated -> + engineListener.onTrackUpdated( + event.data.endpointId, + event.data.trackId, + event.data.metadata + ) + is TracksAdded -> + engineListener.onTracksAdded( + event.data.endpointId, + event.data.tracks + ) + is TracksRemoved -> engineListener.onTracksRemoved(event.data.endpointId, event.data.trackIds) + is EncodingSwitched -> + engineListener.onTrackEncodingChanged( + event.data.endpointId, + event.data.trackId, + event.data.encoding, + event.data.reason + ) + is VadNotification -> engineListener.onVadNotification(event.data.trackId, event.data.status) + is BandwidthEstimation -> engineListener.onBandwidthEstimation(event.data.estimation.roundToLong()) + else -> Timber.e("Failed to process unknown event: $event") } } +} diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalAudioTrack.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalAudioTrack.kt index 4ed197b..2123aa9 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalAudioTrack.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalAudioTrack.kt @@ -13,7 +13,7 @@ import java.util.* * * Internally it wraps a WebRTC AudioTrack. */ -class LocalAudioTrack constructor( +class LocalAudioTrack( var mediaTrack: org.webrtc.AudioTrack ) : AudioTrack(mediaTrack), LocalTrack { override fun start() { diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalScreencastTrack.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalScreencastTrack.kt index f77ba4e..99117ac 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalScreencastTrack.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalScreencastTrack.kt @@ -21,135 +21,134 @@ import java.util.* * that is responsible for capturing the entire device's screen and passing it to the WebRTC * VideoTrack. */ -class LocalScreencastTrack - constructor( - private val source: VideoSource, - mediaTrack: org.webrtc.VideoTrack, - context: Context, - eglBase: EglBase, - private val capturer: ScreenCapturerAndroid, - val videoParameters: VideoParameters, - callback: ProjectionCallback - ) : VideoTrack(mediaTrack, eglBase.eglBaseContext), LocalTrack { - private val screencastConnection = ScreencastServiceConnector(context) - private val mutex = Mutex() - private val coroutineScope: CoroutineScope = - ClosableCoroutineScope(SupervisorJob()) - private var isStopped = false - - suspend fun startForegroundService( - notificationId: Int?, - notification: Notification? - ) { - mutex.withLock { - if (!isStopped) { - screencastConnection.connect() - screencastConnection.start(notificationId, notification) - } +class LocalScreencastTrack( + private val source: VideoSource, + mediaTrack: org.webrtc.VideoTrack, + context: Context, + eglBase: EglBase, + private val capturer: ScreenCapturerAndroid, + val videoParameters: VideoParameters, + callback: ProjectionCallback +) : VideoTrack(mediaTrack, eglBase.eglBaseContext), LocalTrack { + private val screencastConnection = ScreencastServiceConnector(context) + private val mutex = Mutex() + private val coroutineScope: CoroutineScope = + ClosableCoroutineScope(SupervisorJob()) + private var isStopped = false + + suspend fun startForegroundService( + notificationId: Int?, + notification: Notification? + ) { + mutex.withLock { + if (!isStopped) { + screencastConnection.connect() + screencastConnection.start(notificationId, notification) } } + } - override fun start() { - coroutineScope.launch { - mutex.withLock { - if (!isStopped) { - capturer.startCapture( - videoParameters.dimensions.width, - videoParameters.dimensions.height, - videoParameters.maxFps - ) - } + override fun start() { + coroutineScope.launch { + mutex.withLock { + if (!isStopped) { + capturer.startCapture( + videoParameters.dimensions.width, + videoParameters.dimensions.height, + videoParameters.maxFps + ) } } } + } - override fun stop() { - coroutineScope.launch { - mutex.withLock { - isStopped = true - screencastConnection.stop() - capturer.stopCapture() - capturer.dispose() - videoTrack.dispose() - source.dispose() - } + override fun stop() { + coroutineScope.launch { + mutex.withLock { + isStopped = true + screencastConnection.stop() + capturer.stopCapture() + capturer.dispose() + videoTrack.dispose() + source.dispose() } } + } - override fun enabled(): Boolean { - return videoTrack.enabled() - } + override fun enabled(): Boolean { + return videoTrack.enabled() + } - override fun setEnabled(enabled: Boolean) { - videoTrack.setEnabled(enabled) - } + override fun setEnabled(enabled: Boolean) { + videoTrack.setEnabled(enabled) + } /* MediaProjection callback wrapper holding several callbacks that will be invoked once the media projections stops. */ - class ProjectionCallback : MediaProjection.Callback() { - var callbacks: ArrayList<() -> Unit> = arrayListOf() - - override fun onStop() { - callbacks.forEach { - it.invoke() - } + class ProjectionCallback : MediaProjection.Callback() { + var callbacks: ArrayList<() -> Unit> = arrayListOf() - callbacks.clear() + override fun onStop() { + callbacks.forEach { + it.invoke() } - fun addCallback(callback: () -> Unit) { - callbacks.add(callback) - } + callbacks.clear() } - companion object { - /** - * Creates a screencast track. - * - * @param context: context of the current application - * @param factory: an instance of PeerConnectionFactory used for creating video sources and tracks - * @param simulcastConfig: simulcast configuration. By default simulcast is disabled. - * @param eglBase: an instance of EglBase used for rendering the video - */ - fun create( - context: Context, - factory: PeerConnectionFactory, - eglBase: EglBase, - mediaProjectionPermission: Intent, - videoParameters: VideoParameters, - onStopped: (track: LocalScreencastTrack) -> Unit - ): LocalScreencastTrack { - val source = factory.createVideoSource(true) - - val track = factory.createVideoTrack(UUID.randomUUID().toString(), source) - - val callback = ProjectionCallback() - - val capturer = ScreenCapturerAndroid(mediaProjectionPermission, callback) - - capturer.initialize( - SurfaceTextureHelper.create("ScreenVideoCaptureThread", eglBase.eglBaseContext), + fun addCallback(callback: () -> Unit) { + callbacks.add(callback) + } + } + + companion object { + /** + * Creates a screencast track. + * + * @param context: context of the current application + * @param factory: an instance of PeerConnectionFactory used for creating video sources and tracks + * @param simulcastConfig: simulcast configuration. By default simulcast is disabled. + * @param eglBase: an instance of EglBase used for rendering the video + */ + fun create( + context: Context, + factory: PeerConnectionFactory, + eglBase: EglBase, + mediaProjectionPermission: Intent, + videoParameters: VideoParameters, + onStopped: (track: LocalScreencastTrack) -> Unit + ): LocalScreencastTrack { + val source = factory.createVideoSource(true) + + val track = factory.createVideoTrack(UUID.randomUUID().toString(), source) + + val callback = ProjectionCallback() + + val capturer = ScreenCapturerAndroid(mediaProjectionPermission, callback) + + capturer.initialize( + SurfaceTextureHelper.create("ScreenVideoCaptureThread", eglBase.eglBaseContext), + context, + source.capturerObserver + ) + + val localScreencastTrack = + LocalScreencastTrack( + source, + track, context, - source.capturerObserver + eglBase, + capturer, + videoParameters, + callback ) - - val localScreencastTrack = - LocalScreencastTrack( - source, - track, - context, - eglBase, - capturer, - videoParameters, - callback - ) - callback.addCallback { - onStopped(localScreencastTrack) - } - - return localScreencastTrack + callback.addCallback { + onStopped(localScreencastTrack) } + + return localScreencastTrack } } +} diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalVideoTrack.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalVideoTrack.kt index 2a543e0..e045916 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalVideoTrack.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/LocalVideoTrack.kt @@ -10,79 +10,78 @@ import java.util.* * * Internally it wraps a WebRTC VideoTrack. */ -class LocalVideoTrack - constructor( - mediaTrack: org.webrtc.VideoTrack, - private val capturer: Capturer, - eglBase: EglBase, - val videoParameters: VideoParameters - ) : VideoTrack(mediaTrack, eglBase.eglBaseContext), LocalTrack { - data class CaptureDevice(val deviceName: String, val isFrontFacing: Boolean, val isBackFacing: Boolean) - - companion object { - fun create( - context: Context, - factory: PeerConnectionFactory, - eglBase: EglBase, - videoParameters: VideoParameters, - cameraName: String? = null - ): LocalVideoTrack { - val source = factory.createVideoSource(false) - val track = factory.createVideoTrack(UUID.randomUUID().toString(), source) - - val capturer = - CameraCapturer( - context = context, - source = source, - rootEglBase = eglBase, - videoParameters = videoParameters, - cameraName - ) - - return LocalVideoTrack(track, capturer, eglBase, videoParameters) - } +class LocalVideoTrack( + mediaTrack: org.webrtc.VideoTrack, + private val capturer: Capturer, + eglBase: EglBase, + val videoParameters: VideoParameters +) : VideoTrack(mediaTrack, eglBase.eglBaseContext), LocalTrack { + data class CaptureDevice(val deviceName: String, val isFrontFacing: Boolean, val isBackFacing: Boolean) + + companion object { + fun create( + context: Context, + factory: PeerConnectionFactory, + eglBase: EglBase, + videoParameters: VideoParameters, + cameraName: String? = null + ): LocalVideoTrack { + val source = factory.createVideoSource(false) + val track = factory.createVideoTrack(UUID.randomUUID().toString(), source) + + val capturer = + CameraCapturer( + context = context, + source = source, + rootEglBase = eglBase, + videoParameters = videoParameters, + cameraName + ) + + return LocalVideoTrack(track, capturer, eglBase, videoParameters) + } - fun getCaptureDevices(context: Context): List { - val enumerator = - if (Camera2Enumerator.isSupported(context)) { - Camera2Enumerator(context) - } else { - Camera1Enumerator(true) - } - return enumerator.deviceNames.map { name -> - CaptureDevice( - name, - enumerator.isFrontFacing(name), - enumerator.isBackFacing(name) - ) + fun getCaptureDevices(context: Context): List { + val enumerator = + if (Camera2Enumerator.isSupported(context)) { + Camera2Enumerator(context) + } else { + Camera1Enumerator(true) } + return enumerator.deviceNames.map { name -> + CaptureDevice( + name, + enumerator.isFrontFacing(name), + enumerator.isBackFacing(name) + ) } } + } - override fun start() { - capturer.startCapture() - } + override fun start() { + capturer.startCapture() + } - override fun stop() { - capturer.stopCapture() - } + override fun stop() { + capturer.stopCapture() + } - override fun enabled(): Boolean { - return videoTrack.enabled() - } + override fun enabled(): Boolean { + return videoTrack.enabled() + } - override fun setEnabled(enabled: Boolean) { - videoTrack.setEnabled(enabled) - } + override fun setEnabled(enabled: Boolean) { + videoTrack.setEnabled(enabled) + } - fun flipCamera() { - (capturer as? CameraCapturer)?.flipCamera() - } + fun flipCamera() { + (capturer as? CameraCapturer)?.flipCamera() + } - fun switchCamera(deviceName: String) { - (capturer as? CameraCapturer)?.switchCamera(deviceName) - } + fun switchCamera(deviceName: String) { + (capturer as? CameraCapturer)?.switchCamera(deviceName) } +} interface Capturer { fun capturer(): VideoCapturer @@ -92,7 +91,7 @@ interface Capturer { fun stopCapture() } -class CameraCapturer constructor( +class CameraCapturer( private val context: Context, private val source: VideoSource, private val rootEglBase: EglBase, diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/RemoteAudioTrack.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/RemoteAudioTrack.kt index eaf9610..fdf008b 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/media/RemoteAudioTrack.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/media/RemoteAudioTrack.kt @@ -1,6 +1,6 @@ package org.membraneframework.rtc.media -class RemoteAudioTrack constructor( +class RemoteAudioTrack( mediaTrack: org.webrtc.AudioTrack ) : AudioTrack(mediaTrack), RemoteTrack { override fun enabled(): Boolean { diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/transport/PhoenixTransport.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/transport/PhoenixTransport.kt index 6b6e4b6..d096f4c 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/transport/PhoenixTransport.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/transport/PhoenixTransport.kt @@ -39,7 +39,7 @@ interface PhoenixTransportListener { fun onClose() } -class PhoenixTransport constructor( +class PhoenixTransport( private val url: String, private val topic: String, private val ioDispatcher: CoroutineDispatcher, From ebcf5cc07c1f8f5fa5ca7389e3f75dfd3b375ff3 Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Thu, 9 May 2024 17:11:01 +0200 Subject: [PATCH 6/7] change val/var and update gradle --- .../jellyfishclient/JellyfishClientInternal.kt | 8 ++++---- .../java/org/membraneframework/rtc/InternalMembraneRTC.kt | 4 ++-- .../membraneframework/rtc/PeerConnectionFactoryWrapper.kt | 2 +- app/src/main/AndroidManifest.xml | 1 - build.gradle | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt index 0e62db2..1e91717 100644 --- a/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt +++ b/JellyfishClient/src/main/java/com/jellyfishdev/jellyfishclient/JellyfishClientInternal.kt @@ -133,22 +133,22 @@ internal class JellyfishClientInternal( } override fun onTrackAdded(ctx: TrackContext) { - var trackContext = TrackContext(ctx) + val trackContext = TrackContext(ctx) listener.onTrackAdded(trackContext) } override fun onTrackReady(ctx: TrackContext) { - var trackContext = TrackContext(ctx) + val trackContext = TrackContext(ctx) listener.onTrackReady(trackContext) } override fun onTrackRemoved(ctx: TrackContext) { - var trackContext = TrackContext(ctx) + val trackContext = TrackContext(ctx) listener.onTrackRemoved(trackContext) } override fun onTrackUpdated(ctx: TrackContext) { - var trackContext = TrackContext(ctx) + val trackContext = TrackContext(ctx) listener.onTrackUpdated(trackContext) } diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt index 71df822..f6a1cff 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/InternalMembraneRTC.kt @@ -27,9 +27,9 @@ import timber.log.Timber import java.util.* internal class InternalMembraneRTC( - private val createOptions: CreateOptions, + createOptions: CreateOptions, private val listener: MembraneRTCListener, - private val defaultDispatcher: CoroutineDispatcher, + defaultDispatcher: CoroutineDispatcher, private val eglBase: EglBase, private val context: Context ) : RTCEngineListener, PeerConnectionListener { diff --git a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt index 546b1c4..615e04f 100644 --- a/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt +++ b/JellyfishClient/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt @@ -9,7 +9,7 @@ import org.webrtc.PeerConnectionFactory import org.webrtc.audio.AudioDeviceModule internal class PeerConnectionFactoryWrapper( - private val createOptions: CreateOptions, + createOptions: CreateOptions, audioDeviceModule: AudioDeviceModule, eglBase: EglBase, appContext: Context diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 063ee0d..9e32881 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,7 +21,6 @@ diff --git a/build.gradle b/build.gradle index 7618614..8bb5e3c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.1' + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" From 9ae8f1bd77a1aebee3288468300cd7eadf1ddb0d Mon Sep 17 00:00:00 2001 From: Miron Pawlik Date: Mon, 20 May 2024 13:57:39 +0200 Subject: [PATCH 7/7] Update readme --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a2a5857..d218f6d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Jellyfish Android Client Android client library for [Jellyfish](https://github.com/jellyfish-dev/jellyfish). @@ -33,20 +32,22 @@ Add the dependency: Make sure you have: - Running [Jellyfish](https://github.com/jellyfish-dev/jellyfish) server. -- Created room and token of peer in that room. - You can use [dashboard](https://jellyfish-dev.github.io/jellyfish-dashboard/) example to create room and peer token. +- Created room and token of peer in that room. You can use + [dashboard](https://jellyfish-dev.github.io/jellyfish-dashboard/) example to create room and peer token. You can refer to our minimal example on how to use this library. ## Development 1. Set `JELLYFISH_SOCKET_URL` in `~/.gradle/gradle.properties` to your dev backend. -2. Run `./gradlew formatKotlin` to format code. -3. Run `release-it` to release. Follow the prompts, it should add a commit and a git tag and jitpack should pick it up automatically and put the new version in the jitpack repo. +2. Run `ktlint` to format code (if missing, install it with `brew install ktlint`) +3. Run `release-it` to release. Follow the prompts, it should add a commit and a git tag and jitpack should pick it up + automatically and put the new version in the jitpack repo. ## Contributing -We welcome contributions to this SDK. Please report any bugs or issues you find or feel free to make a pull request with your own bug fixes and/or features.` +We welcome contributions to this SDK. Please report any bugs or issues you find or feel free to make a pull request with +your own bug fixes and/or features.` ## Jellyfish Ecosystem @@ -54,7 +55,7 @@ We welcome contributions to this SDK. Please report any bugs or issues you find | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Client SDKs | [React](https://github.com/jellyfish-dev/react-client-sdk), [React Native](https://github.com/jellyfish-dev/react-native-client-sdk), [iOs](https://github.com/jellyfish-dev/ios-client-sdk), [Android](https://github.com/jellyfish-dev/android-client-sdk) | | Server SDKs | [Elixir](https://github.com/jellyfish-dev/elixir_server_sdk), [Python](https://github.com/jellyfish-dev/python-server-sdk), [OpenAPI](https://jellyfish-dev.github.io/jellyfish-docs/api_reference/rest_api) | -| Services | [Videoroom](https://github.com/jellyfish-dev/jellyfish_videoroom) - an example videoconferencing app written in elixir
[Dashboard](https://github.com/jellyfish-dev/jellyfish-dashboard) - an internal tool used to showcase Jellyfish's capabilities | +| Services | [Videoroom](https://github.com/jellyfish-dev/jellyfish_videoroom) - an example videoconferencing app written in elixir
[Dashboard](https://github.com/jellyfish-dev/jellyfish-dashboard) - an internal tool used to showcase Jellyfish's capabilities | | Resources | [Jellyfish Book](https://jellyfish-dev.github.io/book/) - theory of the framework, [Docs](https://jellyfish-dev.github.io/jellyfish-docs/), [Tutorials](https://github.com/jellyfish-dev/jellyfish-clients-tutorials) | | Membrane | Jellyfish is based on [Membrane](https://membrane.stream/), [Discord](https://discord.gg/nwnfVSY) | | Compositor | [Compositor](https://github.com/membraneframework/membrane_video_compositor_plugin) - Membrane plugin to transform video |