Skip to content

Data tracks: core - #990

Merged
ladvoc merged 18 commits into
ladvoc/data-track-channelfrom
ladvoc/data-track-core
Oct 21, 2026
Merged

ladvoc merged 18 commits into
ladvoc/data-track-channelfrom
ladvoc/data-track-core

Conversation

@ladvoc

@ladvoc ladvoc commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Adds core types (e.g., info, frame, schema, etc.) and packetizer/depacketizer.

Closes BOT-541

@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from f15a229 to b7b6532 Compare September 4, 2026 16:04
@ladvoc
ladvoc marked this pull request as ready for review September 4, 2026 16:07
@ladvoc
ladvoc requested a review from a team as a code owner September 4, 2026 16:07
@ladvoc
ladvoc requested a review from boks1971 September 4, 2026 16:07
devin-ai-integration[bot]

This comment was marked as resolved.

@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from b7b6532 to 1c85018 Compare September 4, 2026 22:58
devin-ai-integration[bot]

This comment was marked as resolved.

@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from 5572d61 to dc69ba0 Compare September 8, 2026 21:55
Comment thread datatrack/clock.go Outdated
Comment thread datatrack/clock.go
Comment thread datatrack/depacketizer.go
func (d *depacketizer) frameFromSingle(packet dtp.Packet, options depacketizerPushOptions) depacketizerPushResult {
var result depacketizerPushResult
if len(d.partials) >= options.maxPartialFrames {
result.drop = d.evictOldest(packet.FrameNumber)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The single frame has not been appended to the partials, do we still need to evict one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A Single arriving while the partials map is at capacity is treated as a signal that the oldest in-flight partial is stale: the publisher has moved on to a later frame, and on an unordered channel with no retransmits its missing packets aren't coming. It doesn't change what gets dropped (the next Start would evict it anyway), only that the drop is reported promptly and attributed to the frame that actually superseded it. This matches the Rust implementation.

Comment thread datatrack/depacketizer.go
Comment thread datatrack/handle.go Outdated
@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch 2 times, most recently from c9fa55a to ae3dc6f Compare September 14, 2026 21:28
@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from c2ca3f6 to 5692ffc Compare September 14, 2026 21:40
@ladvoc
ladvoc requested a review from cnderrauber September 14, 2026 21:40

@cnderrauber cnderrauber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from 5692ffc to 5810844 Compare September 22, 2026 16:46
@ladvoc ladvoc changed the title Data tracks core Data tracks: core Sep 22, 2026

@boks1971 boks1971 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left a bunch of small comment. Good stuff @ladvoc !

Would be good to act on the extension ID collision ASAP. I will look into moving the internal one to 255 and accept both on read side for a deploy and then we can replace ID at 1 with e2ee extension id.

Comment thread datatrack/clock.go
nanos = 0
}
seconds, remainder := uint64(nanos)/1_000_000_000, uint64(nanos)%1_000_000_000
return uint32(seconds*clockRate + (remainder*clockRate+500_000_000)/1_000_000_000)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can potentially make this simpler.

d.Seconds()

returns float64. So, you can just multiply that by clockRate and cast back to uint32 I think. Something like

return uint32(float64(clockRate) * d.Seconds())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

May need bounds check though of < 0 or > something which will actually overflow uint32. I don't think there is great handling for greater than uint32 other than just let it wrap. So, maybe something like

return uint32(max(0, float64(clockRate) * d.Seconds()))

@ladvoc ladvoc Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Went back and forth on this with an agent, and it pointed me to this:

In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent.

Which I think means the cast to uint32 may wrap on some arches and saturate on others if we use float64. For reference, this is the same calculation in the Rust implementation; the main difference there is Duration is guaranteed to not be negative.

Comment thread datatrack/handle.go
return 0, errHandlesExhausted
}
a.value++
return trackHandle(a.value), nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

guessing this might be locked from outside. If not, should protect this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, handle allocator is owned by LocalManager (defined in #991) which locks this.

Comment thread datatrack/info.go Outdated
var ErrInvalidSID = errors.New("invalid data track SID")

// SID is the server-assigned identifier of a data track.
type SID string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you want a type, can we use livekit.TrackID? I think that is what server uses for all TrackID (both media and data)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 5bf5e11.

Comment thread datatrack/depacketizer.go Outdated
type depacketizerDropReason int

const (
dropReasonInterrupted depacketizerDropReason = iota

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I usually do the enum prefix fully depacketizerDropReason... so that collisions are avoided just in case and also makes me think less :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in d75e577.

Comment thread datatrack/depacketizer.go
}

func (d *depacketizer) push(packet dtp.Packet, options depacketizerPushOptions) depacketizerPushResult {
switch markerOf(&packet.Header) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can this method be pushed to protocol? Yeah, server does not need that, but it may at some point if it has to do sub-sampling. Also feels cleaner to have all packet utilities there.

When server needs to depacketize, this whole thing could move to protocol too, but that is later. Would be good to have all packet utilities in protocol for now.

@ladvoc ladvoc Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, this is something that will likely be shared in the future. I'm assuming you're talking about the markerOf helper in particular?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, meant markerOf. Basically, anything to do with packet. Can be unit tested there.

Comment thread datatrack/depacketizer_test.go Outdated
"github.com/stretchr/testify/require"
)

var defaultPushOptions = depacketizerPushOptions{maxPartialFrames: 1}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommend renaming this to something like testDefaultPushOptions. defaultPushOptions seems like it is in the implementation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 9f576d9.

Comment thread datatrack/packet.go Outdated
supportedVersion = 0

extensionIDE2EE uint8 = 1
extensionIDUserTimestamp uint8 = 2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have to fix this for the relay stuff. Maybe, we allocate 3 (or maybe the highest number for relay and in one version, the read side has to check for both)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And let us move these to proto definitions.

@ladvoc ladvoc Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I moved most of this to livekit/protocol in livekit/protocol#1834 (except client specific parts) and integrated in 0e0a11b. I didn't add the extension IDs to the Protobuf enum over there yet since that would require re-naming DTEI_PARTICIPANT_SID_LOW—although maybe it is time to do that now. Let me know your thoughts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @ladvoc . Let's wait on adding the extension IDs to the proto definition for a little bit more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is now defined in protocol - livekit/protocol#1861

Comment thread datatrack/packet.go Outdated
Comment thread datatrack/packetizer.go
return nil, errMTUTooShort
}
header.FrameNumber = p.frameNumber
p.frameNumber++

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

guessing there is locking outside

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, in #991, each local track owns a packetizer which it locks.

Comment thread datatrack/proto.go
msg.FrameEncoding = frameEncodingToProto(info.FrameEncoding)
}
return msg
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Till this point, I think it can go into protocol. Also, feels like there may be some protobuf library to do some of this. I am not sure though.

@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from 5810844 to 0caac7c Compare October 5, 2026 22:15
@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from 0e0a11b to 3436ef7 Compare October 19, 2026 19:13
@ladvoc
ladvoc force-pushed the ladvoc/data-track-core branch from 3436ef7 to 9283287 Compare October 19, 2026 20:05
@ladvoc
ladvoc merged commit cd9f9aa into main Oct 21, 2026
9 checks passed
@ladvoc
ladvoc deleted the ladvoc/data-track-core branch October 21, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants