Skip to content

Detect guest audio from MediaStream tracks instead of relying on Video/Data channel#6039

Closed
tareko wants to merge 1 commit into
nextcloud:masterfrom
tareko:fix-missing-guests
Closed

Detect guest audio from MediaStream tracks instead of relying on Video/Data channel#6039
tareko wants to merge 1 commit into
nextcloud:masterfrom
tareko:fix-missing-guests

Conversation

@tareko
Copy link
Copy Markdown
Contributor

@tareko tareko commented Apr 10, 2026

Guest users whose DataChannel never establishes would have isAudioEnabled stay false even though audio tracks exist in the MediaStream. This caused guests to appear as 'connecting' with no audio on mobile while working on desktop.

  • handleStreamChange() now sets isAudioEnabled based on audio track presence
  • handleIceConnectionStateChange() preserves audio/video state during ICE CHECKING/NEW if tracks are still present in the peer connection
  • Add enhanced diagnostic logging for guest participant debugging
  • Add GuestAudioDetectionTest documenting the expected behavior

This PR resolves issue #6037 . The debugging and code was done with the help of OpenCode using GLM-5.1 and Qwen-3.6 Plus.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

…ing on DataChannel

Guest users whose DataChannel never establishes would have isAudioEnabled
stay false even though audio tracks exist in the MediaStream. This caused
guests to appear as 'connecting' with no audio on mobile while working on
desktop.

- handleStreamChange() now sets isAudioEnabled based on audio track presence
- handleIceConnectionStateChange() preserves audio/video state during ICE
  CHECKING/NEW if tracks are still present in the peer connection
- Add enhanced diagnostic logging for guest participant debugging
- Add GuestAudioDetectionTest documenting the expected behavior

Signed-off-by: Tarek Loubani <tarek@tarek.org>
@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 13, 2026

Hi @tareko
thanks for contributing! we will have a closer look soon!

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 20, 2026

Review is planned for this week

@github-actions
Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 29, 2026

Please excuse the current delays in pull request reviews.
The PR is on our list.

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented May 5, 2026

still on our list. Review is not forgotten

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented May 12, 2026

We are now trying to catch up with the pull request reviews. The PR is not forgotten and should be picked up soon.

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented May 26, 2026

Apologies for the delay, i was out sick for a week and now try to catch up

mahibi added a commit that referenced this pull request May 29, 2026
This commit contains the code from #6039 (thank you tareko!)
as it needed to be combined with more fixes during it's review.

  PeerConnectionWrapper — native crash fixes:
  - Call peerConnection.close() before dataChannel.dispose() so the WebRTC
    signaling thread stops dispatching callbacks before native objects are freed
  - Call peerConnection.close() outside the synchronized lock; holding the lock
    deadlocks because close() blocks waiting for the signaling thread, which
    itself needs the lock to run its callbacks
  - Null out peerConnection before calling close() so in-flight callbacks see
    null and return early instead of NPE-ing on peerConnection.hashCode() in
    onIceConnectionChange, onCreateFailure, and onSetFailure
  - Call dataChannel.unregisterObserver() before dispose() in both
    removePeerConnection() and onDataChannel() to prevent the native observer
    from calling back into a disposed Java DataChannel object

  LocalStateBroadcasterNoMcu — initial mute state not sent to peers:
  - IceConnectionStateObserver.job was never started, so local audio/video/
    speaking state was never sent to newly connected participants; only a
    manual mute+unmute triggered a broadcast
  - handleCallParticipantAdded now accepts the live StateFlow<ParticipantUiState>
    and uses first { it.isConnected } to send initial state exactly once when
    ICE connects; base class gets a concrete StateFlow overload that defaults
    to the existing snapshot path so MCU behaviour is unchanged
  - Fix ParticipantHandler initial isConnected = false (was true), which caused
    retrying when ICE actually connected

  ParticipantHandler — guest audio not detected (cherry-pick of PR #6039):
  - handleStreamChange() now sets isAudioEnabled from audio track presence,
    not only isStreamEnabled from video tracks; guests whose DataChannel never
    opens were permanently shown as muted despite having an active audio track
  - handleIceConnectionStateChange() preserves audio/video state during ICE
    NEW/CHECKING if tracks are still present, rather than resetting to false
    unconditionally and relying on a DataChannel message to restore it
  - Add GuestAudioDetectionTest and diagnostic logging

AI-assistant: Claude Code v2.1.142 (Claude Sonnet 4.6)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented May 29, 2026

Thank you @tareko !

During the review i encountered other bugs/crashes and in the end i ended up with another branch where I've incorporated your changes. See #6261
Hope that's okay for you.

Thank you very much for your good contributions and please excuse the long delay for reviews! We will try to improve in this regard!

@mahibi mahibi closed this May 29, 2026
@github-project-automation github-project-automation Bot moved this to ☑️ Done in 💬 Talk team May 29, 2026
mahibi added a commit that referenced this pull request May 29, 2026
This commit contains the code from #6039 (thank you tareko!)
as it needed to be combined with more fixes during it's review.

  PeerConnectionWrapper — native crash fixes:
  - Call peerConnection.close() before dataChannel.dispose() so the WebRTC
    signaling thread stops dispatching callbacks before native objects are freed
  - Call peerConnection.close() outside the synchronized lock; holding the lock
    deadlocks because close() blocks waiting for the signaling thread, which
    itself needs the lock to run its callbacks
  - Null out peerConnection before calling close() so in-flight callbacks see
    null and return early instead of NPE-ing on peerConnection.hashCode() in
    onIceConnectionChange, onCreateFailure, and onSetFailure
  - Call dataChannel.unregisterObserver() before dispose() in both
    removePeerConnection() and onDataChannel() to prevent the native observer
    from calling back into a disposed Java DataChannel object

  LocalStateBroadcasterNoMcu — initial mute state not sent to peers:
  - IceConnectionStateObserver.job was never started, so local audio/video/
    speaking state was never sent to newly connected participants; only a
    manual mute+unmute triggered a broadcast
  - handleCallParticipantAdded now accepts the live StateFlow<ParticipantUiState>
    and uses first { it.isConnected } to send initial state exactly once when
    ICE connects; base class gets a concrete StateFlow overload that defaults
    to the existing snapshot path so MCU behaviour is unchanged
  - Fix ParticipantHandler initial isConnected = false (was true), which caused
    retrying when ICE actually connected

  ParticipantHandler — guest audio not detected (cherry-pick of PR #6039):
  - handleStreamChange() now sets isAudioEnabled from audio track presence,
    not only isStreamEnabled from video tracks; guests whose DataChannel never
    opens were permanently shown as muted despite having an active audio track
  - handleIceConnectionStateChange() preserves audio/video state during ICE
    NEW/CHECKING if tracks are still present, rather than resetting to false
    unconditionally and relying on a DataChannel message to restore it
  - Add GuestAudioDetectionTest and diagnostic logging

AI-assistant: Claude Code v2.1.142 (Claude Sonnet 4.6)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ☑️ Done

Development

Successfully merging this pull request may close these issues.

2 participants