Voice Visualizations

When users talk to your agent instead of typing, AIVoiceGlowView and AIVoiceWaveView give the agent a visual presence. AIVoiceGlowView is a soft globe of drifting color, and AIVoiceWaveView is a single waveform line. Both show what the agent is doing through the way they move. Pass the agent's state:

SizedBox.square(
  dimension: 320,
  child: AIVoiceGlowView(state: AIVoiceAgentState.listening),
);
Info:

The voice agent visualizations are available from version 0.2.0 of stream_chat_flutter_ai.

The Voice agent use case builds a whole voice screen around them: a call with your Stream Agents agent, the globe driven by the agent's state and voice, and what was said written out as a conversation.

States

The state is an AIVoiceAgentState:

State The agent is... AIVoiceGlowView AIVoiceWaveView
idle Waiting to be spoken to A calm, barely-there shimmer A nearly flat line
connecting Not ready yet A loader sweep with a pulsing breath Sweeps from side to side
listening Hearing the user Gently expands and contracts A nearly flat line
thinking Working on an answer Turns slowly Carries a steady, travelling wave
speaking Talking Swells with the speech and its pauses Erupts with the speech, flat in its pauses

The states change only the motion, never the colors, and a change of state eases from one motion into the next. Set the state straight from your agent's events, without animating it yourself.

Neither view has a size of its own: each fills the space it's given, on a transparent background. The globe is about half as wide as the shorter side of that space, and the line about 80% as wide, so give either view a square.

Parameters

Both views take the same parameters:

  • state - what the agent is doing.
  • colors - one to five colors to paint it with. Colors past the fifth are ignored. Leave it out, or pass an empty list, to use the Stream theme's accentPrimary. See Colors. The colors keep their full hue, saturation and brightness, so muted brand colors render as they are.
  • expressivity - how lively the motion is, from 0 (nearly still) through 1 (the default) to 2 (twice as animated). It never changes the colors.
  • audioLevel - the live level of the voice being heard, from 0 to 1, or null. Leave it out, and the view animates speech on its own.

Follow the real voice

To drive the motion from the agent's voice, pass a live audioLevel, updated at least 15 times a second. Live audio lifts the motion to its level, whatever the state. The glow follows the level closely, while the wave smooths it, so the line swells with each phrase rather than jittering with every syllable.

With Stream Video, that's the audioLevel of the agent as a participant of the call:

StreamBuilder(
  stream: call.state.valueStream,
  initialData: call.state.value,
  builder: (context, snapshot) {
    final agent = snapshot.requireData.callParticipants.where((p) => p.userId == agentId).firstOrNull;

    return SizedBox.square(
      dimension: 320,
      child: AIVoiceWaveView(
        state: agentState,
        colors: const [Colors.purple, Colors.pink, Colors.teal],
        expressivity: 1.4,
        audioLevel: agent?.audioLevel,
      ),
    );
  },
);

Colors

The default palette is the Stream theme's accentPrimary, so a StreamTheme with your brand colors, as described in Theming, colors the views too. To use the same palette of several colors everywhere, keep it in one place and pass it as colors to each view:

const voicePalette = [Color(0xFF3F51B5), Color(0xFFE91E63), Color(0xFF009688)];

AIVoiceGlowView(state: agentState, colors: voicePalette);

Touch, accessibility and rendering

  • Tapping the globe or the line bounces it and sends a ripple out from the touch, and a hovering pointer ripples it too. The views don't take the taps, so a GestureDetector around them, such as one that starts a call, still gets them.
  • With reduced motion turned on, the views barely move and ignore taps.
  • Screen readers read either view as "Voice agent", with the state as its value, such as "Listening". These texts are the voiceAgent, voiceIdle, voiceConnecting, voiceListening, voiceThinking and voiceSpeaking members of AITranslations.
  • Both views are drawn with fragment shaders, so they look the same on every platform. Nothing is drawn until the shader has loaded, and like any Flutter animation, the views stop while your app is in the background.