55package io.ktor.client.webrtc
66
77import io.ktor.client.webrtc.media.*
8+ import kotlinx.coroutines.CoroutineStart
9+ import kotlinx.coroutines.launch
810import org.webrtc.*
911import org.webrtc.PeerConnection.Observer
1012import kotlin.coroutines.CoroutineContext
@@ -25,7 +27,7 @@ public class AndroidWebRtcPeerConnection(
2527 if (this ::peerConnection.isInitialized.not ()) {
2628 cont.resume(emptyList())
2729 }
28- peerConnection.getStats { cont.resume(it.toCommon ()) }
30+ peerConnection.getStats { cont.resume(it.toKtor ()) }
2931 }
3032
3133 // helper method to break a dependency cycle (PeerConnection -> PeerConnectionFactory -> Observer)
@@ -44,84 +46,120 @@ public class AndroidWebRtcPeerConnection(
4446 if (hasVideo) mandatory.add(MediaConstraints .KeyValuePair (" OfferToReceiveVideo" , " true" ))
4547 }
4648
49+ private inline fun runInConnectionScope (crossinline block : () -> Unit ) {
50+ // Runs a `block` in the coroutine of the peer connection not to lose possible exceptions.
51+ // We are already running on the special thread, so extra dispatching is not required.
52+ // Moreover, dispatching the coroutine on another thread could break the internal `org.webrtc` logic.
53+ // For instance, it silently breaks registering a data channel observer.
54+ coroutineScope.launch(start = CoroutineStart .UNDISPATCHED ) { block() }
55+ }
56+
4757 private fun createObserver () = object : Observer {
48- override fun onIceCandidate (candidate : IceCandidate ? ) {
49- if (candidate == null ) return
50- events.emitIceCandidate(candidate.toCommon ())
58+ override fun onIceCandidate (candidate : IceCandidate ? ) = runInConnectionScope {
59+ if (candidate == null ) return @runInConnectionScope
60+ events.emitIceCandidate(candidate.toKtor ())
5161 }
5262
5363 override fun onIceCandidatesRemoved (candidates : Array <out IceCandidate >? ) = Unit
5464
55- override fun onAddTrack (receiver : RtpReceiver ? , mediaStreams : Array <out MediaStream >? ) {
56- if (receiver == null ) return
65+ override fun onAddTrack (receiver : RtpReceiver ? , mediaStreams : Array <out MediaStream >? ) = runInConnectionScope {
66+ if (receiver == null ) return @runInConnectionScope
5767 receiver.track()?.let {
5868 events.emitAddTrack(AndroidMediaTrack .from(it))
5969 }
6070 }
6171
62- override fun onRemoveTrack (receiver : RtpReceiver ? ) {
63- if (receiver == null ) return
72+ override fun onRemoveTrack (receiver : RtpReceiver ? ) = runInConnectionScope {
73+ if (receiver == null ) return @runInConnectionScope
6474 receiver.track()?.let {
6575 events.emitRemoveTrack(AndroidMediaTrack .from(it))
6676 }
6777 }
6878
69- override fun onIceConnectionChange (newState : PeerConnection .IceConnectionState ? ) {
70- val commonState = newState.toCommon () ? : return
79+ override fun onIceConnectionChange (newState : PeerConnection .IceConnectionState ? ) = runInConnectionScope {
80+ val commonState = newState.toKtor () ? : return @runInConnectionScope
7181 events.emitIceConnectionStateChange(commonState)
7282 }
7383
74- override fun onConnectionChange (newState : PeerConnection .PeerConnectionState ? ) {
75- val commonState = newState.toCommon () ? : return
84+ override fun onConnectionChange (newState : PeerConnection .PeerConnectionState ? ) = runInConnectionScope {
85+ val commonState = newState.toKtor () ? : return @runInConnectionScope
7686 events.emitConnectionStateChange(commonState)
7787 }
7888
79- override fun onSignalingChange (newState : PeerConnection .SignalingState ? ) {
80- val commonState = newState.toCommon () ? : return
89+ override fun onSignalingChange (newState : PeerConnection .SignalingState ? ) = runInConnectionScope {
90+ val commonState = newState.toKtor () ? : return @runInConnectionScope
8191 events.emitSignalingStateChange(commonState)
8292 }
8393
84- override fun onIceGatheringChange (newState : PeerConnection .IceGatheringState ? ) {
85- val commonState = newState.toCommon () ? : return
94+ override fun onIceGatheringChange (newState : PeerConnection .IceGatheringState ? ) = runInConnectionScope {
95+ val commonState = newState.toKtor () ? : return @runInConnectionScope
8696 events.emitIceGatheringStateChange(commonState)
8797 }
8898
8999 override fun onIceConnectionReceivingChange (receiving : Boolean ) = Unit
90- override fun onRenegotiationNeeded (): Unit = events.emitNegotiationNeeded()
100+ override fun onRenegotiationNeeded () = runInConnectionScope {
101+ events.emitNegotiationNeeded()
102+ }
91103
92104 // we omit streams and operate with tracks instead
93105 override fun onAddStream (p0 : MediaStream ? ) = Unit
94106 override fun onRemoveStream (p0 : MediaStream ? ) = Unit
95107
96- // #TODO: implement data channels
97- override fun onDataChannel (dataChannel : DataChannel ? ) = Unit
108+ override fun onDataChannel (dataChannel : DataChannel ? ) = runInConnectionScope {
109+ if (dataChannel == null ) return @runInConnectionScope
110+ val channel = AndroidWebRtcDataChannel (
111+ nativeChannel = dataChannel,
112+ channelInit = null ,
113+ coroutineScope = coroutineScope,
114+ options = WebRtcDataChannelOptions ()
115+ )
116+ channel.setupEvents(events)
117+ }
98118 }
99119
100120 override val localDescription: WebRtc .SessionDescription ?
101- get() = peerConnection.localDescription?.toCommon ()
121+ get() = peerConnection.localDescription?.toKtor ()
102122
103123 override val remoteDescription: WebRtc .SessionDescription ?
104- get() = peerConnection.remoteDescription?.toCommon ()
124+ get() = peerConnection.remoteDescription?.toKtor ()
105125
106126 override suspend fun createOffer (): WebRtc .SessionDescription {
107127 val offer = suspendCoroutine { cont ->
108128 peerConnection.createOffer(cont.resumeAfterSdpCreate(), offerConstraints())
109129 }
110- return offer.toCommon ()
130+ return offer.toKtor ()
111131 }
112132
113133 override suspend fun createAnswer (): WebRtc .SessionDescription {
114134 val answer = suspendCoroutine { cont ->
115135 peerConnection.createAnswer(cont.resumeAfterSdpCreate(), offerConstraints())
116136 }
117- return answer.toCommon ()
137+ return answer.toKtor ()
118138 }
119139
120140 override suspend fun createDataChannel (
121141 label : String ,
122142 options : WebRtcDataChannelOptions .() -> Unit
123143 ): WebRtcDataChannel {
124- TODO (" Not yet implemented" )
144+ val options = WebRtcDataChannelOptions ().apply (options)
145+ val channelInit = DataChannel .Init ().apply {
146+ if (options.id != null ) {
147+ id = options.id!!
148+ }
149+ if (options.maxRetransmits != null ) {
150+ maxRetransmits = options.maxRetransmits!!
151+ }
152+ if (options.maxPacketLifeTime != null ) {
153+ maxRetransmitTimeMs = options.maxPacketLifeTime?.inWholeMilliseconds?.toInt()!!
154+ }
155+ ordered = options.ordered
156+ protocol = options.protocol
157+ negotiated = options.negotiated
158+ }
159+ val nativeChannel = peerConnection.createDataChannel(label, channelInit)
160+ return AndroidWebRtcDataChannel (nativeChannel, channelInit, coroutineScope, options).apply {
161+ setupEvents(events)
162+ }
125163 }
126164
127165 override suspend fun setLocalDescription (description : WebRtc .SessionDescription ) {
0 commit comments