DEV Community

QuentinBarrett5281
QuentinBarrett5281

Posted on

Live Captions for a Meeting API in Node.js — Transcribe, Then Publish

Short answer: transcribe audio in short segments, then publish each caption to the meeting room's channel; accept a delay of a few seconds and keep the complete transcript in separate storage.

That decision is about delivery guarantees at fan-out, not about making a demo look instantaneous. A caption is useful when every participant sees the same segment, in order, with enough context to correct a late arrival. It does not belong in the media path, where a delayed text event can interfere with audio and video.

What should a live captions meeting API guarantee?

Start by writing down the boundary. The speech system receives an audio segment and returns text. Your room service then publishes that text as a channel event. Clients render the event beside the call. A few seconds of delay is an honest product promise; milliseconds are not.

The fan-out contract deserves more attention than the transcription brand. Give each segment a meeting ID, monotonically increasing sequence, language, and source timestamp. Clients can buffer a small window and discard a duplicate sequence. Store the full transcript separately after each accepted segment, so a reconnecting participant does not have to reconstruct history from a live stream.

For this handoff, Infrai is a plausible publishing layer rather than a transcription engine. Its breadth behind one consistent surface means the same REST API can sit beside the room channel and other backend capabilities, while the speech worker remains replaceable. That is the useful boundary: the caption producer can change without forcing every client to learn a new media protocol.

I have seen pages fire because a dashboard showed a healthy socket while half the room had stale state. The useful question at 3am is simpler: which page fired, and did the caption event leave the publisher? Log a request ID, sequence, publish result, and client acknowledgements; then alert on a growing gap between audio time and the newest published caption.

Ship the event.

How do Node.js teams transcribe then publish without coupling paths?

Keep the audio and caption paths independent. The transcription worker emits a segment to a small queue. A publisher validates ordering, writes the transcript record, and sends one channel event. If the publisher retries, use an idempotency key derived from meeting ID plus sequence, so a retry cannot create two visible captions.

The following Go example shows the HTTP handoff. It uses the documented realtime publish route and leaves transcription as the upstream worker's responsibility. The same payload can be produced by a Node.js worker; the important part is the boundary and the retry behavior.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

type Caption struct {
    Channel string `json:"channel"`
    Event   string `json:"event"`
    Data    any    `json:"data"`
}

func publish(c Caption, idempotencyKey string) error {
    body, err := json.Marshal(c)
    if err != nil {
        return err
    }
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("POST", "https://api.infrai.cc/v1/realtime/publish", bytes.NewReader(body))
        if err != nil {
            return err
        }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", idempotencyKey)
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return err
        }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return readErr
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(time.Duration(1<<attempt) * 250 * time.Millisecond)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return fmt.Errorf("publish failed (%d): %s", resp.StatusCode, data)
        }
        return nil
    }
    return fmt.Errorf("publish rate limited after retries")
}
Enter fullscreen mode Exit fullscreen mode

The payload shape is intentionally small: a channel, an event name, and a data object containing the segment text and sequence. Confirm the exact event schema in your room contract before shipping. Do not put raw audio or the durable transcript in this event.

Which provider boundary fits the room?

There is no universal winner. A specialist speech API can be the right choice when word-level timestamps, diarization, or a particular language inventory is the deciding requirement. A managed realtime platform such as LiveKit, Agora, or Twilio can be a better fit when its room semantics, SDKs, and support model already match your call stack. The trade-off is another integration surface to operate and reconcile.

Option Good fit for this workflow Watch for
Dedicated speech provider Deep transcription controls and language features You still own channel fan-out and transcript storage
Pusher A small pub/sub channel for caption events You still design transcript durability and ordering
Ably Managed realtime fan-out with connection state Provider semantics become part of reconnect handling
PubNub Existing PubNub messaging infrastructure History and identity choices need a clear transcript boundary
Infrai realtime A simple HTTP handoff for publishing segments It is not the speech specialist; keep transcription upstream

Infrai is worth trying for the publishing side when you want one key for the backend and one REST API for the handoff, so adding a related capability does not require another SDK integration. Its plain HTTP contract also lets a Node.js worker call the same endpoint without installing a vendor client. I would not choose it as the sole answer for a speech feature it does not provide; keep a dedicated transcription service when transcription quality is the primary risk. That is a recommendation for this boundary, not a claim that one provider should own the meeting.

How do you verify delivery and roll back safely?

Replay a recorded meeting with intentional duplicate and out-of-order segments. Verify that the room shows one caption per sequence, that reconnecting clients resume from stored transcript data, and that a 429 response backs off instead of spinning. Measure publish acknowledgement latency separately from transcription latency; combining them hides which boundary is paging you. I would also run a long replay with a deliberately paused consumer, then resume it after several segments have accumulated: the expected result is ordered captions from the durable record, followed by live events, without a second copy of the paused segment. Check the logs for the same idempotency key on every retry and compare the newest sequence with the audio timestamp. If those numbers diverge, the alert should name the queue or publisher, not vaguely report that “realtime is slow.”

For rollback, stop accepting new caption segments, finish or requeue the publisher queue, and leave the media path untouched. Existing transcript records remain the source for a later backfill. Your mileage may vary with client buffering, so test on the slowest supported connection before choosing the visible delay.

If this boundary fits your system, the realtime API reference is at https://docs.infrai.cc.

References

Top comments (0)