From 2d3b5c11e7624e2f1c92cd2772d100c4e3e19843 Mon Sep 17 00:00:00 2001 From: bgrozev Date: Tue, 20 Feb 2024 13:54:32 -0600 Subject: [PATCH] Fix failure to leave when the only remaining participants are hidden (#537) * ref: Inline conference.memberCount. * fix: Exclude hidden participants from muted/jigasi count. Fixes failure to leave when the only remaining participants are hidden (for example, a transcriber). --- .../jibri/selenium/pageobjects/CallPage.kt | 38 ++++++++++++++++++- .../status_checks/MediaReceivedStatusCheck.kt | 3 +- .../MediaReceivedStatusCheckTest.kt | 4 +- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/jitsi/jibri/selenium/pageobjects/CallPage.kt b/src/main/kotlin/org/jitsi/jibri/selenium/pageobjects/CallPage.kt index 4efdbfa8..a6c6a1ec 100644 --- a/src/main/kotlin/org/jitsi/jibri/selenium/pageobjects/CallPage.kt +++ b/src/main/kotlin/org/jitsi/jibri/selenium/pageobjects/CallPage.kt @@ -74,11 +74,12 @@ class CallPage(driver: RemoteWebDriver) : AbstractPageObject(driver) { return result } + /** Returns the number of participants excluding hidden participants. */ fun getNumParticipants(): Int { val result = driver.executeScript( """ try { - return APP.conference.membersCount; + return (APP.conference._room.getParticipants().$PARTICIPANT_FILTER_SCRIPT).length + 1; } catch (e) { return e.message; } @@ -203,13 +204,15 @@ class CallPage(driver: RemoteWebDriver) : AbstractPageObject(driver) { } /** - * Return how many of the participants are Jigasi clients + * Return how many of the participants are Jigasi clients. + * Note: excludes any participants that are hidden (for example transcribers) */ fun numRemoteParticipantsJigasi(): Int { val result = driver.executeScript( """ try { return APP.conference._room.getParticipants() + .$PARTICIPANT_FILTER_SCRIPT .filter(participant => participant.getProperty("features_jigasi") == true) .length; } catch (e) { @@ -226,6 +229,28 @@ class CallPage(driver: RemoteWebDriver) : AbstractPageObject(driver) { } } + /** How many of the participants are hidden or hiddenFromRecorder. */ + fun numHiddenParticipants(): Int { + val result = driver.executeScript( + """ + try { + return APP.conference._room.getParticipants() + .filter(p => (p.isHidden() || p.isHiddenFromRecorder()) + .length; + } catch (e) { + return e.message; + } + """.trimMargin() + ) + return when (result) { + is Number -> result.toInt() + else -> { + logger.error("error running numHiddenParticipants script: $result ${result::class.java}") + 0 + } + } + } + /** * Return true if ICE is connected. */ @@ -267,12 +292,14 @@ class CallPage(driver: RemoteWebDriver) : AbstractPageObject(driver) { * Returns a count of how many remote participants are totally muted (audio * and video). We ignore jigasi participants as they maybe muted in their presence * but also hard muted via the device, and we later ignore their state. + * Note: Excludes hidden participants. */ fun numRemoteParticipantsMuted(): Int { val result = driver.executeScript( """ try { return APP.conference._room.getParticipants() + .$PARTICIPANT_FILTER_SCRIPT .filter(participant => participant.isAudioMuted() && participant.isVideoMuted() && participant.getProperty("features_jigasi") !== true) .length; @@ -353,4 +380,11 @@ class CallPage(driver: RemoteWebDriver) : AbstractPageObject(driver) { else -> true } } + + companion object { + /** + * Javascript to apply a filter to the list of participants to exclude ones which should be hidden from jibri. + */ + const val PARTICIPANT_FILTER_SCRIPT = "filter(p => !(p.isHidden() || p.isHiddenFromRecorder()))" + } } diff --git a/src/main/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheck.kt b/src/main/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheck.kt index 50f62ef4..935cd778 100644 --- a/src/main/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheck.kt +++ b/src/main/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheck.kt @@ -39,13 +39,14 @@ class MediaReceivedStatusCheck( val numParticipants = callPage.getNumParticipants() - 1 val numMutedParticipants = callPage.numRemoteParticipantsMuted() val numJigasiParticipants = callPage.numRemoteParticipantsJigasi() + val numHiddenParticipants = callPage.numHiddenParticipants() // We don't get any mute state for Jigasi participants, so to prevent timing out when only Jigasi participants // may be speaking, always count them as "muted" val allClientsMuted = (numMutedParticipants + numJigasiParticipants) == numParticipants logger.info( "Jibri client receive bitrates: $bitrates, num participants: $numParticipants, " + "numMutedParticipants: $numMutedParticipants, numJigasis: $numJigasiParticipants, " + - "all clients muted? $allClientsMuted" + "numHiddenParticipants: $numHiddenParticipants, all clients muted? $allClientsMuted" ) clientsAllMutedTransitionTime.maybeUpdate(allClientsMuted) val downloadBitrate = bitrates.getOrDefault("download", 0L) as Long diff --git a/src/test/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheckTest.kt b/src/test/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheckTest.kt index bba24c63..7b8d7335 100644 --- a/src/test/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheckTest.kt +++ b/src/test/kotlin/org/jitsi/jibri/selenium/status_checks/MediaReceivedStatusCheckTest.kt @@ -17,7 +17,9 @@ class MediaReceivedStatusCheckTest : ShouldSpec() { override fun isolationMode(): IsolationMode? = IsolationMode.InstancePerLeaf private val clock: FakeClock = spyk() - private val callPage: CallPage = mockk() + private val callPage: CallPage = mockk { + every { numHiddenParticipants() } returns 0 + } private val logger: Logger = mockk(relaxed = true) private val check = MediaReceivedStatusCheck(logger, clock)