Skip to content

Commit 7d79203

Browse files
authored
WebRTC Client. Create peer connection in the init block. (#5133)
1 parent 08c0440 commit 7d79203

File tree

8 files changed

+34
-67
lines changed

8 files changed

+34
-67
lines changed

ktor-client/ktor-client-webrtc/android/src/io/ktor/client/webrtc/Engine.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class AndroidWebRtcEngine(
5454
}
5555

5656
val coroutineContext = createConnectionContext(config.exceptionHandler)
57-
return AndroidWebRtcPeerConnection(coroutineContext, config).initialize { observer ->
57+
return AndroidWebRtcPeerConnection(coroutineContext, config) { observer ->
5858
localFactory.createPeerConnection(rtcConfig, observer)
5959
}
6060
}

ktor-client/ktor-client-webrtc/android/src/io/ktor/client/webrtc/PeerConnection.kt

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
package io.ktor.client.webrtc
66

77
import io.ktor.client.webrtc.media.*
8-
import kotlinx.coroutines.CoroutineStart
9-
import kotlinx.coroutines.launch
108
import kotlinx.coroutines.suspendCancellableCoroutine
119
import org.webrtc.*
1210
import org.webrtc.PeerConnection.Observer
@@ -16,29 +14,25 @@ import kotlin.coroutines.resumeWithException
1614

1715
public class AndroidWebRtcPeerConnection(
1816
coroutineContext: CoroutineContext,
19-
config: WebRtcConnectionConfig
17+
config: WebRtcConnectionConfig,
18+
createConnection: (Observer) -> PeerConnection?
2019
) : WebRtcPeerConnection(coroutineContext, config) {
21-
internal lateinit var peerConnection: PeerConnection
20+
internal val peerConnection: PeerConnection
21+
22+
init {
23+
peerConnection = createConnection(createObserver())
24+
?: error(
25+
"Failed to create peer connection. On the Android platform it is usually caused by invalid configuration. For instance, missing turn server username."
26+
)
27+
}
2228

2329
// remember RTP senders because method PeerConnection.getSenders() disposes all returned senders
2430
private val rtpSenders = arrayListOf<AndroidRtpSender>()
2531

2632
override suspend fun getStatistics(): List<WebRtc.Stats> = suspendCancellableCoroutine { cont ->
27-
if (this::peerConnection.isInitialized.not()) {
28-
cont.resume(emptyList())
29-
return@suspendCancellableCoroutine
30-
}
3133
peerConnection.getStats { cont.resume(it.toKtor()) }
3234
}
3335

34-
// helper method to break a dependency cycle (PeerConnection -> PeerConnectionFactory -> Observer)
35-
public fun initialize(block: (Observer) -> PeerConnection?): AndroidWebRtcPeerConnection {
36-
peerConnection = block(createObserver()) ?: error(
37-
"Failed to create peer connection. On the Android platform it is usually caused by invalid configuration. For instance, missing turn server username."
38-
)
39-
return this
40-
}
41-
4236
private val hasVideo get() = rtpSenders.any { it.track?.kind == WebRtcMedia.TrackType.VIDEO }
4337
private val hasAudio get() = rtpSenders.any { it.track?.kind == WebRtcMedia.TrackType.AUDIO }
4438

@@ -47,14 +41,6 @@ public class AndroidWebRtcPeerConnection(
4741
if (hasVideo) mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"))
4842
}
4943

50-
private inline fun runInConnectionScope(crossinline block: () -> Unit) {
51-
// Runs a `block` in the coroutine of the peer connection not to lose possible exceptions.
52-
// We are already running on the special thread, so extra dispatching is not required.
53-
// Moreover, dispatching the coroutine on another thread could break the internal `org.webrtc` logic.
54-
// For instance, it silently breaks registering a data channel observer.
55-
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { block() }
56-
}
57-
5844
private fun createObserver() = object : Observer {
5945
override fun onIceCandidate(candidate: IceCandidate?) = runInConnectionScope {
6046
val candidate = candidate?.toKtor() ?: return@runInConnectionScope

ktor-client/ktor-client-webrtc/api/android/ktor-client-webrtc.api

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public final class io/ktor/client/webrtc/AndroidWebRtcEngineConfig : io/ktor/cli
7272
}
7373

7474
public final class io/ktor/client/webrtc/AndroidWebRtcPeerConnection : io/ktor/client/webrtc/WebRtcPeerConnection {
75-
public fun <init> (Lkotlin/coroutines/CoroutineContext;Lio/ktor/client/webrtc/WebRtcConnectionConfig;)V
75+
public fun <init> (Lkotlin/coroutines/CoroutineContext;Lio/ktor/client/webrtc/WebRtcConnectionConfig;Lkotlin/jvm/functions/Function1;)V
7676
public fun addIceCandidate (Lio/ktor/client/webrtc/WebRtc$IceCandidate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
7777
public fun addTrack (Lio/ktor/client/webrtc/WebRtcMedia$Track;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
7878
public fun close ()V
@@ -82,7 +82,6 @@ public final class io/ktor/client/webrtc/AndroidWebRtcPeerConnection : io/ktor/c
8282
public fun getLocalDescription ()Lio/ktor/client/webrtc/WebRtc$SessionDescription;
8383
public fun getRemoteDescription ()Lio/ktor/client/webrtc/WebRtc$SessionDescription;
8484
public fun getStatistics (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
85-
public final fun initialize (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/webrtc/AndroidWebRtcPeerConnection;
8685
public fun removeTrack (Lio/ktor/client/webrtc/WebRtc$RtpSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
8786
public fun removeTrack (Lio/ktor/client/webrtc/WebRtcMedia$Track;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
8887
public fun restartIce ()V

ktor-client/ktor-client-webrtc/api/ktor-client-webrtc.klib.api

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -969,15 +969,14 @@ final class io.ktor.client.webrtc/IosRtpSender : io.ktor.client.webrtc/WebRtc.Rt
969969

970970
// Targets: [apple]
971971
final class io.ktor.client.webrtc/IosWebRtcConnection : io.ktor.client.webrtc/WebRtcPeerConnection { // io.ktor.client.webrtc/IosWebRtcConnection|null[0]
972-
constructor <init>(kotlin.coroutines/CoroutineContext, io.ktor.client.webrtc/WebRtcConnectionConfig) // io.ktor.client.webrtc/IosWebRtcConnection.<init>|<init>(kotlin.coroutines.CoroutineContext;io.ktor.client.webrtc.WebRtcConnectionConfig){}[0]
972+
constructor <init>(kotlin.coroutines/CoroutineContext, io.ktor.client.webrtc/WebRtcConnectionConfig, kotlin/Function1<WebRTC/RTCPeerConnectionDelegateProtocol, WebRTC/RTCPeerConnection?>) // io.ktor.client.webrtc/IosWebRtcConnection.<init>|<init>(kotlin.coroutines.CoroutineContext;io.ktor.client.webrtc.WebRtcConnectionConfig;kotlin.Function1<WebRTC.RTCPeerConnectionDelegateProtocol,WebRTC.RTCPeerConnection?>){}[0]
973973

974974
final val localDescription // io.ktor.client.webrtc/IosWebRtcConnection.localDescription|{}localDescription[0]
975975
final fun <get-localDescription>(): io.ktor.client.webrtc/WebRtc.SessionDescription? // io.ktor.client.webrtc/IosWebRtcConnection.localDescription.<get-localDescription>|<get-localDescription>(){}[0]
976976
final val remoteDescription // io.ktor.client.webrtc/IosWebRtcConnection.remoteDescription|{}remoteDescription[0]
977977
final fun <get-remoteDescription>(): io.ktor.client.webrtc/WebRtc.SessionDescription? // io.ktor.client.webrtc/IosWebRtcConnection.remoteDescription.<get-remoteDescription>|<get-remoteDescription>(){}[0]
978978

979979
final fun close() // io.ktor.client.webrtc/IosWebRtcConnection.close|close(){}[0]
980-
final fun initialize(kotlin/Function1<WebRTC/RTCPeerConnectionDelegateProtocol, WebRTC/RTCPeerConnection?>): io.ktor.client.webrtc/IosWebRtcConnection // io.ktor.client.webrtc/IosWebRtcConnection.initialize|initialize(kotlin.Function1<WebRTC.RTCPeerConnectionDelegateProtocol,WebRTC.RTCPeerConnection?>){}[0]
981980
final fun restartIce() // io.ktor.client.webrtc/IosWebRtcConnection.restartIce|restartIce(){}[0]
982981
final suspend fun addIceCandidate(io.ktor.client.webrtc/WebRtc.IceCandidate) // io.ktor.client.webrtc/IosWebRtcConnection.addIceCandidate|addIceCandidate(io.ktor.client.webrtc.WebRtc.IceCandidate){}[0]
983982
final suspend fun addTrack(io.ktor.client.webrtc/WebRtcMedia.Track): io.ktor.client.webrtc/WebRtc.RtpSender // io.ktor.client.webrtc/IosWebRtcConnection.addTrack|addTrack(io.ktor.client.webrtc.WebRtcMedia.Track){}[0]

ktor-client/ktor-client-webrtc/common/src/io/ktor/client/webrtc/WebRtcPeerConnection.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package io.ktor.client.webrtc
66

77
import io.ktor.utils.io.core.*
88
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.CoroutineStart
910
import kotlinx.coroutines.cancel
1011
import kotlinx.coroutines.delay
1112
import kotlinx.coroutines.flow.first
@@ -125,6 +126,14 @@ public abstract class WebRtcPeerConnection private constructor(
125126
iceGatheringState.first { it == WebRtc.IceGatheringState.COMPLETE }
126127
}
127128

129+
/**
130+
* Runs a [block] in the coroutine scope of the peer connection without extra dispatching.
131+
* This should be used to run some background tasks without losing thrown exceptions.
132+
*/
133+
internal inline fun runInConnectionScope(crossinline block: () -> Unit) {
134+
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { block() }
135+
}
136+
128137
override fun close() {
129138
coroutineScope.cancel()
130139
}

ktor-client/ktor-client-webrtc/ios/src/io/ktor/client/webrtc/Engine.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ public class IosWebRtcEngine(
3939
) : WebRtcEngineBase("ios-webrtc", config), MediaTrackFactory by mediaTrackFactory {
4040

4141
private val localFactory: RTCPeerConnectionFactory
42-
get() {
43-
return config.rtcFactory ?: (mediaTrackFactory as IosMediaDevices).peerConnectionFactory
44-
}
42+
get() = config.rtcFactory
43+
?: (mediaTrackFactory as? IosMediaDevices)?.peerConnectionFactory
44+
?: error("Please specify custom rtcFactory for custom MediaTrackFactory")
4545

4646
private fun WebRtc.IceServer.toIos(): RTCIceServer {
4747
return RTCIceServer(listOf(urls), username, credential)
@@ -59,7 +59,7 @@ public class IosWebRtcEngine(
5959
}
6060

6161
val coroutineContext = createConnectionContext(config.exceptionHandler)
62-
return IosWebRtcConnection(coroutineContext, config).initialize { delegate ->
62+
return IosWebRtcConnection(coroutineContext, config) { delegate ->
6363
localFactory.peerConnectionWithConfiguration(
6464
constraints = RTCMediaConstraints(),
6565
configuration = rtcConfig,

ktor-client/ktor-client-webrtc/ios/src/io/ktor/client/webrtc/PeerConnection.kt

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import WebRTC.*
88
import io.ktor.client.webrtc.media.*
99
import kotlinx.cinterop.ExperimentalForeignApi
1010
import kotlinx.cinterop.ObjCSignatureOverride
11-
import kotlinx.coroutines.CoroutineStart
12-
import kotlinx.coroutines.launch
1311
import kotlinx.coroutines.suspendCancellableCoroutine
1412
import platform.darwin.NSObject
1513
import kotlin.coroutines.CoroutineContext
@@ -21,44 +19,26 @@ import kotlin.coroutines.resumeWithException
2119
*
2220
* @param coroutineContext coroutine context used to deliver connection callbacks.
2321
* @param config configuration describing ICE servers, media constraints, and other connection options.
22+
* @param createConnection factory function that creates a native peer connection instance using a provided delegate.
2423
*/
2524
@OptIn(ExperimentalForeignApi::class)
2625
public class IosWebRtcConnection(
2726
coroutineContext: CoroutineContext,
28-
config: WebRtcConnectionConfig
27+
config: WebRtcConnectionConfig,
28+
createConnection: (RTCPeerConnectionDelegateProtocol) -> RTCPeerConnection?
2929
) : WebRtcPeerConnection(coroutineContext, config) {
30-
internal lateinit var peerConnection: RTCPeerConnection
30+
internal val peerConnection: RTCPeerConnection
31+
32+
init {
33+
peerConnection = createConnection(createDelegate()) ?: error("Failed to create peer connection.")
34+
}
3135

3236
override suspend fun getStatistics(): List<WebRtc.Stats> = suspendCancellableCoroutine { cont ->
33-
if (!this::peerConnection.isInitialized) {
34-
cont.resume(emptyList())
35-
return@suspendCancellableCoroutine
36-
}
3737
peerConnection.statisticsWithCompletionHandler { stats ->
3838
cont.resume(stats?.toKtor() ?: emptyList())
3939
}
4040
}
4141

42-
/**
43-
* Finishes constructing the underlying native peer connection by invoking [block] with the created delegate.
44-
*
45-
* @param block factory that receives the delegate and returns a configured [RTCPeerConnection].
46-
* @return this connection instance once the native peer connection has been installed.
47-
* @throws IllegalStateException if called more than once or if the native connection cannot be created.
48-
*/
49-
public fun initialize(block: (RTCPeerConnectionDelegateProtocol) -> RTCPeerConnection?): IosWebRtcConnection {
50-
if (this::peerConnection.isInitialized) {
51-
error("Peer connection has been already initialized.")
52-
}
53-
peerConnection = block(createDelegate()) ?: error("Failed to create peer connection.")
54-
return this
55-
}
56-
57-
private inline fun runInConnectionScope(crossinline block: () -> Unit) {
58-
// Runs a `block` in the coroutine of the peer connection not to lose possible exceptions.
59-
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { block() }
60-
}
61-
6242
private fun createDelegate() = object : RTCPeerConnectionDelegateProtocol, NSObject() {
6343
override fun peerConnection(
6444
peerConnection: RTCPeerConnection,

ktor-client/ktor-client-webrtc/ktor-client-webrtc-rs/common/src/io/ktor/client/webrtc/rs/Connection.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ public class RustWebRtcConnection(
1818
config: WebRtcConnectionConfig
1919
) : WebRtcPeerConnection(coroutineContext, config) {
2020

21-
private fun runInConnectionScope(block: suspend () -> Unit) {
22-
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
23-
block()
24-
}
25-
}
26-
2721
init {
2822
inner.registerObserver(object : PeerConnectionObserver {
2923
override fun onConnectionStateChange(state: ConnectionState) = runInConnectionScope {

0 commit comments

Comments
 (0)