Skip to content

Commit

Permalink
Remove backward compat for source names and multi-stream (#1130)
Browse files Browse the repository at this point in the history
* ref: Remove backward compatibility with endpoints with no source name support.

* Remove multi stream backward compat code.
  • Loading branch information
bgrozev authored Jan 29, 2024
1 parent d0fd82d commit 716af90
Show file tree
Hide file tree
Showing 12 changed files with 18 additions and 339 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ data class ParticipantAllocationParameters(
val statsId: String?,
val region: String?,
val sources: EndpointSourceSet,
val supportsSourceNames: Boolean,
val useSsrcRewriting: Boolean,
val forceMuteAudio: Boolean,
val forceMuteVideo: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ internal fun ParticipantInfo.toEndpoint(
if (create) {
setCreate(true)
setStatsId(statsId)
if (supportsSourceNames) {
addCapability(Capability.CAP_SOURCE_NAME_SUPPORT)
}
addCapability(Capability.CAP_SOURCE_NAME_SUPPORT)
if (supportsPrivateAddresses) {
addCapability(Capability.CAP_PRIVATE_ADDRESS_CONNECTIVITY)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class ParticipantInfo(
val statsId = parameters.statsId
val useSctp = parameters.useSctp
val medias = parameters.medias
val supportsSourceNames = parameters.supportsSourceNames
val supportsPrivateAddresses = parameters.supportsPrivateAddresses
val useSsrcRewriting = parameters.useSsrcRewriting
val visitor = parameters.visitor
Expand All @@ -46,7 +45,6 @@ class ParticipantInfo(
put("bridge", session.bridge.jid.resourceOrNull.toString())
put("audio_muted", audioMuted)
put("video_muted", videoMuted)
put("source_names", supportsSourceNames)
put("private_addresses", supportsPrivateAddresses)
put("ssrc_rewriting", useSsrcRewriting)
put("visitor", visitor)
Expand Down
11 changes: 0 additions & 11 deletions jicofo-selector/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,6 @@ jicofo {
// session) or ReplaceTransport (send a transport-replace).
reinvite-method = "RestartJingle"

// Whether to enable backward compatibility for clients which do not support receiving multiple video streams.
//
// Newer jitsi-meet clients support sending video and screensharing in two separate streams. We want to support
// older clients with no support for receiving multiple streams in conferences with newer clients (for this mode we
// assume they can have at most 1 camera and one screensharing stream), and specifically we want them to receive
// the screensharing stream when there is a choice. To achieve this jicofo filters the set of sources signaled to
// older clients, excluding the camera source if there is a screenshering source.
//
// Note: this feature is indented to be removed once older clients (mobile) are phased out.
enable-multi-stream-backward-compat = false

// Rate limits for a participant requesting an ICE restart.
restart-request-rate-limits {
// Never accept a request unless at least `min-interval` has passed since the last request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,14 +806,6 @@ private void inviteChatMember(ChatRoomMember chatRoomMember, boolean justJoined)
features);

ConferenceMetrics.participants.inc();
if (!participant.supportsReceivingMultipleVideoStreams() && !participant.getChatMember().isJigasi())
{
ConferenceMetrics.participantsNoMultiStream.inc();
}
if (!participant.hasSourceNameSupport() && !participant.getChatMember().isJigasi())
{
ConferenceMetrics.participantsNoSourceName.inc();
}

boolean added = (participants.put(chatRoomMember.getOccupantJid(), participant) == null);
if (added)
Expand Down Expand Up @@ -2100,22 +2092,6 @@ public JibriSipGateway getJibriSipGateway()
return jibriSipGateway;
}

/**
* Notifies this conference that one of the participants' screensharing source has changed its "mute" status.
*/
void desktopSourceIsMutedChanged(Participant participant, boolean desktopSourceIsMuted)
{
if (!ConferenceConfig.config.getMultiStreamBackwardCompat())
{
return;
}

participants.values().stream()
.filter(p -> p != participant)
.filter(p -> !p.supportsReceivingMultipleVideoStreams())
.forEach(p -> p.remoteDesktopSourceIsMutedChanged(participant.getEndpointId(), desktopSourceIsMuted));
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -2358,11 +2334,6 @@ public void localRoleChanged(@NotNull MemberRole newRole)
@Override
public void memberPresenceChanged(@NotNull ChatRoomMember member)
{
Participant participant = getParticipant(member.getOccupantJid());
if (participant != null)
{
participant.presenceChanged();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ private void doRun()
participant.getStatId(),
participant.getChatMember().getRegion(),
participant.getSources(),
participant.hasSourceNameSupport(),
participant.useSsrcRewriting(),
forceMuteAudio,
forceMuteVideo,
Expand Down
4 changes: 0 additions & 4 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/ConferenceConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ class ConferenceConfig private constructor() {
"jicofo.conference.reinvite-method".from(newConfig)
}

val multiStreamBackwardCompat: Boolean by config {
"jicofo.conference.enable-multi-stream-backward-compat".from(newConfig)
}

/**
* Whether to strip simulcast streams when signaling receivers. This option requires that jitsi-videobridge
* uses the first SSRC in the SIM group as the target SSRC when rewriting streams, as this is the only SSRC
Expand Down
2 changes: 0 additions & 2 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/FocusManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,6 @@ class FocusManager(
// so we'll merge stats from different "child" objects here.
val stats = OrderedJsonObject()
stats["total_participants"] = ConferenceMetrics.participants.get()
stats["total_participants_no_multi_stream"] = ConferenceMetrics.participantsNoMultiStream.get()
stats["total_participants_no_source_name"] = ConferenceMetrics.participantsNoSourceName.get()
stats["total_conferences_created"] = ConferenceMetrics.conferencesCreated.get()
stats["conferences"] = ConferenceMetrics.conferenceCount.get()
stats["conferences_with_visitors"] = ConferenceMetrics.conferencesWithVisitors.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,6 @@ class ConferenceMetrics {
"The total number of participants that have connected to this Jicofo since it was started."
)

@JvmField
val participantsNoMultiStream = metricsContainer.registerCounter(
"participants_no_multi_stream",
"Number of participants with no support for receiving multiple streams."
)

@JvmField
val participantsNoSourceName = metricsContainer.registerCounter(
"participants_no_source_name",
"Number of participants with no support for source names."
)

@JvmField
val participantsMoved = metricsContainer.registerCounter(
"participants_moved",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import org.jitsi.jicofo.conference.source.ConferenceSourceMap
import org.jitsi.jicofo.conference.source.EndpointSourceSet
import org.jitsi.jicofo.conference.source.EndpointSourceSet.Companion.fromJingle
import org.jitsi.jicofo.conference.source.ValidationFailedException
import org.jitsi.jicofo.conference.source.VideoType
import org.jitsi.jicofo.util.Cancelable
import org.jitsi.jicofo.util.RateLimit
import org.jitsi.jicofo.xmpp.Features
Expand All @@ -34,7 +33,6 @@ import org.jitsi.jicofo.xmpp.jingle.JingleRequestHandler
import org.jitsi.jicofo.xmpp.jingle.JingleSession
import org.jitsi.jicofo.xmpp.muc.ChatRoomMember
import org.jitsi.jicofo.xmpp.muc.MemberRole
import org.jitsi.jicofo.xmpp.muc.SourceInfo
import org.jitsi.jicofo.xmpp.muc.hasModeratorRights
import org.jitsi.utils.OrderedJsonObject
import org.jitsi.utils.logging2.Logger
Expand Down Expand Up @@ -94,8 +92,7 @@ open class Participant @JvmOverloads constructor(
private val sourceSignaling = SourceSignaling(
audio = hasAudioSupport(),
video = hasVideoSupport(),
ConferenceConfig.config.stripSimulcast(),
supportsReceivingMultipleVideoStreams() || !ConferenceConfig.config.multiStreamBackwardCompat
ConferenceConfig.config.stripSimulcast()
)

/**
Expand Down Expand Up @@ -132,61 +129,13 @@ open class Participant @JvmOverloads constructor(
*/
private val signalQueuedSourcesTaskSyncRoot = Any()

/**
* Whether the screensharing source of this participant (if it exists) is muted. If a screensharing source doesn't
* exists this stays false (though the source and the mute status are communicated separately so they may not
* always be in sync)
*/
private var desktopSourceIsMuted = false

/**
* Whether this participant is a "user participant" for the purposes of
* [JitsiMeetConferenceImpl.getUserParticipantCount].
* Needs to be unchanging so counts don't get out of sync.
*/
val isUserParticipant = !chatMember.isJibri && !chatMember.isTranscriber && chatMember.role != MemberRole.VISITOR

init {
updateDesktopSourceIsMuted(chatMember.sourceInfos)
}

/**
* Notify this [Participant] that the underlying [ChatRoomMember]'s presence changed.
*/
fun presenceChanged() {
if (updateDesktopSourceIsMuted(chatMember.sourceInfos)) {
conference.desktopSourceIsMutedChanged(this, desktopSourceIsMuted)
}
}

/**
* Update the value of [.desktopSourceIsMuted] based on the advertised [SourceInfo]s.
* @return true if the value of [.desktopSourceIsMuted] changed as a result of this call.
*/
private fun updateDesktopSourceIsMuted(sourceInfos: Set<SourceInfo>): Boolean {
val newValue = sourceInfos
.any { (_, muted, videoType) -> videoType === VideoType.Desktop && muted }
if (desktopSourceIsMuted != newValue) {
desktopSourceIsMuted = newValue
return true
}
return false
}

/**
* Notify this participant that another participant's (identified by `owner`) screensharing source was muted
* or unmuted.
*/
fun remoteDesktopSourceIsMutedChanged(owner: String, muted: Boolean) {
// This is only needed for backwards compatibility with clients that don't support receiving multiple streams.
if (supportsReceivingMultipleVideoStreams()) {
return
}
sourceSignaling.remoteDesktopSourceIsMutedChanged(owner, muted)
// Signal updates, if any, immediately.
synchronized(signalQueuedSourcesTaskSyncRoot) { scheduleSignalingOfQueuedSources() }
}

/**
* Replaces the channel allocator thread, which is currently allocating channels for this participant (if any)
* with the specified channel allocator (if any).
Expand Down Expand Up @@ -217,9 +166,6 @@ open class Participant @JvmOverloads constructor(
}
}

/** Return `true` if this participant supports source name signaling. */
fun hasSourceNameSupport() = supportedFeatures.contains(Features.SOURCE_NAMES)

/** Return `true` if this participant supports SSRC rewriting functionality. */
fun hasSsrcRewritingSupport() = supportedFeatures.contains(Features.SSRC_REWRITING_V1)

Expand All @@ -228,7 +174,6 @@ open class Participant @JvmOverloads constructor(

/** Return `true` if this participant supports receiving Jingle sources encoded in JSON. */
fun supportsJsonEncodedSources() = supportedFeatures.contains(Features.JSON_SOURCES)
fun supportsReceivingMultipleVideoStreams() = supportedFeatures.contains(Features.RECEIVE_MULTIPLE_STREAMS)

/** Returns `true` iff this participant supports REMB. */
fun hasRembSupport() = supportedFeatures.contains(Features.REMB)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,14 @@ package org.jitsi.jicofo.conference
import org.jitsi.jicofo.conference.AddOrRemove.Add
import org.jitsi.jicofo.conference.AddOrRemove.Remove
import org.jitsi.jicofo.conference.source.ConferenceSourceMap
import org.jitsi.jicofo.conference.source.EndpointSourceSet
import org.jitsi.jicofo.conference.source.Source
import org.jitsi.jicofo.conference.source.VideoType
import org.jitsi.utils.MediaType
import org.json.simple.JSONArray
import org.json.simple.JSONObject

class SourceSignaling(
audio: Boolean = true,
video: Boolean = true,
private val stripSimulcast: Boolean = true,
/**
* Whether the endpoint supports receiving multiple video streams. If it doesn't, we make sure to only signal the
* screensharing (desktop) source when another endpoint has both camera and screensharing.
*
* We assume at most one desktop source and at most one camera source.
*/
private val supportsReceivingMultipleStreams: Boolean = true
private val stripSimulcast: Boolean = true
) {
/** The set of media types supported by the endpoint. */
private val supportedMediaTypes: Set<MediaType> = buildSet {
Expand All @@ -57,31 +47,12 @@ class SourceSignaling(
*/
private var updatedSources = ConferenceSourceMap()

/**
* In the case when [supportsReceivingMultipleStreams] is false, stores any screensharing sources which are
* signaled to be muted, so that they can be restored once unmuted.
*/
private val mutedDesktopSources = ConferenceSourceMap()

fun addSources(sourcesToAdd: ConferenceSourceMap) {
sourcesToAdd.copy().entries.forEach { (owner, ess) ->
if (mutedDesktopSources[owner] == NO_SOURCES) {
// owner's desktop source was muted before the source details were signaled. Suppress and save the
// desktop sources.
val desktopSources = ess.getDesktopSources()
if (!desktopSources.isEmpty()) {
mutedDesktopSources.remove(owner)
mutedDesktopSources.add(owner, desktopSources)
sourcesToAdd.remove(ConferenceSourceMap(owner, desktopSources))
}
}
}
updatedSources.add(sourcesToAdd)
}

fun removeSources(sourcesToRemove: ConferenceSourceMap) {
updatedSources.remove(sourcesToRemove)
mutedDesktopSources.remove(sourcesToRemove)
}

/**
Expand Down Expand Up @@ -124,68 +95,5 @@ class SourceSignaling(
private fun ConferenceSourceMap.filter(): ConferenceSourceMap = copy().apply {
stripByMediaType(supportedMediaTypes)
if (stripSimulcast) stripSimulcast()
if (!supportsReceivingMultipleStreams) {
filterMultiStream()
}
}

/**
* Notifies this instance that a remote participant (identified by [owner]) has muted or unmuted their screensharing
* source.
*/
fun remoteDesktopSourceIsMutedChanged(owner: String, muted: Boolean) {
if (muted) {
// so that we can fall back to the video source
val allParticipantSources = updatedSources[owner] ?: EndpointSourceSet()
val desktopSources = allParticipantSources.getDesktopSources()

// The source was muted. If there was a screensharing source signaled (desktopSources is not empty)
// we remove it from [updatedSources], so that we can signal a source-remove with the next update.
updatedSources.remove(ConferenceSourceMap(owner, desktopSources))
// If the source was not signaled yet, save NO_SOURCES in the map to remember that it is muted once it
// is signaled.
mutedDesktopSources.add(owner, if (desktopSources.isEmpty()) NO_SOURCES else desktopSources)
} else {
val unmutedDesktopSources = mutedDesktopSources[owner]

// Remove it from the muted map so future calls to [addSources] are allowed to add it.
mutedDesktopSources.remove(owner)
// If there was a screensharing source previously signaled, and it is not the NO_SOURCES placeholder, add
// it to [updatedSources] so that is signaled with the next update.
if (unmutedDesktopSources != null && unmutedDesktopSources != NO_SOURCES) {
updatedSources.add(owner, unmutedDesktopSources)
}
}
}
}

/**
* If an endpoint has a screensharing (desktop) source, filter out all other video sources.
*/
private fun ConferenceSourceMap.filterMultiStream() = map { ess ->
val desktopSourceName = ess.sources.find { it.videoType == VideoType.Desktop }?.name
if (desktopSourceName != null) {
val remainingSources = ess.sources.filter {
it.mediaType != MediaType.VIDEO || it.name == desktopSourceName
}.toSet()
val remainingSsrcs = remainingSources.map { it.ssrc }.toSet()
val remainingGroups = ess.ssrcGroups.filter { it.ssrcs.any { it in remainingSsrcs } }.toSet()
EndpointSourceSet(remainingSources, remainingGroups)
} else {
ess
}
}

private fun EndpointSourceSet.getDesktopSources(): EndpointSourceSet {
val desktopSourceName = sources.find { it.videoType == VideoType.Desktop }?.name
return if (desktopSourceName != null) {
val desktopSources = sources.filter { it.name == desktopSourceName }.toSet()
val desktopSsrcs = desktopSources.map { it.ssrc }.toSet()
val desktopGroups = ssrcGroups.filter { it.ssrcs.any { it in desktopSsrcs } }.toSet()
EndpointSourceSet(desktopSources, desktopGroups)
} else {
EndpointSourceSet()
}
}

private val NO_SOURCES = EndpointSourceSet(Source(987654321, MediaType.VIDEO))
Loading

0 comments on commit 716af90

Please sign in to comment.