Repository navigation
feat: Common audio recorder for integrations - #2593
David Elner (delner) wants to merge 1 commit into
Conversation
7db9062 to
0b57244
Compare
| export function createRecording(config: RecordingConfig) { | ||
| const recorder: Recorder = new Recorder( |
There was a problem hiding this comment.
the public api here is kinda weird. Why isn't this all just wrapped up in one class that gets exposed via createRecording?
| !Number.isFinite(packet.at) || | ||
| packet.at < 0 || | ||
| ![0, 1].includes(packet.channel) || | ||
| ![1, 2].includes(packet.channels) || | ||
| !Number.isFinite(packet.rate) || | ||
| packet.rate < 8000 || | ||
| packet.rate > 96000 || | ||
| packet.pcm.length % packet.channels |
There was a problem hiding this comment.
this class, and logic like this, is very hard to read and understand IMO.
| state: snapshot.closed ? "omitted" : "pending", | ||
| ...(snapshot.closed ? { reason: "no_audio_observed" } : {}), | ||
| }); | ||
| const signature = JSON.stringify(descriptors); |
There was a problem hiding this comment.
do we really have to serialize here? I don't really understand the manifest concept overall.
| if (this.retained + bytes > this.maximum) | ||
| throw new Error("capture_byte_limit"); |
There was a problem hiding this comment.
we should fix this with linting/prettier, but I prefer if we used curly braces for conditionals
| if (this.retained + bytes > this.maximum) | |
| throw new Error("capture_byte_limit"); | |
| if (this.retained + bytes > this.maximum) { | |
| throw new Error("capture_byte_limit"); | |
| } |
| @@ -0,0 +1,19 @@ | |||
| /** Process-wide source PCM bound shared by framework adapters. Reservations follow ownership. */ | |||
| class ByteBudget { | |||
There was a problem hiding this comment.
why do we have a byte budget here? I think attachments tracks budget under the hood anyway, that should be enough.
There was a problem hiding this comment.
My original intent was to bound buffers so we didn't overflow memory on long calls. I agree its not necessary for the attachment once the audio is encoded and ready to upload. I'll examine this more carefully.
|
|
||
| /** A sealed segment. Capture remains with Recorder until the cut is safe. | ||
| * Payloads have one owner at a time; the manifest survives their transfer. */ | ||
| export class AudioSegment implements Segment { |
There was a problem hiding this comment.
this needs to be a state machine. so we need a singular entry point around state transitions.
Like
`segment.dispatch({ type: "ready", ...options });0b57244 to
3bd747e
Compare
3bd747e to
e2401c8
Compare
Adds
@braintrust/audio, an optional recording library for voice integrations.It has no runtime dependency on
braintrustor a voice framework.Setting up
Integrations use
createRecording()to supply timestamped PCM, span associations, and an attachment uploader. Installing the package does not enable recording; capture is controlled by the integration.audioFormat"ogg"options.segmentDurationSeconds60options.maxDurationSeconds1800options.maxBufferBytes33554432options.flushFraction0.5turnGrouping.maxPauseMs1500Design
Node 20+. Ogg uses bundled WebAssembly; no FFmpeg or native compilation. Audio dependencies stay outside the default
braintrustinstall.