-
Notifications
You must be signed in to change notification settings - Fork 295
Initial implementation for transcription deduplication #2093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
09cdf6d
31ebd40
64e6dab
ca3086d
1370d3b
3aa7f21
9cf732d
7cd37cd
aa0376c
907c3cd
33b26ac
52880c5
34c8bae
20c6792
61dc935
f94fffd
e916440
b276419
0f486f9
056c30a
02d050b
390f220
4a0f1dc
7c4b4ea
fd72da8
0a9b7d1
9b6a694
9443444
f8e5a1f
d35d954
f380c12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| --- | ||
| 'livekit-client': minor | ||
| --- | ||
|
|
||
| Rebuild transcription events from `lk.transcription` data streams and advertise client protocol 3 | ||
|
|
||
| `RoomEvent.TranscriptionReceived` (and the matching `ParticipantEvent` / `TrackEvent`) are now | ||
| sourced from the `lk.transcription` text stream channel rather than legacy `Transcription` data | ||
| packets, which are ignored from this release on. The event signature is unchanged. | ||
|
|
||
| **This is a behavior change that takes effect immediately, not once agents adopt protocol 3.** | ||
| Agents currently publish both channels; this release reads the stream channel and drops the legacy | ||
| one. Advertising client protocol 3 additionally lets agents stop publishing the legacy copy | ||
| altogether, roughly halving reliable-channel traffic for transcriptions — a significant improvement | ||
| on constrained uplinks. | ||
|
|
||
| Applications reading `lk.transcription` directly via `registerTextStreamHandler` are unaffected: the | ||
| SDK observes the topic internally without taking it over. Any non-agent publisher of legacy | ||
| `Transcription` packets (a bespoke service calling `publish_transcription`, or a pre-1.0 agents | ||
| framework) no longer surfaces. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,6 +83,7 @@ import LocalParticipant from './participant/LocalParticipant'; | |
| import Participant from './participant/Participant'; | ||
| import { type ConnectionQuality, ParticipantKind } from './participant/Participant'; | ||
| import RemoteParticipant from './participant/RemoteParticipant'; | ||
| import { ParticipantAgentAttributes } from './participant/attributes'; | ||
| import { | ||
| RPC_REQUEST_DATA_STREAM_TOPIC, | ||
| RPC_RESPONSE_DATA_STREAM_TOPIC, | ||
|
|
@@ -104,6 +105,7 @@ import type { TrackPublication } from './track/TrackPublication'; | |
| import type { TrackProcessor } from './track/processor/types'; | ||
| import type { AdaptiveStreamSettings } from './track/types'; | ||
| import { getNewAudioContext, kindToSource, sourceToKind } from './track/utils'; | ||
| import TranscriptionStreamConverter from './transcription/TranscriptionStreamConverter'; | ||
| import { | ||
| type ChatMessage, | ||
| type SimulationOptions, | ||
|
|
@@ -234,6 +236,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
|
|
||
| private incomingDataStreamManager: IncomingDataStreamManager; | ||
|
|
||
| private transcriptionStreamConverter: TranscriptionStreamConverter; | ||
|
|
||
| private outgoingDataStreamManager: OutgoingDataStreamManager; | ||
|
|
||
| private incomingDataTrackManager: IncomingDataTrackManager; | ||
|
|
@@ -283,6 +287,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
| this.incomingDataStreamManager = new IncomingDataStreamManager( | ||
| this.options.dataStream?.maxPayloadByteLength, | ||
| ); | ||
| this.transcriptionStreamConverter = new TranscriptionStreamConverter({ | ||
| onTranscription: (transcription) => this.handleTranscription(transcription), | ||
| getMicrophoneTrackSid: this.getMicrophoneTrackSid, | ||
| getDelegatingPublisherIdentity: this.getDelegatingPublisherIdentity, | ||
| }); | ||
| this.incomingDataStreamManager.on( | ||
| 'transcriptionStreamArrived', | ||
| ({ reader, participantIdentity }) => { | ||
| this.transcriptionStreamConverter.handleTextStream(reader, participantIdentity); | ||
| }, | ||
| ); | ||
|
Comment on lines
+290
to
+300
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to reviewers - here is where everything is hooked together:
|
||
| this.outgoingDataStreamManager = new OutgoingDataStreamManager( | ||
| this.engine, | ||
| this.log, | ||
|
|
@@ -1833,6 +1848,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
| this.isResuming = false; | ||
| this.bufferedEvents = []; | ||
| this.transcriptionReceivedTimes.clear(); | ||
| this.transcriptionStreamConverter.reset(); | ||
| this.incomingDataStreamManager.clearControllers(); | ||
| this.incomingDataTrackManager.reset(); | ||
| this.outgoingDataTrackManager.reset(); | ||
|
|
@@ -2099,7 +2115,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
| if (packet.value.case === 'user') { | ||
| this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType); | ||
| } else if (packet.value.case === 'transcription') { | ||
| this.handleTranscription(participant, packet.value.value); | ||
| // Legacy `Transcription` packets are ignored: transcription events are rebuilt from the | ||
| // `lk.transcription` data stream channel instead, which this client advertises support for | ||
| // via client protocol 3. See | ||
| // docs/superpowers/specs/2026-09-04-transcription-back-conversion-design.md | ||
| this.log.debug('ignoring legacy transcription data packet', this.logContext); | ||
|
Comment on lines
+2118
to
+2122
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self - drop |
||
| } else if (packet.value.case === 'sipDtmf') { | ||
| this.handleSipDtmf(participant, packet.value.value); | ||
| } else if (packet.value.case === 'chatMessage') { | ||
|
|
@@ -2173,10 +2193,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
| participant?.emit(ParticipantEvent.SipDTMFReceived, dtmf); | ||
| }; | ||
|
|
||
| private handleTranscription = ( | ||
| _remoteParticipant: RemoteParticipant | undefined, | ||
| transcription: TranscriptionModel, | ||
| ) => { | ||
| private handleTranscription = (transcription: TranscriptionModel) => { | ||
| // find the participant | ||
| const participant = | ||
| transcription.transcribedParticipantIdentity === this.localParticipant.identity | ||
|
|
@@ -2603,6 +2620,28 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) | |
| ); | ||
| } | ||
|
|
||
| private getMicrophoneTrackSid = (identity: Participant['identity']): string | undefined => { | ||
| const participant = this.getParticipantByIdentity(identity); | ||
| for (const publication of participant?.trackPublications.values() ?? []) { | ||
| if (publication.source === Track.Source.Microphone) { | ||
| return publication.trackSid; | ||
| } | ||
| } | ||
| return undefined; | ||
| }; | ||
|
|
||
| private getDelegatingPublisherIdentity = ( | ||
| identity: Participant['identity'], | ||
| ): string | undefined => { | ||
| // An avatar worker carries `lk.publish_on_behalf` naming the agent it speaks for. | ||
| for (const participant of this.remoteParticipants.values()) { | ||
| if (participant.attributes[ParticipantAgentAttributes.PublishOnBehalf] === identity) { | ||
| return participant.identity; | ||
| } | ||
| } | ||
| return undefined; | ||
| }; | ||
|
|
||
| private setStatsLogging(enabled: boolean) { | ||
| if (enabled) { | ||
| if (!this.statsLogInterval) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.