diff --git a/.editorconfig b/.editorconfig
index b8777ff..139e176 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,8 @@
[*.{kt,kts}]
-ktlint_disabled_rules = no-wildcard-imports,filename
+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
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
@@ -7,7 +10,7 @@ ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthet
indent_size = 4
indent_style = space
insert_final_newline = true
-ktlint_code_style = official
+ktlint_code_style = ktlint_official
ktlint_ignore_back_ticked_identifier = false
ktlint_standard_no-wildcard-imports= disabled
max_line_length = 120
diff --git a/.github/workflows/run_lint_and_tests.yaml b/.github/workflows/run_lint_and_tests.yaml
index 4ba1eb9..0dae1a6 100644
--- a/.github/workflows/run_lint_and_tests.yaml
+++ b/.github/workflows/run_lint_and_tests.yaml
@@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v3
- name: Download ktlint
- run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.47.1/ktlint && chmod a+x ktlint && sudo mv ktlint /usr/local/bin/
+ 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
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTC.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTC.kt
index d47a64b..92b1ec4 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTC.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTC.kt
@@ -35,224 +35,241 @@ import org.webrtc.Logging
* or going inactive.
*/
class MembraneRTC
-private constructor(
- private var client: InternalMembraneRTC
-) {
- /**
- * Tries to connect the RTC Engine. If user is accepted then onConnected will be called.
- * In other case {@link Callbacks.onConnectError} is invoked.
- *
- * @param endpointMetadata - Any information that other endpoints will receive in onEndpointAdded
- * after accepting this endpoint
- */
- fun connect(endpointMetadata: Metadata) {
- client.connect(endpointMetadata)
- }
-
- /**
- * Disconnects the client.
- *
- * Once the client gets disconnected it should not be reused. New client should be created instead.
- */
- fun disconnect() {
- client.disconnect()
- }
+ private constructor(
+ private var client: InternalMembraneRTC
+ ) {
+ /**
+ * Tries to connect the RTC Engine. If user is accepted then onConnected will be called.
+ * In other case {@link Callbacks.onConnectError} is invoked.
+ *
+ * @param endpointMetadata - Any information that other endpoints will receive in onEndpointAdded
+ * after accepting this endpoint
+ */
+ fun connect(endpointMetadata: Metadata) {
+ client.connect(endpointMetadata)
+ }
- /**
- * Feeds media event received from RTC Engine to MembraneWebRTC.
- * This function should be called whenever some media event from RTC Engine
- * was received and can result in MembraneWebRTC generating some other
- * media events.
- * @param mediaEvent - String data received over custom signalling layer.
- */
- fun receiveMediaEvent(mediaEvent: SerializedMediaEvent) {
- client.receiveMediaEvent(mediaEvent)
- }
+ /**
+ * Disconnects the client.
+ *
+ * Once the client gets disconnected it should not be reused. New client should be created instead.
+ */
+ fun disconnect() {
+ client.disconnect()
+ }
- /**
- * Creates a video track utilizing device's camera.
- *
- * The client assumes that the user has already granted camera permissions.
- *
- * @param videoParameters a set of target parameters such as camera resolution, frame rate or simulcast configuration
- * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
- * @param captureDeviceName the name of the device to start video capture with, you can get device name by using
- * `LocalVideoTrack.getCaptureDevices` method
- * @return an instance of the video track
- */
- fun createVideoTrack(
- videoParameters: VideoParameters,
- metadata: Metadata,
- captureDeviceName: String? = null
- ): LocalVideoTrack {
- return client.createLocalVideoTrack(videoParameters, metadata, captureDeviceName)
- }
+ /**
+ * Feeds media event received from RTC Engine to MembraneWebRTC.
+ * This function should be called whenever some media event from RTC Engine
+ * was received and can result in MembraneWebRTC generating some other
+ * media events.
+ * @param mediaEvent - String data received over custom signalling layer.
+ */
+ fun receiveMediaEvent(mediaEvent: SerializedMediaEvent) {
+ client.receiveMediaEvent(mediaEvent)
+ }
- /**
- * Creates an audio track utilizing device's microphone.
- *
- * The client assumes that the user has already granted microphone recording permissions.
- *
- * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
- * @return an instance of the audio track
- */
- fun createAudioTrack(metadata: Metadata): LocalAudioTrack {
- return client.createLocalAudioTrack(metadata)
- }
+ /**
+ * Creates a video track utilizing device's camera.
+ *
+ * The client assumes that the user has already granted camera permissions.
+ *
+ * @param videoParameters a set of target parameters such as camera resolution, frame rate or simulcast configuration
+ * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
+ * @param captureDeviceName the name of the device to start video capture with, you can get device name by using
+ * `LocalVideoTrack.getCaptureDevices` method
+ * @return an instance of the video track
+ */
+ fun createVideoTrack(
+ videoParameters: VideoParameters,
+ metadata: Metadata,
+ captureDeviceName: String? = null
+ ): LocalVideoTrack {
+ return client.createLocalVideoTrack(videoParameters, metadata, captureDeviceName)
+ }
- /**
- * Creates a screen track recording the entire device's screen.
- *
- * The method requires a media projection permission to be able to start the recording. The client assumes that the intent is valid.
- *
- * @param mediaProjectionPermission a valid media projection permission intent that can be used to starting a screen capture
- * @param videoParameters a set of target parameters of the screen capture such as resolution, frame rate or simulcast configuration
- * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
- * @param onEnd callback that will be invoked once the screen capture ends
- * @return an instance of the screencast track
- */
- fun createScreencastTrack(
- mediaProjectionPermission: Intent,
- videoParameters: VideoParameters,
- metadata: Metadata,
- onEnd: (() -> Unit)? = null
- ): LocalScreencastTrack {
- return client.createScreencastTrack(mediaProjectionPermission, videoParameters, metadata, onEnd)
- }
+ /**
+ * Creates an audio track utilizing device's microphone.
+ *
+ * The client assumes that the user has already granted microphone recording permissions.
+ *
+ * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
+ * @return an instance of the audio track
+ */
+ fun createAudioTrack(metadata: Metadata): LocalAudioTrack {
+ return client.createLocalAudioTrack(metadata)
+ }
- /**
- * Removes an instance of local track from the client.
- *
- * @param trackId an id of a valid local track that has been created using the current client
- * @return a boolean whether the track has been successfully removed or not
- */
- fun removeTrack(trackId: String): Boolean {
- return client.removeTrack(trackId)
- }
+ /**
+ * Creates a screen track recording the entire device's screen.
+ *
+ * The method requires a media projection permission to be able to start the recording. The client assumes that the intent is valid.
+ *
+ * @param mediaProjectionPermission a valid media projection permission intent that can be used to starting a screen capture
+ * @param videoParameters a set of target parameters of the screen capture such as resolution, frame rate or simulcast configuration
+ * @param metadata the metadata that will be sent to the Membrane RTC Engine for media negotiation
+ * @param onEnd callback that will be invoked once the screen capture ends
+ * @return an instance of the screencast track
+ */
+ fun createScreencastTrack(
+ mediaProjectionPermission: Intent,
+ videoParameters: VideoParameters,
+ metadata: Metadata,
+ onEnd: (() -> Unit)? = null
+ ): LocalScreencastTrack {
+ return client.createScreencastTrack(mediaProjectionPermission, videoParameters, metadata, onEnd)
+ }
- /**
- * Sets track encoding that server should send to the client library.
- *
- * The encoding will be sent whenever it is available.
- * If chosen encoding is temporarily unavailable, some other encoding
- * will be sent until chosen encoding becomes active again.
- *
- * @param trackId an id of a remote track
- * @param encoding an encoding to receive
- */
- fun setTargetTrackEncoding(trackId: String, encoding: TrackEncoding) {
- client.setTargetTrackEncoding(trackId, encoding)
- }
+ /**
+ * Removes an instance of local track from the client.
+ *
+ * @param trackId an id of a valid local track that has been created using the current client
+ * @return a boolean whether the track has been successfully removed or not
+ */
+ fun removeTrack(trackId: String): Boolean {
+ return client.removeTrack(trackId)
+ }
- /**
- * Enables track encoding so that it will be sent to the server.
- *
- * @param trackId an id of a local track
- * @param encoding an encoding that will be enabled
- */
- fun enableTrackEncoding(trackId: String, encoding: TrackEncoding) {
- client.enableTrackEncoding(trackId, encoding)
- }
+ /**
+ * Sets track encoding that server should send to the client library.
+ *
+ * The encoding will be sent whenever it is available.
+ * If chosen encoding is temporarily unavailable, some other encoding
+ * will be sent until chosen encoding becomes active again.
+ *
+ * @param trackId an id of a remote track
+ * @param encoding an encoding to receive
+ */
+ fun setTargetTrackEncoding(
+ trackId: String,
+ encoding: TrackEncoding
+ ) {
+ client.setTargetTrackEncoding(trackId, encoding)
+ }
- /**
- * Disables track encoding so that it will be no longer sent to the server.
- *
- * @param trackId and id of a local track
- * @param encoding an encoding that will be disabled
- */
- fun disableTrackEncoding(trackId: String, encoding: TrackEncoding) {
- client.disableTrackEncoding(trackId, encoding)
- }
+ /**
+ * Enables track encoding so that it will be sent to the server.
+ *
+ * @param trackId an id of a local track
+ * @param encoding an encoding that will be enabled
+ */
+ fun enableTrackEncoding(
+ trackId: String,
+ encoding: TrackEncoding
+ ) {
+ client.enableTrackEncoding(trackId, encoding)
+ }
- /**
- * Updates the metadata for the current endpoint.
- * @param endpointMetadata Data about this endpoint that other endpoints will receive upon connecting.
- *
- * If the metadata is different from what is already tracked in the room, the optional
- * callback `onEndpointUpdated` will be triggered for other endpoints in the room.
- */
- fun updateEndpointMetadata(endpointMetadata: Metadata) {
- client.updateEndpointMetadata(endpointMetadata)
- }
+ /**
+ * Disables track encoding so that it will be no longer sent to the server.
+ *
+ * @param trackId and id of a local track
+ * @param encoding an encoding that will be disabled
+ */
+ fun disableTrackEncoding(
+ trackId: String,
+ encoding: TrackEncoding
+ ) {
+ client.disableTrackEncoding(trackId, encoding)
+ }
- /**
- * Updates the metadata for a specific track.
- * @param trackId local track id of audio or video track.
- * @param trackMetadata Data about this track that other endpoints will receive upon connecting.
- *
- * If the metadata is different from what is already tracked in the room, the optional
- * callback `onTrackUpdated` will be triggered for other endpoints in the room.
- */
- fun updateTrackMetadata(trackId: String, trackMetadata: Metadata) {
- client.updateTrackMetadata(trackId, trackMetadata)
- }
+ /**
+ * Updates the metadata for the current endpoint.
+ * @param endpointMetadata Data about this endpoint that other endpoints will receive upon connecting.
+ *
+ * If the metadata is different from what is already tracked in the room, the optional
+ * callback `onEndpointUpdated` will be triggered for other endpoints in the room.
+ */
+ fun updateEndpointMetadata(endpointMetadata: Metadata) {
+ client.updateEndpointMetadata(endpointMetadata)
+ }
- /**
- * Updates maximum bandwidth for the track identified by trackId.
- * This value directly translates to quality of the stream and, in case of video, to the amount of RTP packets being sent.
- * In case trackId points at the simulcast track bandwidth is split between all of the variant streams proportionally to their resolution.
- * @param trackId track id of a video track
- * @param bandwidthLimit bandwidth in kbps
- */
- fun setTrackBandwidth(trackId: String, bandwidthLimit: TrackBandwidthLimit.BandwidthLimit) {
- client.setTrackBandwidth(trackId, bandwidthLimit)
- }
+ /**
+ * Updates the metadata for a specific track.
+ * @param trackId local track id of audio or video track.
+ * @param trackMetadata Data about this track that other endpoints will receive upon connecting.
+ *
+ * If the metadata is different from what is already tracked in the room, the optional
+ * callback `onTrackUpdated` will be triggered for other endpoints in the room.
+ */
+ fun updateTrackMetadata(
+ trackId: String,
+ trackMetadata: Metadata
+ ) {
+ client.updateTrackMetadata(trackId, trackMetadata)
+ }
- /**
- * Updates maximum bandwidth for the given simulcast encoding of the given track.
- * @param trackId track id of a video track
- * @param encoding rid of the encoding
- * @param bandwidthLimit bandwidth in kbps
- */
- fun setEncodingBandwidth(
- trackId: String,
- encoding: String,
- bandwidthLimit: TrackBandwidthLimit.BandwidthLimit
- ) {
- client.setEncodingBandwidth(trackId, encoding, bandwidthLimit)
- }
+ /**
+ * Updates maximum bandwidth for the track identified by trackId.
+ * This value directly translates to quality of the stream and, in case of video, to the amount of RTP packets being sent.
+ * In case trackId points at the simulcast track bandwidth is split between all of the variant streams proportionally to their resolution.
+ * @param trackId track id of a video track
+ * @param bandwidthLimit bandwidth in kbps
+ */
+ fun setTrackBandwidth(
+ trackId: String,
+ bandwidthLimit: TrackBandwidthLimit.BandwidthLimit
+ ) {
+ client.setTrackBandwidth(trackId, bandwidthLimit)
+ }
- /**
- * Changes severity level of debug logs
- * @param severity enum value representing the logging severity
- */
- fun changeWebRTCLoggingSeverity(severity: Logging.Severity) {
- Logging.enableLogToDebugOutput(severity)
- }
+ /**
+ * Updates maximum bandwidth for the given simulcast encoding of the given track.
+ * @param trackId track id of a video track
+ * @param encoding rid of the encoding
+ * @param bandwidthLimit bandwidth in kbps
+ */
+ fun setEncodingBandwidth(
+ trackId: String,
+ encoding: String,
+ bandwidthLimit: TrackBandwidthLimit.BandwidthLimit
+ ) {
+ client.setEncodingBandwidth(trackId, encoding, bandwidthLimit)
+ }
- /**
- * Returns current connection stats
- * @return a map containing statistics
- */
- fun getStats(): Map {
- return client.getStats()
- }
+ /**
+ * Changes severity level of debug logs
+ * @param severity enum value representing the logging severity
+ */
+ fun changeWebRTCLoggingSeverity(severity: Logging.Severity) {
+ Logging.enableLogToDebugOutput(severity)
+ }
- companion object {
/**
- * Creates an instance of MembraneRTC client.
- *
- * @param appContext the context of the current application
- * @param listener a listener that will receive all notifications emitted by the MembraneRTC
- * @param options a set of options defining parameters such as encoder parameters
- * @return an instance of the client in connecting state
+ * Returns current connection stats
+ * @return a map containing statistics
*/
- fun create(
- appContext: Context,
- listener: MembraneRTCListener,
- options: CreateOptions = CreateOptions()
- ): MembraneRTC {
- val ctx = appContext.applicationContext
+ fun getStats(): Map {
+ return client.getStats()
+ }
+
+ companion object {
+ /**
+ * Creates an instance of MembraneRTC client.
+ *
+ * @param appContext the context of the current application
+ * @param listener a listener that will receive all notifications emitted by the MembraneRTC
+ * @param options a set of options defining parameters such as encoder parameters
+ * @return an instance of the client in connecting state
+ */
+ fun create(
+ appContext: Context,
+ listener: MembraneRTCListener,
+ options: CreateOptions = CreateOptions()
+ ): MembraneRTC {
+ val ctx = appContext.applicationContext
- val component = DaggerMembraneRTCComponent
- .factory()
- .create(ctx)
+ val component =
+ DaggerMembraneRTCComponent
+ .factory()
+ .create(ctx)
- val client = component
- .membraneRTCFactory()
- .create(options, listener, Dispatchers.Default)
+ val client =
+ component
+ .membraneRTCFactory()
+ .create(options, listener, Dispatchers.Default)
- return MembraneRTC(client)
+ return MembraneRTC(client)
+ }
}
}
-}
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCError.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCError.kt
index fcc35fd..799dfc8 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCError.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCError.kt
@@ -2,7 +2,9 @@ package org.membraneframework.rtc
sealed class MembraneRTCError : Exception() {
data class RTC(val reason: String) : MembraneRTCError()
+
data class Transport(val reason: String) : MembraneRTCError()
+
data class Unknown(val reason: String) : MembraneRTCError()
override fun toString(): String {
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCListener.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCListener.kt
index ff148d6..ec7b1e6 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCListener.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/MembraneRTCListener.kt
@@ -10,7 +10,10 @@ interface MembraneRTCListener {
fun onSendMediaEvent(event: SerializedMediaEvent)
// Callback invoked when the client has been approved to participate in media exchange.
- fun onConnected(endpointID: String, otherEndpoints: List)
+ fun onConnected(
+ endpointID: String,
+ otherEndpoints: List
+ )
// Called when endpoint of this MembraneRTC instance was removed
fun onDisconnected()
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt
index 678e47f..8f2080c 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionFactoryWrapper.kt
@@ -9,39 +9,39 @@ import org.webrtc.*
import org.webrtc.audio.AudioDeviceModule
internal class PeerConnectionFactoryWrapper
-@AssistedInject constructor(
- @Assisted private val createOptions: CreateOptions,
- audioDeviceModule: AudioDeviceModule,
- eglBase: EglBase,
- appContext: Context
-) {
- @AssistedFactory
- interface PeerConnectionFactoryWrapperFactory {
- fun create(
- createOptions: CreateOptions
- ): PeerConnectionFactoryWrapper
- }
+ @AssistedInject
+ constructor(
+ @Assisted private val createOptions: CreateOptions,
+ audioDeviceModule: AudioDeviceModule,
+ eglBase: EglBase,
+ appContext: Context
+ ) {
+ @AssistedFactory
+ 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/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionListener.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionListener.kt
index fc5ada7..a0ae770 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionListener.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionListener.kt
@@ -4,6 +4,10 @@ import org.webrtc.IceCandidate
import org.webrtc.MediaStreamTrack
internal interface PeerConnectionListener {
- fun onAddTrack(trackId: String, track: MediaStreamTrack)
+ fun onAddTrack(
+ trackId: String,
+ track: MediaStreamTrack
+ )
+
fun onLocalIceCandidate(candidate: IceCandidate)
}
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt
index a6970d2..8474510 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/PeerConnectionManager.kt
@@ -25,510 +25,551 @@ import java.util.*
import kotlin.math.pow
internal class PeerConnectionManager
-@AssistedInject constructor(
- @Assisted private val peerConnectionListener: PeerConnectionListener,
- @Assisted private val peerConnectionFactory: PeerConnectionFactoryWrapper
-) : PeerConnection.Observer {
-
- @AssistedFactory
- interface PeerConnectionManagerFactory {
- fun create(
- listener: PeerConnectionListener,
- peerConnectionFactory: PeerConnectionFactoryWrapper
- ): PeerConnectionManager
- }
-
- 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 val coroutineScope: CoroutineScope =
- ClosableCoroutineScope(SupervisorJob())
-
- 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
+ @AssistedInject
+ constructor(
+ @Assisted private val peerConnectionListener: PeerConnectionListener,
+ @Assisted private val peerConnectionFactory: PeerConnectionFactoryWrapper
+ ) : PeerConnection.Observer {
+ @AssistedFactory
+ interface PeerConnectionManagerFactory {
+ fun create(
+ listener: PeerConnectionListener,
+ peerConnectionFactory: PeerConnectionFactoryWrapper
+ ): PeerConnectionManager
+ }
+
+ 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 val coroutineScope: CoroutineScope =
+ ClosableCoroutineScope(SupervisorJob())
+
+ 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
}
- 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
+ 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))
- }
+ 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
- }
+ peerConnectionMutex.withLock {
+ val pc =
+ peerConnection ?: run {
+ Timber.e("addTrack: Peer connection not yet established")
+ return
+ }
+
+ if (videoParameters?.maxBitrate != null) {
+ applyBitrate(sendEncodings, videoParameters.maxBitrate)
+ }
- if (videoParameters?.maxBitrate != null) {
- applyBitrate(sendEncodings, videoParameters.maxBitrate)
+ pc.addTransceiver(
+ track.rtcTrack(),
+ RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
+ streamIds,
+ sendEncodings
+ )
+ pc.enforceSendOnlyDirection()
}
-
- 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
- }
-
- val k0 = encodings.minByOrNull { it.scaleResolutionDownBy ?: 1.0 }
-
- val bitrateParts = encodings.sumOf {
- ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow(
- 2
- )
- }
-
- val x = maxBitrate.limit / bitrateParts
-
- 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")
+ 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
}
- val sender = pc.senders.find { it.track()?.id() == trackId } ?: run {
- Timber.e("setTrackBandwidth: Invalid trackId: track sender not found")
+ if (maxBitrate.limit == 0) {
+ encodings.forEach { it.maxBitrateBps = null }
return
}
- val params = sender.parameters
- applyBitrate(params.getEncodings(), bandwidthLimit)
+ val k0 = encodings.minByOrNull { it.scaleResolutionDownBy ?: 1.0 }
- sender.parameters = params
- }
- }
+ val bitrateParts =
+ encodings.sumOf {
+ ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow(
+ 2
+ )
+ }
- 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 x = maxBitrate.limit / bitrateParts
- val params = sender.parameters
- val encodingParameters = params.encodings.find { it.rid == encoding } ?: run {
- Timber.e("setEncodingBandwidth: Invalid encoding: encoding not found")
- return
+ encodings.forEach {
+ it.maxBitrateBps =
+ (x * ((k0?.scaleResolutionDownBy ?: 1.0) / (it.scaleResolutionDownBy ?: 1.0)).pow(2) * 1024).toInt()
}
+ }
- encodingParameters.maxBitrateBps = bandwidthLimit.limit * 1024
+ 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
+ }
+ }
- 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
+ }
+
+ 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")
+ 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
+ }
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)
+ private suspend fun drainCandidates() {
+ qrcMutex.withLock {
+ this.queuedRemoteCandidates?.let {
+ for (candidate in it) {
+ this.peerConnection?.addIceCandidate(candidate)
+ }
+ this.queuedRemoteCandidates = null
}
- 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("")
+ 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()
+ }
- 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()
-
- val mapping = mutableMapOf()
- pc.transceivers.forEach {
- val trackId = it.sender.track()?.id() ?: return@forEach
+ private fun midToTrackIdMapping(localTracks: List): Map {
+ val pc = peerConnection ?: return emptyMap()
- if (!localTracks.map { track -> track.id() }.contains(trackId)) return@forEach
+ val mapping = mutableMapOf()
- mapping[it.mid] = trackId
- }
+ pc.transceivers.forEach {
+ val trackId = it.sender.track()?.id() ?: return@forEach
- return mapping
- }
+ if (!localTracks.map { track -> track.id() }.contains(trackId)) return@forEach
- data class SdpOffer(
- val description: String,
- val midToTrackIdMapping: Map
- )
+ mapping[it.mid] = trackId
+ }
- suspend fun getSdpOffer(
- integratedTurnServers: List,
- tracksTypes: Map,
- localTracks: List
- ): SdpOffer {
- qrcMutex.withLock {
- this@PeerConnectionManager.queuedRemoteCandidates = mutableListOf()
+ return mapping
}
- prepareIceServers(integratedTurnServers)
- var needsRestart = true
- if (peerConnection == null) {
- setupPeerConnection(localTracks)
- needsRestart = false
- }
- peerConnectionMutex.withLock {
- val pc = peerConnection!!
+ data class SdpOffer(
+ val description: String,
+ val midToTrackIdMapping: Map
+ )
- if (needsRestart) {
- pc.restartIce()
+ suspend fun getSdpOffer(
+ integratedTurnServers: List,
+ tracksTypes: Map,
+ localTracks: List
+ ): SdpOffer {
+ qrcMutex.withLock {
+ this@PeerConnectionManager.queuedRemoteCandidates = mutableListOf()
}
+ prepareIceServers(integratedTurnServers)
- addNecessaryTransceivers(tracksTypes)
+ var needsRestart = true
+ if (peerConnection == null) {
+ setupPeerConnection(localTracks)
+ needsRestart = false
+ }
+ peerConnectionMutex.withLock {
+ val pc = peerConnection!!
- pc.transceivers.forEach {
- if (it.direction == RtpTransceiver.RtpTransceiverDirection.SEND_RECV) {
- it.direction = RtpTransceiver.RtpTransceiverDirection.SEND_ONLY
+ if (needsRestart) {
+ pc.restartIce()
}
- }
- val constraints = MediaConstraints()
+ addNecessaryTransceivers(tracksTypes)
- Timber.i("Creating offer")
- val offer = pc.createOffer(constraints).getOrThrow()
+ pc.transceivers.forEach {
+ if (it.direction == RtpTransceiver.RtpTransceiverDirection.SEND_RECV) {
+ it.direction = RtpTransceiver.RtpTransceiverDirection.SEND_ONLY
+ }
+ }
- Timber.i("Setting local description")
- pc.setLocalDescription(offer).getOrThrow()
+ val constraints = MediaConstraints()
- return SdpOffer(offer.description, midToTrackIdMapping(localTracks))
- }
- }
+ Timber.i("Creating offer")
+ val offer = pc.createOffer(constraints).getOrThrow()
- 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
+ Timber.i("Setting local description")
+ pc.setLocalDescription(offer).getOrThrow()
+
+ return SdpOffer(offer.description, midToTrackIdMapping(localTracks))
}
- 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
+ }
+
+ 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
}
- 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
+ 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 transceiver =
+ pc.transceivers.find {
+ it.receiver.id() == receiver?.id()
+ } ?: return@launch
- val mid = transceiver.mid
+ val mid = transceiver.mid
- trackId = midToTrackId[mid] ?: run {
- Timber.e("onAddTrack: Track with mid=$mid not found")
- return@launch
+ 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 onDataChannel(dataChannel: DataChannel?) {
- Timber.d("New data channel")
- }
-
- override fun onRenegotiationNeeded() {
- Timber.d("Renegotiation needed")
- }
-
- 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
+ override fun onRemoveTrack(receiver: RtpReceiver?) {
+ super.onRemoveTrack(receiver)
+ }
+
+ override fun onDataChannel(dataChannel: DataChannel?) {
+ Timber.d("New data channel")
+ }
+
+ override fun onRenegotiationNeeded() {
+ Timber.d("Renegotiation needed")
+ }
+
+ 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
+ }
}
}
}
-}
/**
* Enforces `SEND_ONLY` direction in case of `SEND_RECV` transceivers.
diff --git a/MembraneRTC/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt b/MembraneRTC/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt
index 10f18d7..fefa00b 100644
--- a/MembraneRTC/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt
+++ b/MembraneRTC/src/main/java/org/membraneframework/rtc/RTCEngineCommunication.kt
@@ -12,119 +12,130 @@ import timber.log.Timber
import kotlin.math.roundToLong
internal class RTCEngineCommunication
-@AssistedInject
-constructor(
- @Assisted
- private val engineListener: RTCEngineListener
-) {
- @AssistedFactory
- interface RTCEngineCommunicationFactory {
- fun create(
- listener: RTCEngineListener
- ): RTCEngineCommunication
- }
+ @AssistedInject
+ constructor(
+ @Assisted
+ private val engineListener: RTCEngineListener
+ ) {
+ @AssistedFactory
+ 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