Skip to content

Data track integration#1820

Open
1egoman wants to merge 78 commits intomainfrom
data-track-integration
Open

Data track integration#1820
1egoman wants to merge 78 commits intomainfrom
data-track-integration

Conversation

@1egoman
Copy link
Contributor

@1egoman 1egoman commented Feb 20, 2026

Integrates the data track managers (both incoming and outgoing) into the existing sdk Room and RemoteParticipant interfaces. Once this is merged, data tracks is fully implemented!

Interface - Publishing

const localDataTrack = await room.publishDataTrack({name: "data track name"});
await localDataTrack.tryPush(new Uint8Array(/* ... */));

// Once you are done with the local data track, it can be unpublished:
await localDataTrack.unpublish();

Interface - Subscribing

// There are two ways to get a remote data track, either a room event:
room.on(RoomEvent.RemoteDataTrackPublished, async (remoteDataTrack) => {
  // ...
});

// Or a new field on `RemoteParticipant`:
const remoteDataTrack = remoteParticipant.dataTracks.get("track name"); // Get a data track which has been published
const remoteDataTrack = await remoteParticipant.dataTracks.getDeferred("track name"); // Get a data track which _will_ be published shortly

// Either way, once you have a remoteDataTrack, you can subscribe:
const stream = await remoteDataTrack.subscribe(/* optional abort signal */);
for await (const frame of stream) {
  console.log(`Received bytes from ${remoteDataTrack.info.name}:`, frame.payload);
}

Todo:

@changeset-bot
Copy link

changeset-bot bot commented Feb 20, 2026

⚠️ No Changeset found

Latest commit: e5dc9ae

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 94.71 KB (+9.17% 🔺)
dist/livekit-client.umd.js 103.49 KB (+6.34% 🔺)

@1egoman 1egoman force-pushed the data-track-integration branch 2 times, most recently from 2f31fbc to 6a5960d Compare February 25, 2026 21:48
@1egoman 1egoman force-pushed the data-track-integration branch from 6a5960d to 2e6f8cf Compare March 5, 2026 20:51
@1egoman 1egoman changed the title (WIP) Data track integration Data track integration Mar 5, 2026
@ladvoc ladvoc self-requested a review March 6, 2026 22:09
/**
* Emits when a new data track has been published by a downstream participant.
*/
RemoteDataTrackPublished = 'remoteDataTrackPublished',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RemoteDataTrackPublished = 'remoteDataTrackPublished',
DataTrackPublished = 'dataTrackPublished',

conforms with existing TrackPublished and TrackUnpublished APIs which are remote if not explicitly prefixed with Local

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we would also proxy the events as a ParticipantEvent and emit it directly on the participant.
If we want it to feel a bit closer to how existing APIs work, that might be a good idea here, too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conforms with existing TrackPublished and TrackUnpublished APIs which are remote if not explicitly prefixed with Local

Ah, good point - updated.

usually we would also proxy the events as a ParticipantEvent and emit it directly on the participant.
If we want it to feel a bit closer to how existing APIs work, that might be a good idea here, too

Looking at the current participant events, it looks like they are segmented by locality rather than type. ie:

  trackPublished: (publication: RemoteTrackPublication) => void;
  // ...
  trackUnpublished: (publication: RemoteTrackPublication) => void;
  // ...
  localTrackPublished: (publication: LocalTrackPublication) => void;
  localTrackUnpublished: (publication: LocalTrackPublication) => void;

Because of this, I'm sort of of the opinion to hold off on proxying these events to the participant until the larger track changes get rolled out across everything, and then at that point we can get these all going through the same interfaces rather than tacking on some additional data track specific events to this list. Any concerns with that approach?


/** Publish the track to the SFU. This must be done before calling {@link tryPush} for the first time. */
async publish(signal?: AbortSignal) {
try {
Copy link
Contributor

@lukasIO lukasIO Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this should error early (or warn and return) if isPublished is true?

Copy link
Contributor Author

@1egoman 1egoman Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already an error that gets thrown within publishRequest if the track is already published, but I could probably make the error message a little nicer (right now it is one of those "panic" bare errors).

IMO in this case, I think an explicit throw makes sense because you really should not be publishing / unpublishing data tracks without some care. But looking at the existing tracks it looks like it just warns and that's it. So maybe it makes sense to convert this throw into a warning, not sure.

EDIT: more thoughts on this here!

* */
async unpublish() {
if (!this.handle) {
throw DataTrackPushFrameError.trackUnpublished();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: might be enough to log a warning here and return early?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I am not sure - see my previous comment about the throw vs warning here. I like the hard error in that it is something that can be caught and explicitly handled though since this is checking an invariant. The existing track publication logic for audio / video tracks logs a warning so maybe it makes sense to update it to be consistent.

I guess one downside to the hard error is it opens this code up to a TOC-TOU issue where something like the below could happen:

if (track.isPublished) {
  // Imagine here the track is unpublished at the SFU level for some unrelated reason ...
  await track.unpublish(); // ... so this would then throw an error
}

I am definitely curious what others think here!

@1egoman 1egoman force-pushed the data-track-integration branch from 2619816 to 8e7a138 Compare March 16, 2026 17:40
1egoman added a commit that referenced this pull request Mar 16, 2026
@1egoman 1egoman marked this pull request as ready for review March 16, 2026 18:38
1egoman added 12 commits March 18, 2026 09:20
…urn DataTrackSubscriptionReader instead of ReadableStream"

This reverts commit ee2d5ed.
The api now looks like:
const readableStream = track.subscribe({ signal });
for await (const frame of readableStream) {
  // Do something with `frame`
}

Manual cancellation can be done with `signal` OR `readableStream` - they
should act identically.
…sts can determine sfu subscription completion
This is a pretty rare case but could occur in theory
error = DataTrackPublishError.limitReached(response.message);
break;
default:
this.log.error(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: If later new error reasons are added for data track operations, would we want to log an error on older client or introduce an unknown case?

Copy link
Contributor Author

@1egoman 1egoman Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I hadn't considered this. It looks like the rust implementation is raising an error in this case currently - a PublishError::Internal.

I will do something similar here (2d58fee) but in regards to the name: What do you think about ::Unknown for this case in both js / rust, or maybe something else that is specific for this exact case? Internal is being used as a catch all in a few other places so it isn't unique.

/**
* Emits when a new data track has been published by a downstream participant.
*/
DataTrackPublished = 'dataTrackPublished',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment: In Rust, this event is called RemoteDataTrackPublished.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a change lukas requested, more info here: #1820 (comment)

src/room/Room.ts Outdated
.on('sfuUpdateSubscription', (event) => {
this.engine.client.sendUpdateDataSubscription(event.sid, event.subscribe);
})
.on('trackAvailable', (event) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Would it make sense to rename the manager level events for consistency with the room level ones (e.g., published instead of available)? Will do this in Rust too if you end up changing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, renamed here: e5dc9ae

Copy link
Contributor

@ladvoc ladvoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ✅

… are active

The message from the SFU doesn't contain all participant identities,
only the ones which change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants