Skip to content

feat(call): keep call running while browsing other conversations (minimized call bar)#18444

Draft
Rikdekker wants to merge 1 commit into
nextcloud:mainfrom
Rikdekker:feat/minimized-call-while-browsing
Draft

feat(call): keep call running while browsing other conversations (minimized call bar)#18444
Rikdekker wants to merge 1 commit into
nextcloud:mainfrom
Rikdekker:feat/minimized-call-while-browsing

Conversation

@Rikdekker

@Rikdekker Rikdekker commented Jun 26, 2026

Copy link
Copy Markdown

☑️ Resolves

Navigating to another conversation while in a call used to end the call, forcing users to leave the call to read or reply in other chats. This PR keeps the call running and shows it as a persistent minimized in-call bar while another conversation opens as chat-only — matching how Teams, Google Meet and Zoom keep a "return to call" banner while you browse.

Talk switch

How it works

Talk shares a single signaling connection that can only be in one room at a time, and a single active session per user. So the browsed conversation is opened without joining its signaling room or creating a second active session:

  • The call's conversation keeps the signaling room and the active session, so audio/video/screenshare keep flowing.
  • The browsed conversation's chat works over REST + polling (fetchConversation + fetchParticipants, no POST participants/active). Sending, history, reactions and read markers all work; only typing/presence push is unavailable while the call runs — indicated by a subtle hint.
  • Starting a call in another conversation while one is already running asks for confirmation first (leave the current call and join here).

The core invariant: while a call is active, no code path may create a second active session (a central guard in the joinConversation Vuex action forces chat-only for any other conversation; a real join only resumes after the call is left). This prevents the call's session from being superseded and dropped.

pr.talk.mp4

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

🖌️ UI Checklist

🖼️ Screenshots / Screencasts

🏚️ Before 🏡 After
Opening another chat during a call ends the call The call keeps running as a dark in-call bar under the header; the other chat opens as chat-only

🚧 Tasks

  • Keep the call running when navigating to another conversation (no DELETE /call/{token}, no signaling.joinRoom for the browsed room)
  • Persistent minimized in-call bar (conversation name, call timer, mute, return-to-call, leave); full-width top bar on mobile-web
  • Subtle hint in the browsed chat that live updates are poll-only during a call
  • Confirmation dialog before starting a second call (leave current call & join here)
  • Single-active-session invariant enforced centrally in joinConversation

🏁 Checklist

  • 🌏 Tested with different browsers / clients:
    • Chromium (Chrome / Edge / Opera / Brave)
    • Firefox
    • Safari
    • Talk Desktop
    • Integrations with Files sidebar and other apps
    • Not risky to browser differences / client
  • 🖌️ Design was reviewed, approved or inspired by the design team
  • ⛑️ Tests are included or not possible
  • 📗 User documentation in https://github.com/nextcloud/documentation/tree/master/user_manual/talk has been updated or is not required

Notes for reviewers

  • Scope: web + Talk Desktop (same bundle). Native Talk Android/iOS are separate repositories and out of scope.
  • Trade-off: the browsed conversation's chat is poll-only while a call is minimized (no typing/presence push, ~0.5–1s latency). This is surfaced with an NcNoteCard hint and is the intended behavior given the single signaling connection.
  • No new server requirements: purely client-side. No new endpoints or capabilities; the existing session-state capability and REST chat polling are reused. Minimum Nextcloud version is unchanged (NC 35 / Talk 25).
  • Bar styling: reuses the existing $color-call-background call-chrome token with white-on-dark text (consistent with CallTime/VideoBottomBar).
  • Design-team review on the in-call bar placement/styling would be welcome.

🛠️ API Checklist

No API changes — this is a frontend-only feature.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not possible
  • 📘 API documentation in docs/ has been updated or is not required
  • 🔖 Capability is added or not needed

@Antreesy

Copy link
Copy Markdown
Contributor

Thanks for the contribution!

As it's a significant architectural change even in the current state (that is prone to errors as there is already a lot of things built on top of assumption HPB connection is present), we need more time to first discuss whether we'd want to continue with this approach before starting to review code changes.

For the note: for this PR to work #16733 might be needed as a pre-requisite, to allow basic functionality without signaling (websocket) connection

@Rikdekker Rikdekker force-pushed the feat/minimized-call-while-browsing branch from 171c5af to b2f31d9 Compare June 26, 2026 13:56
@Rikdekker

Copy link
Copy Markdown
Author

Thanks for the quick feedback, and fully agree this needs an approach discussion before a line-by-line review.

I dug into the #16733 / #16725 relationship and want to share what I found, plus a small refactor I just pushed to address the "built on the assumption HPB is present" concern.

On the HPB dependency

The good news is that the two things the call relies on already survive without joining a signaling room for the browsed conversation:

  • Sending messages is a plain REST POST .../chat/{token} — it does not require a signaling session, so it works with and without HPB.
  • The call keeps running because the single signaling connection stays in the call's room (currentRoomToken is never moved): without HPB the internal _startPullingMessages loop keeps that session alive, and with HPB the websocket does. I verified the call survives >60s while browsing other conversations (internal signaling). I don't have an HPB test instance handy, so HPB confirmation would be very welcome.

So #16733 isn't a hard blocker for sending in this flow. But you're right that the underlying concern is real, and it pointed me at a genuine smell in my first version:

The actual problem (and fix)

My first version enabled the browsed conversation's chat by calling updateLastJoinedConversationToken(B) — i.e. pretending B's signaling room was joined. That's exactly the "lots of things assume a real join" footgun: it would also make the typing indicator and other signaling-gated code treat B as joined, which is wrong.

I've replaced that with a dedicated, explicit concept instead of overloading "joined":

  • A computed currentConversationChatAvailable = currentConversationIsJoined || isCallMinimized gates the chat input / placeholder and the start-call button.
  • currentConversationIsJoined (and therefore typing, WebRTC, presence) is left untouched — the browsed conversation is not treated as signaling-joined.
  • The chat-only branch no longer writes lastJoinedConversationToken.

This keeps all existing HPB/signaling assumptions intact (nothing newly believes a non-joined room is joined), and it composes cleanly with #16733: if/when that lands, its currentConversationIsJoinedWithoutHPB can simply be OR-ed into the same computed. My case ("deliberately not joining") and #16733's case ("tried to join, failed") are distinct, so I kept them as separate inputs rather than reusing the failure flag.

I've pushed this refactor so the diff reflects the approach, but I'm very happy to adjust direction based on what you'd prefer — including waiting on #16733 or gating the whole feature behind it. Keeping it as a draft until we align.

@Rikdekker Rikdekker force-pushed the feat/minimized-call-while-browsing branch 2 times, most recently from 499ece2 to 83ffd96 Compare June 30, 2026 06:34
…imized call bar)

Navigating to another conversation while in a call used to end the call,
forcing users to leave the call to read other chats. Now the call keeps
running and is shown as a minimized in-call bar (conversation name, timer,
mute, return-to-call, leave), while the other conversation opens as chat-only.

Talk shares a single signaling connection that can only be in one room at a
time, so the browsed conversation is opened without joining its signaling
room: its chat works over REST + polling (no typing/presence push, indicated
by a subtle hint). The call's conversation keeps the signaling room and the
active session, so audio/video/screenshare keep flowing.

Starting a call in another conversation while one is already running asks for
confirmation first (leave the current call and join here), matching the
behavior of Teams/Zoom/Meet. Works on web and the desktop client; the
in-call bar adapts to a full-width top bar on mobile-web.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
@Rikdekker Rikdekker force-pushed the feat/minimized-call-while-browsing branch from 83ffd96 to 984dbf0 Compare June 30, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants