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
正在显示
9 个修改的文件
包含
120 行增加
和
32 行删除
| @@ -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 | } |
| @@ -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 |
-
请 注册 或 登录 后发表评论