Repository navigation
Keep the camera's sender when the camera turns off - #63
Merged
Merged
Conversation
removeVideoTrack took the camera's sender off with pc.removeTrack. The SFU opens each connection with two video m-lines the client can send on, one meant for the camera and one for the screen, and addTrack never reuses a transceiver that has sent. So when the camera came back it took the second m-line. A screen share after that got a transceiver with no mid, the SFU never received it, and everyone else saw "<name>'s screen isn't coming through". With the screen shared first, the camera had no m-line left to come back on. That's GRYT-1329. removeVideoTrack now pauses the sender with replaceTrack(null), like removeScreenVideoTrack does, and the next addVideoTrack puts the new track on it. Nothing in the SDP changes, so it doesn't ask the SFU to renegotiate either. A camera that comes back goes out under the first stream it was given, so apps have to announce that stream's id. The client already does that for the screen. Its camera half is a client PR that waits for this release. check-camera-sender runs useSFU through the engine harness: camera on, off and on, then a share, twice. It fails against main. Co-Authored-By: Claude Opus 5 <[email protected]>
sivert-io
added a commit
to Gryt-chat/mobile
that referenced
this pull request
Sep 21, 2026
…(GRYT-1336) 0.5.9 is Gryt-chat/voice#63 and #64. #63 pauses the camera's sender when the camera turns off. It used to remove it, and a camera turned back on then took the screen's m-line (GRYT-1329). #64 lowers the microphone's default ceiling from 510 to 64 kbps. The phone passes no channel bitrate, so it gets the new default. A sender keeps its stream id through replaceTrack, so a paused sender comes back under the first stream's id. But the phone announced each new stream's own id. On 0.5.9 everyone else would see a placeholder for a camera turned back on, and a second screen share already did that on 0.5.8. Now the phone announces the id of the first stream each role sent on the connection. It's the same rule as the desktop client's senderStreamIds.ts (client#624, #625). Co-Authored-By: Claude Opus 5 <[email protected]>
sivert-io
added a commit
to Gryt-chat/mobile
that referenced
this pull request
Sep 21, 2026
…(GRYT-1336) (#232) 0.5.9 is Gryt-chat/voice#63 and #64. #63 pauses the camera's sender when the camera turns off. It used to remove it, and a camera turned back on then took the screen's m-line (GRYT-1329). #64 lowers the microphone's default ceiling from 510 to 64 kbps. The phone passes no channel bitrate, so it gets the new default. A sender keeps its stream id through replaceTrack, so a paused sender comes back under the first stream's id. But the phone announced each new stream's own id. On 0.5.9 everyone else would see a placeholder for a camera turned back on, and a second screen share already did that on 0.5.8. Now the phone announces the id of the first stream each role sent on the connection. It's the same rule as the desktop client's senderStreamIds.ts (client#624, #625). Co-authored-by: Claude Opus 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The engine half of GRYT-1329. The task id is left out of the branch and the title so this merge doesn't close the task. The client PR, Gryt-chat/client#625, closes it.
What was wrong
Turn your camera on, off and on again, then share your screen. Everyone else in the call gets "'s screen isn't coming through" for as long as the share lasts. Share first and then turn the camera off and on, and the camera doesn't come back.
The SFU is always the offerer. It opens each connection with four receive transceivers: microphone, camera, screen video and screen audio (
CreatePeerConnectionin the SFU'sinternal/webrtc/peer.go). A client can only send on an m-line the SFU offered.removeVideoTracktook the camera off withpc.removeTrack. That leaves the transceiver behind as one that has sent, andaddTracknever reuses those. So the camera coming back took the next free video m-line, which was the screen's. The share then got a transceiver with no mid, and the SFU never received it.Alice's transceivers, read in Chrome after each step, against a local server and SFU from main. Each entry is
mid:kind:currentDirection:track:On main a second round is worse. The camera lands on the screen's transceiver, which never got a mid, so the camera doesn't get out either. On this branch a second round stays on mids 1 and 2.
What changed
removeVideoTrackpauses the camera's sender withreplaceTrack(null)and keeps it, likeremoveScreenVideoTrackdoes. The nextaddVideoTracktakes the replace path and puts the new track on the same sender. Nothing in the SDP changes, so it no longer asks the SFU to renegotiate.check-camera-senderrunsuseSFUthrough the engine harness: camera on, off and on, then a share, twice. It checks that the camera keeps one sender, thatremoveTracknever runs, and that pausing and resuming don't renegotiate. Against main it fails with "turning the camera off removed its sender".Where to look
replaceTrackdoesn't change the msid, so a camera that comes back is still published under the first camera stream's id. Apps have to announce that id invoice:camera:state. The client does that for the screen, and Turning the camera off and on left no room for a screen share (GRYT-1329) client#625 does it for the camera. I ran this branch with the client's old id handling, and the camera never got a frame for the other guest after it came back. So the client's pin and its id change go in together.useCamera.tssendsnext.idon every camera-on, so it has to change when it takes this release. That's GRYT-1336. Its second screen share should have the same mismatch already.addTrackfor the other role can reuse it, and the engine holds one sender under two roles. On main that already happens screen first: a share started and stopped during an SFU signalling outage hands its sender to the camera. With this change the camera-first order does it too. I reproduced both by holding therenegotiatemessage in the page. In practice it takes a signalling outage, so it's filed as GRYT-1337 with a likely fix.removeTracktoo, sinceOnTrackwaits for the connection to close. With this, a connection has one camera track, where main added another each time the camera came back.Release order
@gryt/voiceas 0.5.9.yarn installforyarn.lockafter the release, then CI can go green.What fixed slots would need to change for several shares (GRYT-1335)
This fix keeps the camera and the screen on one m-line each, and neither cares which one it gets. Several windows shared at once, each as its own stream, won't fit in four fixed slots, though. For that:
CreatePeerConnectionmakes exactly four, and the SFU is always the offerer, so a client can't add an m-line itself. Itsrenegotiatemessage would have to say how many video and audio senders it has, and the SFU would add receive transceivers to match beforeCreateOffer.isMicrophoneinhandler_join.gofinds the mic by position: transceiver 0, audio. New m-lines added after it keep that true.OnTrackwaits on it. That's one idle track today. With windows added and removed through a long call, every viewer would collect dead tracks.addTrackwon't reuse one that has sent.voice:screen:statecarries one video and one audio stream id per person, and the client'ssenderStreamIds.tskeys ids by role.🤖 Generated with Claude Code