Skip to content

Improve pinned participant state on rejoin with new session#1644

Open
rahul-lohra wants to merge 12 commits intodevelopfrom
feature/rahullohra/pinned_users
Open

Improve pinned participant state on rejoin with new session#1644
rahul-lohra wants to merge 12 commits intodevelopfrom
feature/rahullohra/pinned_users

Conversation

@rahul-lohra
Copy link
Copy Markdown
Contributor

@rahul-lohra rahul-lohra commented Apr 14, 2026

Goal

Solves the gap where:

pinned participant leaves
joins again with another session
existing participants have obsolete pins information, and render the user unpinned

ParticipantJoined SFU event now will carry is_pinned property so you can use it for updating the client-side state correctly

Implementation

React PR

  • Add a property is_pinned in ParticipantJoinedEvent

Core logic

fun handleEvent(event: VideoEvent) {
   is ParticipantJoinedEvent ->  updateServerSidePins(internalParticipants, event)
}

internal fun updateServerSidePins(internalParticipants: Map<SessionId, ParticipantState>, event: ParticipantJoinedEvent) {
    val participantSessionId = event.participant.session_id
    if (internalParticipants.containsKey(participantSessionId)) {
        if (event.isPinned) {
                updateServerSidePins(updatedList)
            }
        } else {
            _serverPins.value = _serverPins.value.filter { it.key != participantSessionId }
        }
    }
}

There are auto generated files, which are not the core part of this PR

  1. SignalLostSignalingServiceDecorator.kt
  2. SignalingServiceTracerDecorator.kt
  3. go files

🎨 UI Changes

None

Testing

Copy this exactly:

🧪 Testing Scenarios

Scenario A: Pin propagation on join

  • Start a new call from Web with User A
  • User B joins the call
  • On User A, perform "Pin Everyone" on User B
  • User C joins the call
  • Verify User C sees User B pinned
    Status: Passed

Scenario A – Rejoin Flow (User C)

  • User C leaves the call
  • User C rejoins
  • Verify On User C still sees User B pinned
    Status: Passed

Scenario A – Rejoin Flow (User B)

Step 1: User B leaves

  • User B leaves the call
  • Verify on User C that no participants are pinned
    Status: Passed

Step 2: User B rejoins flow

  • User B rejoins the call
  • Verify on User B that User B appears pinned
    Observed:
  • User B is not pinned
  • Spotlight UI is not rendered
    Root Cause:
  • Pins from JoinCallResponseEvent contains a different sessionId
  • Leads to mismatch while restoring pinned state
  • Server-side issue (known)
    Status: Failed (expected due to known issue)

Step 3: User B rejoins flow
State on User C

  • Verify on User C still sees User B pinned
    Status: Passed

Notes

  • Inconsistency observed:
    • User B (self view) → not pinned
    • User C (remote view) → sees User B pinned
    • Due to sessionId mismatch on rejoin (server-side)

Summary

  • Pin state works for new joins and non-pinned user rejoins
  • Known issue: pinned user rejoin fails due to sessionId inconsistency

@rahul-lohra rahul-lohra self-assigned this Apr 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 14, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 14, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-video-android-core 12.02 MB 12.04 MB 0.02 MB 🟢
stream-video-android-ui-xml 5.68 MB 5.68 MB 0.00 MB 🟢
stream-video-android-ui-compose 6.28 MB 6.28 MB 0.00 MB 🟢

@rahul-lohra rahul-lohra changed the title [WIP] Pinned Users Improve pinned participant state on rejoin with new session Apr 29, 2026
@rahul-lohra rahul-lohra marked this pull request as ready for review April 29, 2026 18:16
@rahul-lohra rahul-lohra requested a review from a team as a code owner April 29, 2026 18:16
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Walkthrough

A shell script automates SFU proto generation from the GetStream protocol repository. The signaling system gains a metrics-reporting RPC. Participant pinning state now uses SessionId types instead of raw strings. Event models are extended with pinned participant flags, and RTP-related protobuf messages are introduced for metrics collection.

Changes

Cohort / File(s) Summary
Proto Schema & Generation
generate-sfu.sh, stream-video-android-core/src/main/proto/video/sfu/event/events.proto, stream-video-android-core/src/main/proto/video/sfu/models/models.proto, stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal.proto
New Bash script automates SFU proto generation via shallow clone and directory copy. Proto files extended with is_pinned flag in ParticipantJoined, negotiation_id in SubscriberOffer and SendAnswerRequest, webrtc_version in ClientDetails, new SDK types (Python, VisionAgents), and new RTP-related messages (RtpBase, InboundRtp, OutboundRtp, RemoteInboundRtp, RemoteOutboundRtp) for metrics.
Generated API & Twirp
stream-video-android-core/api/stream-video-android-core.api, stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal.twirp.go
API surface updated with new SendMetrics RPC, updated constructor signatures for events/messages to include pinning and negotiation fields, and new RTP model types. Twirp Go code regenerated with complete SendMetrics end-to-end implementation including client stubs, server routing, request/response marshaling, and hook lifecycle support.
Event & State Management
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt, stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt, stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt
ParticipantJoinedEvent constructor updated to include isPinned flag. CallState retypes pinned participant state to use SessionId instead of String, adds server-side pin synchronization in join/leave event handlers, and includes additional debug logging. RTCEventMapper now propagates pin status from proto to ParticipantJoinedEvent.
Signaling Decorators
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/utils/SignalLostSignalingServiceDecorator.kt, stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/trace/SignalingServiceTracerDecorator.kt
Both decorators add sendMetrics method support. SignalLostSignalingServiceDecorator forwards requests without applying error-propagation logic; SignalingServiceTracerDecorator delegates without emitting trace logs.
UI Components & Tests
stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt, stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantsLayout.kt, stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt
ParticipantLabel switches from derived state to directly observing pinnedParticipants flow. Minor whitespace cleanup in layout. Test updated to use public call.state.pins.updateLocalPins(...) API instead of direct internal state manipulation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops of joy through proto fields so bright,
SessionIds hop where pins take flight,
Metrics gathered, RTP flows stream true,
SignalServer metrics, fresh and new! 📊✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes all required sections: Goal clearly states the problem and solution, Implementation describes the approach with code snippets and references the React PR, Testing section provides detailed scenarios with pass/fail status.
Title check ✅ Passed The title accurately describes the main objective: improving pinned participant state handling during rejoin scenarios with new session IDs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/rahullohra/pinned_users

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
generate-sfu.sh (3)

3-3: Harden shell options for safer failures.

Line 3 uses set -e only; -u and -o pipefail make failure behavior more deterministic in automation scripts.

