Repository navigation
fix: let only the latest subscription detach a camera track - #1
Merged
Magmusacy merged 1 commit intoNov 12, 2026
Merged
Conversation
A track has one native consumer slot, shared by all processor handles. react-client starts the new effect before it stops the old one, so remove() on the old subscription emptied the slot and froze the video. Detach only from the latest subscription of the track.
Magmusacy
self-requested a review
November 5, 2026 11:51
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.
Problem
When a camera effect directly replaces another effect on the same track, the new effect gets no frames. Its video stops on the last frame. The changes effect ->
nullandnull-> effect workCause
applyMiddlewarein@fishjam-cloud/react-clientstarts the new effect, publishes it, then stops the old effect.subscription.remove(), andremove()always callsprocessor.detach().detach()does not know which consumer calls it. It removes the new consumer and the tap of the track:fj_detachTrackIdOnWorkerQueueandCameraFrameTap.detachConsumerTrackPrivate.releaseCameraFrameTapactiveSubscriptionsguard does not stop this. Its key is the processor handle, andgetCameraFrameProcessorreturns a new handle on each call.Fix
Only the latest subscription of a track can detach. An older subscription still clears its own callback.
The map changes on the same JS tick as
processor.attach, so it follows the order of the native slot. A failed attach does not change the map