davidliu
Committed by GitHub

handle subscribedqualityupdate (#35)

* update protocol

* OnSubscribedQualityUpdate

* fix addTransceiver

* fix bug

* ignore subscribed quality updates from 0.15.1 and below

* fix transceivers not getting hooked up

* add dynacast option and rename autoManageVideo to adaptiveStream
@@ -50,7 +50,7 @@ class LiveKit { @@ -50,7 +50,7 @@ class LiveKit {
50 options.videoTrackPublishDefaults?.let { 50 options.videoTrackPublishDefaults?.let {
51 room.videoTrackPublishDefaults = it 51 room.videoTrackPublishDefaults = it
52 } 52 }
53 - room.autoManageVideo = options.autoManageVideo 53 + room.adaptiveStream = options.adaptiveStream
54 54
55 return room 55 return room
56 } 56 }
1 package io.livekit.android 1 package io.livekit.android
2 2
  3 +import io.livekit.android.room.Room
3 import io.livekit.android.room.participant.AudioTrackPublishDefaults 4 import io.livekit.android.room.participant.AudioTrackPublishDefaults
4 import io.livekit.android.room.participant.VideoTrackPublishDefaults 5 import io.livekit.android.room.participant.VideoTrackPublishDefaults
5 import io.livekit.android.room.track.LocalAudioTrackOptions 6 import io.livekit.android.room.track.LocalAudioTrackOptions
@@ -7,14 +8,14 @@ import io.livekit.android.room.track.LocalVideoTrackOptions @@ -7,14 +8,14 @@ import io.livekit.android.room.track.LocalVideoTrackOptions
7 8
8 data class RoomOptions( 9 data class RoomOptions(
9 /** 10 /**
10 - * Automatically manage quality of subscribed video tracks, subscribe to the  
11 - * an appropriate resolution based on the size of the video elements that tracks  
12 - * are attached to.  
13 - *  
14 - * Also observes the visibility of attached tracks and pauses receiving data  
15 - * if they are not visible. 11 + * @see [Room.adaptiveStream]
16 */ 12 */
17 - val autoManageVideo: Boolean = false, 13 + val adaptiveStream: Boolean = false,
  14 +
  15 + /**
  16 + * @see [Room.dynacast]
  17 + */
  18 + val dynacast: Boolean = false,
18 19
19 val audioTrackCaptureDefaults: LocalAudioTrackOptions? = null, 20 val audioTrackCaptureDefaults: LocalAudioTrackOptions? = null,
20 val videoTrackCaptureDefaults: LocalVideoTrackOptions? = null, 21 val videoTrackCaptureDefaults: LocalVideoTrackOptions? = null,
@@ -385,6 +385,7 @@ internal constructor( @@ -385,6 +385,7 @@ internal constructor(
385 fun onFailToConnect(error: Throwable) 385 fun onFailToConnect(error: Throwable)
386 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind) 386 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind)
387 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) 387 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
  388 + fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate)
388 } 389 }
389 390
390 companion object { 391 companion object {
@@ -526,6 +527,10 @@ internal constructor( @@ -526,6 +527,10 @@ internal constructor(
526 listener?.onStreamStateUpdate(streamStates) 527 listener?.onStreamStateUpdate(streamStates)
527 } 528 }
528 529
  530 + override fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate) {
  531 + listener?.onSubscribedQualityUpdate(subscribedQualityUpdate)
  532 + }
  533 +
529 //--------------------------------- DataChannel.Observer ------------------------------------// 534 //--------------------------------- DataChannel.Observer ------------------------------------//
530 535
531 override fun onBufferedAmountChange(previousAmount: Long) { 536 override fun onBufferedAmountChange(previousAmount: Long) {
@@ -84,8 +84,18 @@ constructor( @@ -84,8 +84,18 @@ constructor(
84 * 84 *
85 * Also observes the visibility of attached tracks and pauses receiving data 85 * Also observes the visibility of attached tracks and pauses receiving data
86 * if they are not visible. 86 * if they are not visible.
  87 + *
  88 + * Defaults to false.
  89 + */
  90 + var adaptiveStream: Boolean = false
  91 +
  92 + /**
  93 + * Dynamically pauses video layers that are not being consumed by any subscribers,
  94 + * significantly reducing publishing CPU and bandwidth usage.
  95 + *
  96 + * Defaults to false.
87 */ 97 */
88 - var autoManageVideo: Boolean = false 98 + var dynacast: Boolean = false
89 99
90 /** 100 /**
91 * Default options to use when creating an audio track. 101 * Default options to use when creating an audio track.
@@ -140,7 +150,7 @@ constructor( @@ -140,7 +150,7 @@ constructor(
140 return 150 return
141 } 151 }
142 152
143 - val lp = localParticipantFactory.create(response.participant) 153 + val lp = localParticipantFactory.create(response.participant, dynacast)
144 lp.internalListener = this 154 lp.internalListener = this
145 localParticipant = lp 155 localParticipant = lp
146 if (response.otherParticipantsList.isNotEmpty()) { 156 if (response.otherParticipantsList.isNotEmpty()) {
@@ -182,12 +192,13 @@ constructor( @@ -182,12 +192,13 @@ constructor(
182 } 192 }
183 193
184 fun getParticipant(sid: String): Participant? { 194 fun getParticipant(sid: String): Participant? {
185 - if(sid == localParticipant.sid){ 195 + if (sid == localParticipant.sid) {
186 return localParticipant 196 return localParticipant
187 } else { 197 } else {
188 return remoteParticipants[sid] 198 return remoteParticipants[sid]
189 } 199 }
190 } 200 }
  201 +
191 @Synchronized 202 @Synchronized
192 private fun getOrCreateRemoteParticipant( 203 private fun getOrCreateRemoteParticipant(
193 sid: String, 204 sid: String,
@@ -207,8 +218,14 @@ constructor( @@ -207,8 +218,14 @@ constructor(
207 218
208 coroutineScope.launch { 219 coroutineScope.launch {
209 participant.events.collect { 220 participant.events.collect {
210 - when(it){  
211 - is ParticipantEvent.TrackStreamStateChanged -> eventBus.postEvent(RoomEvent.TrackStreamStateChanged(this@Room, it.trackPublication, it.streamState)) 221 + when (it) {
  222 + is ParticipantEvent.TrackStreamStateChanged -> eventBus.postEvent(
  223 + RoomEvent.TrackStreamStateChanged(
  224 + this@Room,
  225 + it.trackPublication,
  226 + it.streamState
  227 + )
  228 + )
212 } 229 }
213 } 230 }
214 } 231 }
@@ -262,7 +279,7 @@ constructor( @@ -262,7 +279,7 @@ constructor(
262 participant.audioLevel = speaker.level 279 participant.audioLevel = speaker.level
263 participant.isSpeaking = speaker.active 280 participant.isSpeaking = speaker.active
264 281
265 - if(speaker.active) { 282 + if (speaker.active) {
266 updatedSpeakers[speaker.sid] = participant 283 updatedSpeakers[speaker.sid] = participant
267 } else { 284 } else {
268 updatedSpeakers.remove(speaker.sid) 285 updatedSpeakers.remove(speaker.sid)
@@ -288,14 +305,14 @@ constructor( @@ -288,14 +305,14 @@ constructor(
288 } 305 }
289 306
290 private fun handleDisconnect() { 307 private fun handleDisconnect() {
291 - if(state == State.DISCONNECTED) { 308 + if (state == State.DISCONNECTED) {
292 return 309 return
293 } 310 }
294 311
295 try { 312 try {
296 val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 313 val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
297 cm.unregisterNetworkCallback(this) 314 cm.unregisterNetworkCallback(this)
298 - } catch (e : IllegalArgumentException) { 315 + } catch (e: IllegalArgumentException) {
299 // do nothing, may happen on older versions if attempting to unregister twice. 316 // do nothing, may happen on older versions if attempting to unregister twice.
300 } 317 }
301 318
@@ -377,7 +394,7 @@ constructor( @@ -377,7 +394,7 @@ constructor(
377 trackSid = track.id() 394 trackSid = track.id()
378 } 395 }
379 val participant = getOrCreateRemoteParticipant(participantSid) 396 val participant = getOrCreateRemoteParticipant(participantSid)
380 - participant.addSubscribedMediaTrack(track, trackSid!!, autoManageVideo) 397 + participant.addSubscribedMediaTrack(track, trackSid!!, adaptiveStream)
381 } 398 }
382 399
383 /** 400 /**
@@ -387,7 +404,7 @@ constructor( @@ -387,7 +404,7 @@ constructor(
387 for (info in updates) { 404 for (info in updates) {
388 val participantSid = info.sid 405 val participantSid = info.sid
389 406
390 - if(localParticipant.sid == participantSid) { 407 + if (localParticipant.sid == participantSid) {
391 localParticipant.updateFromInfo(info) 408 localParticipant.updateFromInfo(info)
392 continue 409 continue
393 } 410 }
@@ -454,7 +471,7 @@ constructor( @@ -454,7 +471,7 @@ constructor(
454 } 471 }
455 472
456 override fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) { 473 override fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) {
457 - for(streamState in streamStates){ 474 + for (streamState in streamStates) {
458 val participant = getParticipant(streamState.participantSid) ?: continue 475 val participant = getParticipant(streamState.participantSid) ?: continue
459 val track = participant.tracks[streamState.trackSid] ?: continue 476 val track = participant.tracks[streamState.trackSid] ?: continue
460 477
@@ -462,6 +479,10 @@ constructor( @@ -462,6 +479,10 @@ constructor(
462 } 479 }
463 } 480 }
464 481
  482 + override fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate) {
  483 + localParticipant.handleSubscribedQualityUpdate(subscribedQualityUpdate)
  484 + }
  485 +
465 /** 486 /**
466 * @suppress 487 * @suppress
467 */ 488 */
@@ -505,7 +526,7 @@ constructor( @@ -505,7 +526,7 @@ constructor(
505 * @suppress 526 * @suppress
506 */ 527 */
507 override fun onTrackPublished(publication: RemoteTrackPublication, participant: RemoteParticipant) { 528 override fun onTrackPublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {
508 - listener?.onTrackPublished(publication, participant, this) 529 + listener?.onTrackPublished(publication, participant, this)
509 eventBus.postEvent(RoomEvent.TrackPublished(this, publication, participant), coroutineScope) 530 eventBus.postEvent(RoomEvent.TrackPublished(this, publication, participant), coroutineScope)
510 } 531 }
511 532
@@ -513,7 +534,7 @@ constructor( @@ -513,7 +534,7 @@ constructor(
513 * @suppress 534 * @suppress
514 */ 535 */
515 override fun onTrackUnpublished(publication: RemoteTrackPublication, participant: RemoteParticipant) { 536 override fun onTrackUnpublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {
516 - listener?.onTrackUnpublished(publication, participant, this) 537 + listener?.onTrackUnpublished(publication, participant, this)
517 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope) 538 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope)
518 } 539 }
519 540
@@ -521,7 +542,7 @@ constructor( @@ -521,7 +542,7 @@ constructor(
521 * @suppress 542 * @suppress
522 */ 543 */
523 override fun onTrackPublished(publication: LocalTrackPublication, participant: LocalParticipant) { 544 override fun onTrackPublished(publication: LocalTrackPublication, participant: LocalParticipant) {
524 - listener?.onTrackPublished(publication, participant, this) 545 + listener?.onTrackPublished(publication, participant, this)
525 eventBus.postEvent(RoomEvent.TrackPublished(this, publication, participant), coroutineScope) 546 eventBus.postEvent(RoomEvent.TrackPublished(this, publication, participant), coroutineScope)
526 } 547 }
527 548
@@ -529,7 +550,7 @@ constructor( @@ -529,7 +550,7 @@ constructor(
529 * @suppress 550 * @suppress
530 */ 551 */
531 override fun onTrackUnpublished(publication: LocalTrackPublication, participant: LocalParticipant) { 552 override fun onTrackUnpublished(publication: LocalTrackPublication, participant: LocalParticipant) {
532 - listener?.onTrackUnpublished(publication, participant, this) 553 + listener?.onTrackUnpublished(publication, participant, this)
533 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope) 554 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope)
534 } 555 }
535 556
1 package io.livekit.android.room 1 package io.livekit.android.room
2 2
3 import com.google.protobuf.util.JsonFormat 3 import com.google.protobuf.util.JsonFormat
  4 +import com.vdurmont.semver4j.Semver
4 import io.livekit.android.ConnectOptions 5 import io.livekit.android.ConnectOptions
5 import io.livekit.android.Version 6 import io.livekit.android.Version
6 import io.livekit.android.dagger.InjectionNames 7 import io.livekit.android.dagger.InjectionNames
@@ -50,6 +51,7 @@ constructor( @@ -50,6 +51,7 @@ constructor(
50 private var currentWs: WebSocket? = null 51 private var currentWs: WebSocket? = null
51 private var isReconnecting: Boolean = false 52 private var isReconnecting: Boolean = false
52 var listener: Listener? = null 53 var listener: Listener? = null
  54 + private var serverVersion: Semver? = null
53 private var lastUrl: String? = null 55 private var lastUrl: String? = null
54 56
55 private var joinContinuation: CancellableContinuation<Either<LivekitRtc.JoinResponse, Unit>>? = null 57 private var joinContinuation: CancellableContinuation<Either<LivekitRtc.JoinResponse, Unit>>? = null
@@ -359,6 +361,11 @@ constructor( @@ -359,6 +361,11 @@ constructor(
359 // Only handle joins if not connected. 361 // Only handle joins if not connected.
360 if (response.hasJoin()) { 362 if (response.hasJoin()) {
361 isConnected = true 363 isConnected = true
  364 + try {
  365 + serverVersion = Semver(response.join.serverVersion)
  366 + } catch (t: Throwable) {
  367 + LKLog.w(t) { "Thrown while trying to parse server version." }
  368 + }
362 joinContinuation?.resumeWith(Result.success(Either.Left(response.join))) 369 joinContinuation?.resumeWith(Result.success(Either.Left(response.join)))
363 } else { 370 } else {
364 LKLog.e { "Received response while not connected. ${toJsonProtobuf.print(response)}" } 371 LKLog.e { "Received response while not connected. ${toJsonProtobuf.print(response)}" }
@@ -418,6 +425,16 @@ constructor( @@ -418,6 +425,16 @@ constructor(
418 LivekitRtc.SignalResponse.MessageCase.STREAM_STATE_UPDATE -> { 425 LivekitRtc.SignalResponse.MessageCase.STREAM_STATE_UPDATE -> {
419 listener?.onStreamStateUpdate(response.streamStateUpdate.streamStatesList) 426 listener?.onStreamStateUpdate(response.streamStateUpdate.streamStatesList)
420 } 427 }
  428 + LivekitRtc.SignalResponse.MessageCase.SUBSCRIBED_QUALITY_UPDATE -> {
  429 + val versionToIgnoreUpTo = Semver("0.15.1")
  430 + if (serverVersion?.compareTo(versionToIgnoreUpTo) ?: 1 <= 0) {
  431 + return
  432 + }
  433 + listener?.onSubscribedQualityUpdate(response.subscribedQualityUpdate)
  434 + }
  435 + LivekitRtc.SignalResponse.MessageCase.SUBSCRIPTION_PERMISSION_UPDATE -> {
  436 + // TODO
  437 + }
421 LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET, 438 LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET,
422 null -> { 439 null -> {
423 LKLog.v { "empty messageCase!" } 440 LKLog.v { "empty messageCase!" }
@@ -445,6 +462,7 @@ constructor( @@ -445,6 +462,7 @@ constructor(
445 fun onLeave() 462 fun onLeave()
446 fun onError(error: Throwable) 463 fun onError(error: Throwable)
447 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) 464 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
  465 + fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate)
448 } 466 }
449 467
450 companion object { 468 companion object {
@@ -31,6 +31,8 @@ class LocalParticipant @@ -31,6 +31,8 @@ class LocalParticipant
31 internal constructor( 31 internal constructor(
32 @Assisted 32 @Assisted
33 info: LivekitModels.ParticipantInfo, 33 info: LivekitModels.ParticipantInfo,
  34 + @Assisted
  35 + private val dynacast: Boolean,
34 internal val engine: RTCEngine, 36 internal val engine: RTCEngine,
35 private val peerConnectionFactory: PeerConnectionFactory, 37 private val peerConnectionFactory: PeerConnectionFactory,
36 private val context: Context, 38 private val context: Context,
@@ -253,8 +255,8 @@ internal constructor( @@ -253,8 +255,8 @@ internal constructor(
253 val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) 255 val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
254 256
255 when (track) { 257 when (track) {
256 - is LocalVideoTrack -> track.transceiver  
257 - is LocalAudioTrack -> track.transceiver 258 + is LocalVideoTrack -> track.transceiver = transceiver
  259 + is LocalAudioTrack -> track.transceiver = transceiver
258 else -> { 260 else -> {
259 throw IllegalArgumentException("Trying to publish a non local track of type ${track.javaClass}") 261 throw IllegalArgumentException("Trying to publish a non local track of type ${track.javaClass}")
260 } 262 }
@@ -314,6 +316,7 @@ internal constructor( @@ -314,6 +316,7 @@ internal constructor(
314 if (encodings.size >= VIDEO_RIDS.size) { 316 if (encodings.size >= VIDEO_RIDS.size) {
315 throw IllegalStateException("Attempting to add more encodings than we have rids for!") 317 throw IllegalStateException("Attempting to add more encodings than we have rids for!")
316 } 318 }
  319 + // encodings is mutable, so this will grab next available rid
317 val rid = VIDEO_RIDS[encodings.size] 320 val rid = VIDEO_RIDS[encodings.size]
318 encodings.add(videoEncoding.toRtpEncoding(rid, scale)) 321 encodings.add(videoEncoding.toRtpEncoding(rid, scale))
319 } 322 }
@@ -334,11 +337,13 @@ internal constructor( @@ -334,11 +337,13 @@ internal constructor(
334 val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 2.0 337 val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 2.0
335 addEncoding(lowPreset.encoding, lowScale) 338 addEncoding(lowPreset.encoding, lowScale)
336 } 339 }
337 -  
338 addEncoding(encoding, 1.0) 340 addEncoding(encoding, 1.0)
339 } else { 341 } else {
340 encodings.add(encoding.toRtpEncoding()) 342 encodings.add(encoding.toRtpEncoding())
341 } 343 }
  344 +
  345 + // Make largest size at front. addTransceiver seems to fail if ordered from smallest to largest.
  346 + encodings.reverse()
342 return encodings 347 return encodings
343 } 348 }
344 349
@@ -405,6 +410,15 @@ internal constructor( @@ -405,6 +410,15 @@ internal constructor(
405 } 410 }
406 } 411 }
407 412
  413 + private fun ridForVideoQuality(quality: LivekitModels.VideoQuality): String? {
  414 + return when (quality) {
  415 + LivekitModels.VideoQuality.HIGH -> "f"
  416 + LivekitModels.VideoQuality.MEDIUM -> "h"
  417 + LivekitModels.VideoQuality.LOW -> "q"
  418 + else -> null
  419 + }
  420 + }
  421 +
408 fun unpublishTrack(track: Track) { 422 fun unpublishTrack(track: Track) {
409 val publication = localTrackPublications.firstOrNull { it.track == track } 423 val publication = localTrackPublications.firstOrNull { it.track == track }
410 if (publication === null) { 424 if (publication === null) {
@@ -482,6 +496,32 @@ internal constructor( @@ -482,6 +496,32 @@ internal constructor(
482 pub?.muted = muted 496 pub?.muted = muted
483 } 497 }
484 498
  499 + fun handleSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate) {
  500 + val trackSid = subscribedQualityUpdate.trackSid
  501 + val qualities = subscribedQualityUpdate.subscribedQualitiesList
  502 + val pub = tracks[trackSid] ?: return
  503 + val track = pub.track as? LocalVideoTrack ?: return
  504 +
  505 + val sender = track.transceiver?.sender ?: return
  506 + val parameters = sender.parameters ?: return
  507 + val encodings = parameters.encodings ?: return
  508 +
  509 + var hasChanged = false
  510 + for (quality in qualities) {
  511 + val rid = ridForVideoQuality(quality.quality) ?: continue
  512 + val encoding = encodings.firstOrNull { it.rid == rid } ?: continue
  513 + if (encoding.active != quality.enabled) {
  514 + hasChanged = true
  515 + encoding.active = quality.enabled
  516 + LKLog.v { "setting layer ${quality.quality} to ${quality.enabled}" }
  517 + }
  518 + }
  519 +
  520 + if (hasChanged) {
  521 + sender.parameters = parameters
  522 + }
  523 + }
  524 +
485 525
486 interface PublishListener { 526 interface PublishListener {
487 fun onPublishSuccess(publication: TrackPublication) {} 527 fun onPublishSuccess(publication: TrackPublication) {}
@@ -490,7 +530,7 @@ internal constructor( @@ -490,7 +530,7 @@ internal constructor(
490 530
491 @AssistedFactory 531 @AssistedFactory
492 interface Factory { 532 interface Factory {
493 - fun create(info: LivekitModels.ParticipantInfo): LocalParticipant 533 + fun create(info: LivekitModels.ParticipantInfo, dynacast: Boolean): LocalParticipant
494 } 534 }
495 535
496 companion object { 536 companion object {
@@ -43,7 +43,7 @@ class RoomTest { @@ -43,7 +43,7 @@ class RoomTest {
43 var eglBase: EglBase = MockEglBase() 43 var eglBase: EglBase = MockEglBase()
44 44
45 val localParticantFactory = object : LocalParticipant.Factory { 45 val localParticantFactory = object : LocalParticipant.Factory {
46 - override fun create(info: LivekitModels.ParticipantInfo): LocalParticipant { 46 + override fun create(info: LivekitModels.ParticipantInfo, dynacast: Boolean): LocalParticipant {
47 return Mockito.mock(LocalParticipant::class.java) 47 return Mockito.mock(LocalParticipant::class.java)
48 } 48 }
49 } 49 }
1 -Subproject commit 8785fbf5c143612bf002dbbf6ca74db4e22f2f77 1 +Subproject commit 88ab66e0e761ff304042b286c41de3c803b45576
@@ -17,8 +17,6 @@ import io.livekit.android.util.flow @@ -17,8 +17,6 @@ import io.livekit.android.util.flow
17 import kotlinx.coroutines.ExperimentalCoroutinesApi 17 import kotlinx.coroutines.ExperimentalCoroutinesApi
18 import kotlinx.coroutines.flow.* 18 import kotlinx.coroutines.flow.*
19 import kotlinx.coroutines.launch 19 import kotlinx.coroutines.launch
20 -import okio.Utf8  
21 -import java.nio.charset.Charset  
22 20
23 @OptIn(ExperimentalCoroutinesApi::class) 21 @OptIn(ExperimentalCoroutinesApi::class)
24 class CallViewModel( 22 class CallViewModel(
@@ -76,12 +74,17 @@ class CallViewModel( @@ -76,12 +74,17 @@ class CallViewModel(
76 74
77 init { 75 init {
78 viewModelScope.launch { 76 viewModelScope.launch {
  77 +
  78 + launch {
  79 + error.collect { Timber.e(it) }
  80 + }
  81 +
79 try { 82 try {
80 val room = LiveKit.connect( 83 val room = LiveKit.connect(
81 application, 84 application,
82 url, 85 url,
83 token, 86 token,
84 - roomOptions = RoomOptions(autoManageVideo = true), 87 + roomOptions = RoomOptions(adaptiveStream = true),
85 listener = this@CallViewModel 88 listener = this@CallViewModel
86 ) 89 )
87 90