Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/big-ghosts-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@livekit/rtc-node": patch
---

feat: handle track republish
2 changes: 1 addition & 1 deletion packages/livekit-rtc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@datastructures-js/deque": "1.0.8",
"@livekit/mutex": "^1.0.0",
"@livekit/typed-emitter": "^3.0.0",
"@livekit/rtc-ffi-bindings": "0.12.54",
"@livekit/rtc-ffi-bindings": "0.12.56",
"pino": "^9.0.0",
"pino-pretty": "^13.0.0"
},
Expand Down
32 changes: 31 additions & 1 deletion packages/livekit-rtc/src/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { Mutex } from '@livekit/mutex';
import { EncryptionState, type EncryptionType } from '@livekit/rtc-ffi-bindings';
import type { FfiEvent } from '@livekit/rtc-ffi-bindings';
import { DisconnectReason, type OwnedParticipant } from '@livekit/rtc-ffi-bindings';
import type { DataStream_Trailer, DisconnectCallback } from '@livekit/rtc-ffi-bindings';
import type {
DataStream_Trailer,
DisconnectCallback,
TrackPublicationInfo,
} from '@livekit/rtc-ffi-bindings';
import {
type ConnectCallback,
ConnectRequest,
Expand Down Expand Up @@ -514,6 +518,19 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
const publication = this.localParticipant.trackPublications.get(ev.value.publicationSid!);
this.localParticipant.trackPublications.delete(ev.value.publicationSid!);
this.emit(RoomEvent.LocalTrackUnpublished, publication!, this.localParticipant!);
} else if ((ev.case as string) == 'localTrackRepublished') {
const value = (ev as any).value;
const previousSid: string = value.previousSid!;
const newInfo: TrackPublicationInfo = value.info!;
const publication = this.localParticipant.trackPublications.get(previousSid);
if (publication) {
publication.updateInfo(newInfo);
this.localParticipant.trackPublications.delete(previousSid);
this.localParticipant.trackPublications.set(publication.sid!, publication);
this.emit(RoomEvent.LocalTrackRepublished, publication, previousSid, this.localParticipant);
} else {
log.warn(`RoomEvent.LocalTrackRepublished: previous publication not found: ${previousSid}`);
}
} else if (ev.case == 'localTrackSubscribed') {
const publication = this.localParticipant.trackPublications.get(ev.value.trackSid!);
if (publication) {
Expand Down Expand Up @@ -936,6 +953,18 @@ export type RoomCallbacks = {
publication: LocalTrackPublication,
participant: LocalParticipant,
) => void;
/**
* Fired when the SDK auto-republished a local track during a full
* reconnect. The publication object's identity is preserved (the same
* instance is updated in place with the new server-assigned SIDs);
* `previousSid` is provided for callers that key external state on the
* old SID and need to reconcile.
*/
localTrackRepublished: (
publication: LocalTrackPublication,
previousSid: string,
participant: LocalParticipant,
) => void;
localTrackSubscribed: (track: LocalTrack) => void;
trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;
trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;
Expand Down Expand Up @@ -993,6 +1022,7 @@ export enum RoomEvent {
ParticipantDisconnected = 'participantDisconnected',
LocalTrackPublished = 'localTrackPublished',
LocalTrackUnpublished = 'localTrackUnpublished',
LocalTrackRepublished = 'localTrackRepublished',
LocalTrackSubscribed = 'localTrackSubscribed',
TrackPublished = 'trackPublished',
TrackUnpublished = 'trackUnpublished',
Expand Down
6 changes: 5 additions & 1 deletion packages/livekit-rtc/src/tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,11 @@ describeE2E('livekit-rtc e2e', () => {
off += s.length;
}
const detected = estimateFreqHz(concat, pubRateHz);
expect(Math.abs(detected - sineHz)).toBeLessThan(20);
// Wider tolerance than the clean-path sine test: post-reconnect
// audio has brief discontinuities, and the autocorrelation is
// integer-lag (next neighbors to 60Hz are exactly 80Hz/40Hz), so
// ±20Hz lands right on the failure boundary under CI load.
expect(Math.abs(detected - sineHz)).toBeLessThan(25);

return { rooms, subRoom: subRoom!, pubRoom: pubRoom! };
} finally {
Expand Down
12 changes: 12 additions & 0 deletions packages/livekit-rtc/src/track_publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ export abstract class TrackPublication {
get encryptionType(): EncryptionType | undefined {
return this.info?.encryptionType;
}

/**
* Update the publication's info in place. Used by the SDK when the
* server re-issues IDs / metadata for an existing publication (e.g.
* after a full reconnect). Application code holding a cached
* publication reference continues to read fresh values via the
* unchanged object identity.
* @internal
*/
updateInfo(info: TrackPublicationInfo): void {
this.info = info;
}
}

export class LocalTrackPublication extends TrackPublication {
Expand Down
50 changes: 25 additions & 25 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading