Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AND-392] Fix microphone mute/unmute issue #1316

Merged
merged 8 commits into from
Mar 13, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ import stream.video.sfu.signal.UpdateMuteStatesResponse
import stream.video.sfu.signal.UpdateSubscriptionsRequest
import stream.video.sfu.signal.UpdateSubscriptionsResponse
import java.util.Collections
import java.util.UUID

/**
* Keeps track of which track is being rendered at what resolution.
Expand Down Expand Up @@ -571,24 +570,19 @@ public class RtcSession internal constructor(
setMuteState(isEnabled = it == DeviceStatus.Enabled, TrackType.TRACK_TYPE_VIDEO)

if (it == DeviceStatus.Enabled) {
val newTrack = call.peerConnectionFactory.makeVideoTrack(
call.mediaManager.videoSource,
UUID.randomUUID().toString(),
)
publisher?.publishStream(
newTrack,
val track = publisher?.publishStream(
TrackType.TRACK_TYPE_VIDEO,
call.mediaManager.camera.resolution.value,
)
setLocalTrack(
TrackType.TRACK_TYPE_VIDEO,
VideoTrack(
streamId = buildTrackId(TrackType.TRACK_TYPE_VIDEO),
video = newTrack,
video = track as org.webrtc.VideoTrack,
),
)
} else {
publisher?.unpublishStream(TrackType.TRACK_TYPE_VIDEO, false)
publisher?.unpublishStream(TrackType.TRACK_TYPE_VIDEO)
}
}
}
Expand All @@ -599,23 +593,18 @@ public class RtcSession internal constructor(
setMuteState(isEnabled = it == DeviceStatus.Enabled, TrackType.TRACK_TYPE_AUDIO)

if (it == DeviceStatus.Enabled) {
val newTrack = call.peerConnectionFactory.makeAudioTrack(
call.mediaManager.audioSource,
UUID.randomUUID().toString(),
)
publisher?.publishStream(
newTrack,
val track = publisher?.publishStream(
TrackType.TRACK_TYPE_AUDIO,
)
setLocalTrack(
TrackType.TRACK_TYPE_AUDIO,
AudioTrack(
streamId = buildTrackId(TrackType.TRACK_TYPE_AUDIO),
audio = newTrack,
audio = track as org.webrtc.AudioTrack,
),
)
} else {
publisher?.unpublishStream(TrackType.TRACK_TYPE_AUDIO, false)
publisher?.unpublishStream(TrackType.TRACK_TYPE_AUDIO)
}
}
}
Expand All @@ -628,23 +617,18 @@ public class RtcSession internal constructor(
TrackType.TRACK_TYPE_SCREEN_SHARE,
)
if (it == DeviceStatus.Enabled) {
val newTrack = call.peerConnectionFactory.makeVideoTrack(
call.mediaManager.screenShareVideoSource,
UUID.randomUUID().toString(),
)
publisher?.publishStream(
newTrack,
val track = publisher?.publishStream(
TrackType.TRACK_TYPE_SCREEN_SHARE,
)
setLocalTrack(
TrackType.TRACK_TYPE_SCREEN_SHARE,
VideoTrack(
streamId = buildTrackId(TrackType.TRACK_TYPE_SCREEN_SHARE),
video = newTrack,
video = track as org.webrtc.VideoTrack,
),
)
} else {
publisher?.unpublishStream(TrackType.TRACK_TYPE_SCREEN_SHARE, false)
publisher?.unpublishStream(TrackType.TRACK_TYPE_SCREEN_SHARE)
}
}
}
Expand Down Expand Up @@ -959,7 +943,6 @@ public class RtcSession internal constructor(
publishOptions = publishOptions,
coroutineScope = coroutineScope,
mediaConstraints = mediaConstraints,
onStreamAdded = { addStream(it) },
onNegotiationNeeded = { _, _ -> },
onIceCandidate = ::sendIceCandidate,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import io.getstream.video.android.core.call.connection.utils.toVideoDimension
import io.getstream.video.android.core.call.connection.utils.toVideoLayers
import io.getstream.video.android.core.model.IceCandidate
import io.getstream.video.android.core.model.StreamPeerType
import io.getstream.video.android.core.trySetEnabled
import io.getstream.video.android.core.utils.SdpSession
import io.getstream.video.android.core.utils.safeCall
import io.getstream.video.android.core.utils.safeCallWithDefault
Expand Down Expand Up @@ -81,8 +82,8 @@ internal class Publisher(
onIceCandidate,
maxBitRate,
) {
private val defaultScreenShareFormat = CaptureFormat(1080, 720, 24, 30)
private val defaultFormat = CaptureFormat(1080, 720, 24, 30)
private val defaultScreenShareFormat = CaptureFormat(1280, 720, 24, 30)
private val defaultFormat = CaptureFormat(1280, 720, 24, 30)
private var isIceRestarting = false

override fun onRenegotiationNeeded() {
Expand Down Expand Up @@ -155,45 +156,64 @@ internal class Publisher(
* Starts publishing the given track.
*/
suspend fun publishStream(
track: MediaStreamTrack,
trackType: TrackType,
captureFormat: CaptureFormat? = null,
) {
logger.i { "Publishing track: $trackType" }
if (track.state() == MediaStreamTrack.State.ENDED) {
logger.e { "Can't publish a track that has ended already." }
return
}
): MediaStreamTrack? {
logger.i { "[trackPublishing] Publishing track: $trackType" }

if (publishOptions.none { it.track_type == trackType }) {
logger.e { "No publish options found for $trackType" }
return
logger.e { "[trackPublishing] No publish options found for $trackType" }
return null
}

// enable the track if disabled
if (!track.enabled()) track.setEnabled(true)

for (publishOption in publishOptions) {
if (publishOption.track_type != trackType) continue

val trackToPublish = newTrackFromSource(publishOption.track_type)
val transceiver = transceiverCache.get(publishOption)
if (transceiver != null) {
try {
transceiver.sender?.setTrack(trackToPublish, true)
val sender = transceiver.sender
val senderTrack = sender.track()
if (senderTrack != null && !senderTrack.isDisposed) {
logger.d { "[trackPublishing] Track already exists." }
senderTrack.trySetEnabled(true)
logTrack(senderTrack)
return senderTrack
} else {
logger.d { "[trackPublishing] Track is disposed, creating new one." }
val newTrack = newTrackFromSource(publishOption.track_type)
sender.setTrack(newTrack, true)
return newTrack
}
} catch (e: Exception) {
// Fallback if anything happens with the sender
logger.w { "Failed to set track for ${publishOption.track_type}, creating new transceiver" }
transceiverCache.remove(publishOption)
addTransceiver(captureFormat, trackToPublish, publishOption)
val fallbackTrack = newTrackFromSource(publishOption.track_type)
addTransceiver(captureFormat, fallbackTrack, publishOption)
return fallbackTrack
}
} else {
addTransceiver(captureFormat, trackToPublish, publishOption)
logger.d {
"[trackPublishing] No transceiver found for $trackType, creating new track and transceiver."
}
// This is the first time we are adding the transceiver.
val newTrack = newTrackFromSource(publishOption.track_type)
addTransceiver(captureFormat, newTrack, publishOption)
return newTrack
}
}
return null
}

