From d4f97c714f5c30ae2c402353481f799ee1a58a3d Mon Sep 17 00:00:00 2001 From: Tomasz Mazur <47872060+AHGIJMKLKKZNPJKQR@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:59:06 +0100 Subject: [PATCH 1/4] Group tracks into streams --- packages/ts-client/src/FishjamClient.ts | 37 +++++++++- packages/ts-client/src/LocalStream.ts | 69 +++++++++++++++++++ packages/ts-client/src/index.ts | 1 + .../webrtc-client/src/tracks/LocalTrack.ts | 6 +- packages/webrtc-client/src/webRTCEndpoint.ts | 16 +++-- 5 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 packages/ts-client/src/LocalStream.ts diff --git a/packages/ts-client/src/FishjamClient.ts b/packages/ts-client/src/FishjamClient.ts index 992bc862f..28326903d 100644 --- a/packages/ts-client/src/FishjamClient.ts +++ b/packages/ts-client/src/FishjamClient.ts @@ -26,6 +26,7 @@ import { isAuthError } from './auth'; import { connectEventsHandler } from './connectEventsHandler'; import { TrackTypeError } from './errors'; import { isComponent, isJoinError, isPeer } from './guards'; +import { LocalStream } from './LocalStream'; import { MessageQueue } from './messageQueue'; import { ReconnectManager } from './reconnection'; import type { @@ -99,6 +100,10 @@ export class FishjamClient = new Map(); + constructor(config?: CreateConfig) { super(); @@ -407,9 +412,16 @@ export class FishjamClient { + this.localTrackStreamMap.set(event.trackId, event.track); this.emit('localTrackAdded', event); }); this.webrtc?.on('localTrackRemoved', (event) => { + const track = this.localTrackStreamMap.get(event.trackId); + if (track) { + this.localCameraStream.removeTrack(track); + this.localScreenShareStream.removeTrack(track); + this.localTrackStreamMap.delete(event.trackId); + } this.emit('localTrackRemoved', event); }); this.webrtc?.on('localTrackReplaced', (event) => { @@ -571,7 +583,27 @@ export class FishjamClient { - stream?.removeTrack(track); - }); + if (oldTrack) { + stream?.removeTrack(oldTrack); + } if (newTrack) { stream?.addTrack(newTrack); diff --git a/packages/webrtc-client/src/webRTCEndpoint.ts b/packages/webrtc-client/src/webRTCEndpoint.ts index b2e9aeb87..b25d5dad1 100644 --- a/packages/webrtc-client/src/webRTCEndpoint.ts +++ b/packages/webrtc-client/src/webRTCEndpoint.ts @@ -400,10 +400,11 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter { const resolutionNotifier = new Deferred(); const trackId = this.getTrackId(uuidv4()); - const stream = new MediaStream(); + const trackStream = stream ?? new MediaStream(); // TODO: Simulcast is disabled manually, enable it once bandwidth estimation is implemented or we add manual track selection support. const simulcastConfig: MediaEvent_Track_SimulcastConfig = { @@ -416,11 +417,18 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter 0 ? _maxBandwidth : 0; try { - stream.addTrack(track); + if (!stream) trackStream.addTrack(track); this.commandsQueue.pushCommand({ handler: async () => - this.localTrackManager.addTrackHandler(trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth), + this.localTrackManager.addTrackHandler( + trackId, + track, + trackStream, + trackMetadata, + simulcastConfig, + maxBandwidth, + ), parse: () => this.localTrackManager.parseAddTrack(track, simulcastConfig, maxBandwidth), resolve: 'after-renegotiation', resolutionNotifier, @@ -433,7 +441,7 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter Date: Mon, 23 Mar 2026 17:39:15 +0100 Subject: [PATCH 2/4] Lint --- packages/ts-client/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ts-client/src/index.ts b/packages/ts-client/src/index.ts index 14ea4a243..d7f8cb0f1 100644 --- a/packages/ts-client/src/index.ts +++ b/packages/ts-client/src/index.ts @@ -1,7 +1,6 @@ export { AUTH_ERROR_REASONS, type AuthErrorReason, isAuthError } from './auth'; export { TrackTypeError } from './errors'; export { FishjamClient } from './FishjamClient'; -export { LocalStream } from './LocalStream'; export { isJoinError, JOIN_ERRORS, type JoinErrorReason } from './guards'; export { type LivestreamCallbacks, @@ -11,6 +10,7 @@ export { receiveLivestream, type ReceiveLivestreamResult, } from './livestream'; +export { LocalStream } from './LocalStream'; export type { ReconnectConfig, ReconnectionStatus } from './reconnection'; export type { ClientType, From 69ad9111e9a2d2b11a64bdbf5fda1ddd518df1c5 Mon Sep 17 00:00:00 2001 From: Tomasz Mazur <47872060+AHGIJMKLKKZNPJKQR@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:44:15 +0100 Subject: [PATCH 3/4] Don't export internal class --- packages/ts-client/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ts-client/src/index.ts b/packages/ts-client/src/index.ts index d7f8cb0f1..8a3d053b5 100644 --- a/packages/ts-client/src/index.ts +++ b/packages/ts-client/src/index.ts @@ -10,7 +10,6 @@ export { receiveLivestream, type ReceiveLivestreamResult, } from './livestream'; -export { LocalStream } from './LocalStream'; export type { ReconnectConfig, ReconnectionStatus } from './reconnection'; export type { ClientType, From 910410e6de2fa1f591ff2ff1310708eca72f90bd Mon Sep 17 00:00:00 2001 From: Tomasz Mazur <47872060+AHGIJMKLKKZNPJKQR@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:40:05 +0100 Subject: [PATCH 4/4] Simplify logic and handle replaceTrack --- packages/ts-client/src/FishjamClient.ts | 45 ++++++++-------- packages/ts-client/src/LocalStream.ts | 69 ------------------------- 2 files changed, 21 insertions(+), 93 deletions(-) delete mode 100644 packages/ts-client/src/LocalStream.ts diff --git a/packages/ts-client/src/FishjamClient.ts b/packages/ts-client/src/FishjamClient.ts index 28326903d..f78cea3c0 100644 --- a/packages/ts-client/src/FishjamClient.ts +++ b/packages/ts-client/src/FishjamClient.ts @@ -26,7 +26,6 @@ import { isAuthError } from './auth'; import { connectEventsHandler } from './connectEventsHandler'; import { TrackTypeError } from './errors'; import { isComponent, isJoinError, isPeer } from './guards'; -import { LocalStream } from './LocalStream'; import { MessageQueue } from './messageQueue'; import { ReconnectManager } from './reconnection'; import type { @@ -100,9 +99,9 @@ export class FishjamClient = new Map(); + private cameraStream = new MediaStream(); + private screenShareStream = new MediaStream(); + private trackIdToTrack: Map = new Map(); constructor(config?: CreateConfig) { super(); @@ -412,19 +411,24 @@ export class FishjamClient { - this.localTrackStreamMap.set(event.trackId, event.track); + this.trackIdToTrack.set(event.trackId, [event.track, event.stream]); this.emit('localTrackAdded', event); }); this.webrtc?.on('localTrackRemoved', (event) => { - const track = this.localTrackStreamMap.get(event.trackId); - if (track) { - this.localCameraStream.removeTrack(track); - this.localScreenShareStream.removeTrack(track); - this.localTrackStreamMap.delete(event.trackId); - } + const [track, stream] = this.trackIdToTrack.get(event.trackId)!; + + if (track) stream.removeTrack(track); + + this.trackIdToTrack.delete(event.trackId); this.emit('localTrackRemoved', event); }); this.webrtc?.on('localTrackReplaced', (event) => { + const [oldTrack, stream] = this.trackIdToTrack.get(event.trackId)!; + + if (oldTrack) stream.removeTrack(oldTrack); + if (event.track) stream.addTrack(event.track); + + this.trackIdToTrack.set(event.trackId, [event.track, stream]); this.emit('localTrackReplaced', event); }); this.webrtc?.on('localTrackBandwidthSet', (event) => { @@ -587,22 +591,15 @@ export class FishjamClient