Suggested fix
-set -e
+set -euo pipefail
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-sfu.sh` at line 3, Replace the lone "set -e" shell option with
hardened options to avoid silent failures: change the existing set -e line to
set -euo pipefail (optionally also set IFS=$'\n\t' afterwards) so undefined
variables and pipeline failures are treated as errors; update the single "set
-e" occurrence in the script accordingly.

5-5: Consider HTTPS default for better portability.

Line 5 requires SSH key setup (git@github.com:...), which commonly breaks local/CI usage unless keys are preconfigured.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-sfu.sh` at line 5, The REPO_URL variable in generate-sfu.sh is set
to an SSH URL which requires SSH keys; change it to use the HTTPS form (e.g.,
https://github.com/GetStream/protocol.git) or make REPO_URL default to the HTTPS
URL but allow overriding via an environment variable (keep the REPO_URL symbol
and read from ${REPO_URL:-"https://github.com/GetStream/protocol.git"} so CI and
local users without SSH keys can clone by default).

21-25: Use trap for guaranteed cleanup of the temp clone directory.

If clone/copy/Spotless fails, cleanup at Line 59 is skipped and leaves build artifacts behind.

Suggested fix
 rm -rf "$CLONE_DIR"
 mkdir -p "$BUILD_DIR"
+
+cleanup() {
+  rm -rf "$CLONE_DIR"
+}
+trap cleanup EXIT
+
 git clone --depth=1 --branch "$REFERENCE_VALUE" "$REPO_URL" "$CLONE_DIR"
@@
-# Step 6: Delete CLONE_DIR
-echo "🗑 Cleaning up cloned repo..."
-rm -rf "$CLONE_DIR"
-
 echo "✅ Done."

Also applies to: 57-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-sfu.sh` around lines 21 - 25, Add a shell trap to guarantee cleanup
of the temporary clone directory by registering a handler that removes
"$CLONE_DIR" (and any other temp artifacts like "$BUILD_DIR" if desired) on EXIT
and ERR; specifically, after creating or cloning into "$CLONE_DIR" (around where
git clone and cd "$CLONE_DIR" occur) add a trap 'cleanup' or inline trap that
runs rm -rf "$CLONE_DIR" (and optionally rm -rf "$BUILD_DIR") so the directory
is removed regardless of script failure or early exit, and ensure any explicit
cleanup at lines 57-59 is removed or made idempotent because the trap will
already handle it.
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/trace/SignalingServiceTracerDecorator.kt (1)

128-130: Consider adding a comment for consistency.

The sendStats method at line 123-126 has a // Not traced comment explaining why tracing is skipped. Adding a similar comment to sendMetrics would improve maintainability and make the intentional omission explicit.

📝 Suggested comment addition
+    // Not traced - high-frequency metrics reporting
     override suspend fun sendMetrics(sendMetricsRequest: SendMetricsRequest): SendMetricsResponse {
         return target.sendMetrics(sendMetricsRequest)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/trace/SignalingServiceTracerDecorator.kt`
around lines 128 - 130, Add a short explanatory comment above the sendMetrics
override to match sendStats' style (e.g., "// Not traced") so maintainers know
tracing is intentionally skipped; locate the sendMetrics(sendMetricsRequest:
SendMetricsRequest): SendMetricsResponse method in
SignalingServiceTracerDecorator and insert the same brief comment as used for
sendStats to keep consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@generate-sfu.sh`:
- Around line 23-35: The git clone invocation always passes --branch
"$REFERENCE_VALUE", which breaks when REFERENCE_TYPE="commit" because commit
SHAs are not branch names; change the logic so clone does not include --branch
for commit mode (e.g., branch/tag: use --branch "$REFERENCE_VALUE", commit:
clone without --branch or clone with --no-checkout and then fetch/checkout the
SHA). Update the script around the git clone and the REFERENCE_TYPE conditional
to handle REFERENCE_TYPE ("branch", "tag", "commit") separately so that when
REFERENCE_TYPE is "commit" the clone succeeds and the subsequent git fetch/git
checkout of REFERENCE_VALUE (the SHA) in the commit branch-handling block (the
lines referencing REFERENCE_TYPE, REFERENCE_VALUE and the git fetch/checkout
commands) is reached and used.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt`:
- Around line 1107-1112: Replace the leftover debug call using
android.util.Log.d("Noob", ...) with the module's logger: use the existing
logger instance (logger) to emit the same message at an appropriate level (e.g.,
logger.d or logger.v) and include the _serverPins.value.map { it.key
}.joinToString(",") payload; guard the log emission behind
StreamVideoImpl.developmentMode (or existing development-mode check) to respect
verbosity rules and avoid hardcoded tags.
- Around line 1268-1281: The debug Log.d call in updateServerSidePins (the one
using hardcoded "Noob" tag and printing
pins/internalParticipantsText/pinnedInCall) must be removed or replaced with the
module logger and guarded by StreamVideoImpl.developmentMode; locate the Log.d
invocation inside updateServerSidePins and either delete it or change it to use
the existing logger (e.g., logger.debug/trace) and wrap the log emit with a
developmentMode check so production builds don't leak verbose debug output.
- Around line 1505-1507: The code creates an unused local variable
participantNames via participants.joinToString in CallState.kt; remove the dead
assignment (the participantNames declaration) from the enclosing function or
block (or, if intended for logging, replace the unused variable with a direct
log/usage) so there is no unused local variable left; ensure no other logic
depends on participantNames before deleting.

In
`@stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt`:
- Around line 131-137: The test constructs a PinUpdate with arguments reversed
relative to the implementation: when map key "1" is the sessionId, the PinUpdate
must be created as PinUpdate(userId, sessionId) to match CallState.pin()
behavior; update the call to call.state.pins.updateLocalPins(...) so the map
entry uses PinUpdate("userId", "1") (wrapped in PinUpdateAtTime) instead of
PinUpdate("1", "userId") so keys and PinUpdate fields align with the
PinUpdateAtTime/PinUpdate usage.

---

Nitpick comments:
In `@generate-sfu.sh`:
- Line 3: Replace the lone "set -e" shell option with hardened options to avoid
silent failures: change the existing set -e line to set -euo pipefail
(optionally also set IFS=$'\n\t' afterwards) so undefined variables and pipeline
failures are treated as errors; update the single "set -e" occurrence in the
script accordingly.
- Line 5: The REPO_URL variable in generate-sfu.sh is set to an SSH URL which
requires SSH keys; change it to use the HTTPS form (e.g.,
https://github.com/GetStream/protocol.git) or make REPO_URL default to the HTTPS
URL but allow overriding via an environment variable (keep the REPO_URL symbol
and read from ${REPO_URL:-"https://github.com/GetStream/protocol.git"} so CI and
local users without SSH keys can clone by default).
- Around line 21-25: Add a shell trap to guarantee cleanup of the temporary
clone directory by registering a handler that removes "$CLONE_DIR" (and any
other temp artifacts like "$BUILD_DIR" if desired) on EXIT and ERR;
specifically, after creating or cloning into "$CLONE_DIR" (around where git
clone and cd "$CLONE_DIR" occur) add a trap 'cleanup' or inline trap that runs
rm -rf "$CLONE_DIR" (and optionally rm -rf "$BUILD_DIR") so the directory is
removed regardless of script failure or early exit, and ensure any explicit
cleanup at lines 57-59 is removed or made idempotent because the trap will
already handle it.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/trace/SignalingServiceTracerDecorator.kt`:
- Around line 128-130: Add a short explanatory comment above the sendMetrics
override to match sendStats' style (e.g., "// Not traced") so maintainers know
tracing is intentionally skipped; locate the sendMetrics(sendMetricsRequest:
SendMetricsRequest): SendMetricsResponse method in
SignalingServiceTracerDecorator and insert the same brief comment as used for
sendStats to keep consistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5997c08f-386f-412e-9052-dacf26066c5e

📥 Commits

Reviewing files that changed from the base of the PR and between 1d744dc and dada793.

⛔ Files ignored due to path filters (6)
  • stream-video-android-core/src/main/proto/video/sfu/event/events.pb.go is excluded by !**/*.pb.go
  • stream-video-android-core/src/main/proto/video/sfu/event/events_vtproto.pb.go is excluded by !**/*.pb.go
  • stream-video-android-core/src/main/proto/video/sfu/models/models.pb.go is excluded by !**/*.pb.go
  • stream-video-android-core/src/main/proto/video/sfu/models/models_vtproto.pb.go is excluded by !**/*.pb.go
  • stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal.pb.go is excluded by !**/*.pb.go
  • stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal_vtproto.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (14)
  • generate-sfu.sh
  • stream-video-android-core/api/stream-video-android-core.api
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/utils/SignalLostSignalingServiceDecorator.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/trace/SignalingServiceTracerDecorator.kt
  • stream-video-android-core/src/main/proto/video/sfu/event/events.proto
  • stream-video-android-core/src/main/proto/video/sfu/models/models.proto
  • stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal.proto
  • stream-video-android-core/src/main/proto/video/sfu/signal_rpc/signal.twirp.go
  • stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt
  • stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt
  • stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantsLayout.kt
💤 Files with no reviewable changes (1)
  • stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantsLayout.kt

Comment thread generate-sfu.sh
@rahul-lohra rahul-lohra added the pr:improvement Enhances an existing feature or code label Apr 29, 2026
@rahul-lohra rahul-lohra changed the title Improve pinned participant state on rejoin with new session [AND-1144] Improve pinned participant state on rejoin with new session Apr 29, 2026
@rahul-lohra rahul-lohra changed the title [AND-1144] Improve pinned participant state on rejoin with new session Improve pinned participant state on rejoin with new session Apr 29, 2026
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
36.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Enhances an existing feature or code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant