Member Junction
    Preparing search index...

    Abstract base class for browser-side, provider-direct realtime (voice) clients.

    This is the client-side mirror of the server's BaseRealtimeModel pattern: the server mints an ephemeral credential + session config (ClientRealtimeSessionConfig) via its provider driver, and the browser resolves the matching CLIENT driver through the MemberJunction ClassFactory using the config's Provider string as the registration key (e.g. 'openai'):

    const client = MJGlobal.Instance.ClassFactory.CreateInstance<BaseRealtimeClient>(
    BaseRealtimeClient, startResult.Provider);

    Concrete drivers (e.g. OpenAIRealtimeClient) own ALL provider wire concerns: transport (WebRTC/WebSocket), event-name translation, the response state machine (never letting a tool-result reply collide with an in-flight response), narration-kind tagging, and audible-playback tracking. Hosts own POLICY: when to narrate, what instructions to speak, transcript persistence, and UI state.

    All On* methods register a single handler (matching the server IRealtimeSession style); registering again replaces the previous handler.

    Hard-won, provider-independent rules every client driver MUST honor (the client-side mirror of the obligations documented on the server's BaseRealtimeModel in @memberjunction/ai). Most were paid for in live debugging; do not relearn them:

    1. Silent exit from 'speaking' after a tool call. When the model emits a tool call, the host typically shows its own busy ("thinking") indicator while the tool executes. The driver must leave the 'speaking' state silently (no state emission) at tool-call time so the turn's trailing frames don't clobber the host's indicator.
    2. Busy-flag release on tool-call emission (deadlock guard). A tool-call frame means the model has yielded the floor pending the result. Any internal "response active" flag MUST be cleared then — otherwise the eventual SendToolResult (or a queued send) deadlocks waiting for a turn boundary that will never arrive until after the result is sent.
    3. Playback flush + honest IsAudioPlaying on interruption. On a true barge-in the driver must flush any locally-owned audio playback and report IsAudioPlaying === false promptly — stale queued audio after an interruption is a product bug, not a nicety. The same flush applies to CancelActiveResponse.
    4. SendText must NOT echo a user transcript — and implies barge-in. The driver must not synthesize a user-role transcript event for injected text (the host owns the local echo and would render the message twice), and an active spoken response is cancelled via the driver's own cancel path before the text is injected (see CancelActiveResponse).
    5. Tool-result delivery invariant. Every result fed back via SendToolResult must EVENTUALLY be voiced/processed by the model and never dropped. If the provider rejects overlapping generation triggers, the driver queues the result's trigger behind the in-flight response and flushes it at the next turn boundary. The result must also EXPLICITLY trigger generation on providers that don't auto-continue after a tool response: OpenAI requires an explicit response.create; Gemini auto-continues UNLESS prior client content (a context note's turnComplete: false) left the turn open, in which case the driver must commit the turn or the model stays silent.
    6. Token/credential expiry surfaces as a FATAL error. When the ephemeral credential dies (expiry, revocation, unexpected socket loss), the driver must surface it through OnError with Fatal: true so the host tears down cleanly instead of idling forever on a dead connection.
    7. 'listening' only after the session config is applied. The driver must not report the session as listening until the server-built session config (system prompt
      • tools) has actually been applied to the provider socket — otherwise early turns run against an unconfigured model.
    8. SessionConfig is a private pact. The ClientRealtimeSessionConfig.SessionConfig payload is authored by the same-keyed SERVER driver and consumed only by this client driver; its shape may change between the two driver halves without notice. Hosts and intermediaries treat it as an opaque blob — and so must any code outside the matching driver pair.
    9. Audio metering is a CAPABILITY, not an obligation — but wire what you own. A driver that owns (or can tap) an audio plane should attach BaseRealtimeClient.attachInputAudioMeter / attachOutputAudioMeter so GetAudioActivity reports real levels: the mic stream everywhere (RealtimeAudioMeter.ForStream), the shared RealtimePcmPlayback.CreateMeter() on client-owned-audio drivers, the remote WebRTC stream on peer-connection drivers. Meters must be released on disconnect (closeAudioMeters). A driver with no tappable plane simply attaches nothing — hosts fall back to turn-state animation.

    Hierarchy (View Summary)

    Index

    Constructors

    Accessors

    • get IsAudioPlaying(): boolean

      true while model audio is AUDIBLY playing in the browser. Distinct from IsBusy — audio plays at realtime while generation finishes early, so the model can be "idle" while speech is still coming out of the speaker. Hosts must gate narration on BOTH, or queued utterances come out late and stale.

      Returns boolean

    • get IsBusy(): boolean

      true while a model response is in flight (generation started and not yet done). Distinct from IsAudioPlaying: generation runs ahead of playback. Hosts use this to gate interim narration so it never interrupts a reply.

      Returns boolean

    Methods

    • Cancels the model's ACTIVE spoken response and flushes pending playback so a new user turn can take the floor immediately; no-op when nothing is active. MUST NOT affect server-side delegated work — cancelling speech is a floor-control action, not an abort of in-flight tools/agents (hosts abort delegated work from OnInterruption / their own policy, never from this call).

      Implementations must leave IsBusy and IsAudioPlaying honest after the cancel (response inactive, local playback flushed).

      Returns void

    • Opens the provider connection using the server-minted ephemeral credential and applies config.SessionConfig once the control channel is ready — so prompt and tool authority stay server-side even though the browser owns the socket. The payload's SHAPE is a private pact between this driver and the same-keyed server driver that minted it (see driver obligation #8); how it is applied is entirely driver-specific.

      Parameters

      • config: ClientRealtimeSessionConfig

        The server-minted client session config (provider, model, ephemeral token, session config).

      • micStream: MediaStream

        The user's microphone capture stream. The caller acquires it (so IT owns the permission prompt UX); the client attaches it to the transport and stops its tracks on Disconnect.

      • OptionalcameraStream: MediaStream

        OPTIONAL camera capture stream for a VIDEO session (the model "sees" the user). Only attached by video-capable drivers; audio-only drivers ignore it (back-compatible — existing callers and drivers need not pass or read it). The model/avatar's video comes BACK via OnRemoteVideo.

      Returns Promise<void>

    • Tears down the provider connection and all client-held resources (control channel, transport, mic tracks, audio sink) and emits a final 'closed' state (unless the session already ended in 'error'). Safe to call more than once.

      Returns Promise<void>

    • Emits the model/avatar's remote video stream to the registered handler (video drivers only).

      Parameters

      • stream: MediaStream

      Returns void

    • The session's current audible activity, or null when this driver attached no meters at all (hosts then keep their turn-state-driven visuals). Sampled by hosts per animation frame — implementations are cheap, allocation-light reads of an AnalyserNode; no per-call provider traffic.

      Returns RealtimeAudioActivity

    • Returns the AGENT's remote-audio MediaStream when this driver owns a tappable remote-audio plane (e.g. a WebRTC peer-connection driver routes the model's audio track here), or null otherwise. Hosts use it to MIX the agent's voice into a browser-side recording alongside the mic; a null return (the default, and what every non-WebRTC driver gives) degrades gracefully to mic-only capture.

      Optional capability: the method itself is optional — call sites must use client.GetRemoteMediaStream?.() ?? null. Drivers that can't expose a remote stream simply don't implement it (or return null).

      Returns MediaStream

    • Registers the (single) interruption handler.

      True barge-in only: fires ONLY when USER INPUT CUT OFF ACTIVE MODEL OUTPUT — a model response in flight or audio audibly playing when the user took the floor. A user simply taking their normal turn while the model is idle is NOT an interruption, and drivers must not report it as one (e.g. a raw "speech started" frame must be gated on whether a response is actually active or audio is playing).

      On interruption, drivers must also flush locally-owned playback and report IsAudioPlaying === false promptly (driver obligation #3). Hosts typically use this hook to abort in-flight delegated work so a stale result is never narrated into a conversation that has moved on.

      Parameters

      • handler: () => void

      Returns void

    • Optional capability: registers a handler invoked when the agent's remote-audio stream becomes available — immediately if it has already landed, otherwise when the WebRTC track arrives (typically AFTER Connect resolves). Lets a host attach the agent voice to a recording that began before the track landed. Call as client.OnRemoteMediaStream?.(cb); drivers without a remote stream simply don't implement it.

      Parameters

      • handler: (stream: MediaStream) => void

      Returns void

    • Registers the (single) remote-VIDEO handler — the model/avatar's video track for a VIDEO session (a talking-head the host renders, e.g. as the agent's tile). Invoked once the provider publishes its video track.

      Optional capability: audio-only drivers (the default) never emit — registering is always safe, but hosts must not assume a video track arrives. Video-capable drivers (BaseRealtimeModel.SupportsVideo) call emitRemoteVideo when the track is live.

      Parameters

      • handler: (stream: MediaStream) => void

        Invoked with the remote video MediaStream when it becomes available.

      Returns void

    • Registers the (single) usage handler.

      Emissions carry token deltas for the response/turn that just completed (see RealtimeClientUsage — deltas preferred; cumulative-only providers must convert in the driver). Hosts accumulate and relay/persist on their own cadence (e.g. the voice session service debounces a RelayRealtimeUsage mutation onto the co-agent prompt run).

      Optional capability: drivers whose provider exposes no usage telemetry simply never emit — registering a handler is always safe, but hosts must not assume emissions arrive. See RealtimeClientUsage for per-driver availability.

      Parameters

      Returns void

    • Asks the model for ONE brief interim utterance (e.g. a spoken progress update while delegated work runs). Implementations MUST tag the resulting turn's transcripts with Kind: 'narration' and MUST NOT let this response collide with a pending tool-result reply — the tool result is queued and spoken as soon as the narration finishes.

      Collision rule — drivers MUST queue or skip: when a model response is already in flight, the driver must either defer the update to the next turn boundary or skip it outright (skipping is acceptable — narration is disposable by contract; a stale "still working…" line has no value once the real result lands). Hosts SHOULD still gate calls on IsBusy / IsAudioPlaying for timing quality — a well-timed update beats a queued-then-stale one — but the driver is the safety net.

      Parameters

      • instructions: string

        The exact provider instructions for the utterance. The instruction TEXT is host policy (e.g. first-person phrasing rules) — the client only carries it.

      Returns void

    • Injects background context into the model's conversation WITHOUT forcing a reply — e.g. delegated-run progress the model can draw on the next time it speaks.

      Parameters

      • text: string

        The context note text (the host owns any prefixing/formatting policy).

      Returns void

    • Injects typed text into the live session as a USER turn and asks the model to respond. Implementations must route the reply through the same collision-safe path as tool results so it never collides with an in-flight response. No-op when the control channel is not open.

      SendText implies barge-in: an active spoken response is cancelled (via the driver's own CancelActiveResponse path) before the text is injected, so the typed turn takes the floor immediately instead of waiting behind stale speech.

      Implementations must NOT synthesize a user-role transcript event for the injected text — the host owns the local echo (driver obligation #4).

      Parameters

      • text: string

        The user's typed message (callers pass pre-trimmed, non-empty text).

      Returns void

    • Feeds an executed tool's result back to the model, correlated by callID, and ensures the model SPEAKS the result as soon as possible: immediately when idle, otherwise queued behind the in-flight response (e.g. a progress narration) so the trigger is never dropped by the provider.

      Parameters

      • callID: string

        The CallID from the originating RealtimeClientToolCall.

      • outputJson: string

        The tool's result as a JSON-stringified string.

      Returns void

    • Mutes / unmutes the microphone (implementations toggle the mic tracks' enabled flag — the transport stays up; the provider just receives silence while muted).

      Parameters

      • muted: boolean

        true to mute the mic, false to unmute.

      Returns void