private fun newTrackFromSource(trackType: TrackType): MediaStreamTrack {
private fun logTrack(senderTrack: MediaStreamTrack?) {
logger.d {
"[trackPublishing] Track: ${senderTrack?.enabled()}:${senderTrack?.state()}:${senderTrack?.id()}"
}
}

@VisibleForTesting
public fun newTrackFromSource(trackType: TrackType): MediaStreamTrack {
return when (trackType) {
TrackType.TRACK_TYPE_AUDIO -> {
val id = UUID.randomUUID().toString()
Expand Down Expand Up @@ -265,17 +285,14 @@ internal class Publisher(
}
}

suspend fun unpublishStream(trackType: TrackType, stopTrack: Boolean) {
for (option in publishOptions) {
if (option.track_type != trackType) continue
val transceiver = transceiverCache.get(option)
if (transceiver?.sender?.track()?.isDisposed == false) continue
try {
transceiver?.stop()
transceiver?.dispose()
transceiverCache.remove(option)
} catch (e: Exception) {
logger.w { "Transceiver for option ${option.id}-${option.track_type}" }
suspend fun unpublishStream(trackType: TrackType) {
transceiverCache.getByTrackType(trackType).forEach { transceiver ->
logger.d { "[trackPublishing] Unpublishing track: $trackType" }
val sender = transceiver.sender
val senderTrack = sender.track()
senderTrack?.let { track ->
track.trySetEnabled(false)
logTrack(track)
}
}
}
Expand Down Expand Up @@ -332,10 +349,9 @@ internal class Publisher(
"Update publish quality ($publishOptionId-$trackType-${videoSender.codec?.name}), requested layers by SFU: $enabledLayers"
}

val sender =
transceiverCache.get(videoSender.toPublishOption())?.sender?.takeUnless {
it.track()?.isDisposed == true
}
val sender = transceiverCache.get(videoSender.toPublishOption())?.sender?.takeUnless {
it.track()?.isDisposed == true
}

if (sender == null) {
logger.w { "Update publish quality, no video sender found." }
Expand All @@ -360,6 +376,9 @@ internal class Publisher(

sender.parameters = params
logger.i { "Update publish quality, enabled rids: $activeLayers" }
activeLayers.forEach { layer ->
logger.i { "Update publish quality, enabled rid: ${layer.rid}" }
}
}

internal fun updateEncodings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.video.android.core.call.connection.transceivers
import io.getstream.video.android.core.call.connection.utils.OptimalVideoLayer
import org.webrtc.RtpTransceiver
import stream.video.sfu.models.PublishOption
import stream.video.sfu.models.TrackType
import java.util.Collections

internal class TransceiverCache {
Expand Down Expand Up @@ -74,6 +75,10 @@ internal class TransceiverCache {
private fun findLayer(publishOption: PublishOption): TrackLayersCache? {
return layers[publishOption.key()]
}

fun getByTrackType(trackType: TrackType): List<RtpTransceiver> {
return items().filter { it.publishOption.track_type == trackType }.map { it.transceiver }
}
}

// Helper data classes:
Expand Down
Loading
Loading