VideoCard class

A stateful widget displaying video participant card with stream rendering, controls, and waveform.

Renders a participant tile showing:

  • Video stream via CardVideoDisplay (or MiniCard fallback if no stream)
  • Name badge overlay (top-right by default)
  • Audio/video toggle buttons (top-left by default)
  • Animated waveform bars (9 bars, visible when video off + audio detected)
  • Optional mirror mode for local camera

Rendering Logic:

  1. If customBuilder provided → delegates full rendering
  2. Else builds Stack with:
    • Background Container (containerBuilder)
    • Video display via CardVideoDisplay or MiniCard fallback
    • AudioDecibelCheck (monitors audio levels)
    • Waveform overlay (overlayBuilder → waveformBuilder)
    • Name badge (infoBuilder)
    • Control buttons (audio/video toggles)

Layout Structure:

Stack (wrapperBuilder)
  ├─ Positioned.fill → Container (containerBuilder)
  │  └─ IF videoStream != null:
  │     └─ CardVideoDisplay (video stream)
  │     ELSE:
  │     └─ MiniCard (avatar fallback)
  ├─ AudioDecibelCheck (monitors audio, updates waveform state)
  ├─ IF showWaveform:
  │  └─ Positioned.fill → Container (overlayBuilder)
  │     └─ Row (waveformBuilder)
  │        └─ 9 × AnimatedContainer (bars)
  ├─ Positioned (infoPosition) → Container (infoBuilder)
  │  └─ Row (nameBadge + waveform indicator)
  └─ Positioned (controlsPosition) → Row
     ├─ GestureDetector (audio toggle)
     └─ GestureDetector (video toggle)

Video Stream Rendering:

  • Uses CardVideoDisplay to render MediaStream via WebRTC RTCVideoRenderer
  • Applies mirror mode if doMirror=true (CSS scaleX(-1))
  • Falls back to MiniCard avatar if videoStream null or video track unavailable
  • Respects forceFullDisplay for aspect ratio handling

Audio Level Detection:

  • AudioDecibelCheck component polls parameters.audioDecibels every 2 seconds
  • Matches by participant.name against audioDecibels[i].name
  • Updates showWaveform state based on:
    • Participant unmuted (participant.muted == false)
    • Audio detected (avgAudioDecibels > threshold)
    • Video off or !forceFullDisplay
  • Triggers waveform animation via AnimationControllers

Control Button Workflow:

  1. Audio Toggle (Mute/Unmute):

    • Taps mute icon in controls overlay
    • Calls toggleAudio()controlUserMedia
    • Parameters: { participantId, participantName, type: 'audio' }
    • Checks permissions (host/coHost with 'media' permission)
    • Emits socket event 'controlMedia' to server
  2. Video Toggle (On/Off):

    • Taps video icon in controls overlay
    • Calls toggleVideo()controlUserMedia
    • Parameters: { participantId, participantName, type: 'video' }
    • Checks permissions (host/coHost with 'media' permission)
    • Emits socket event 'controlMedia' to server

Waveform Animation:

  • State creates 9 AnimationControllers (1 per bar)
  • animateWaveform() starts repeat(reverse: true) animation
  • resetWaveform() stops/resets all controllers
  • Bar heights scale from 1px (silent) to 40px (loud)
  • Bars hidden if participant muted or no audio detected
  • Disposed on widget unmount

Builder Hook Priorities:

  • customBuilder → full widget replacement (ignores all other props)
  • wrapperBuilder → wraps Stack; receives stackChildren + default
  • containerBuilder → wraps video/avatar container; receives child + default
  • infoBuilder → wraps name badge/waveform; receives nameBadge + waveform + default
  • overlayBuilder → wraps waveform overlay; receives waveform + default
  • waveformBuilder → wraps bars; receives animationControllers + default

Common Use Cases:

  1. Grid View with Videos:

    GridView.builder(
      itemCount: videoParticipants.length,
      itemBuilder: (context, index) {
        final participant = videoParticipants[index];
        return VideoCard(
          options: VideoCardOptions(
            parameters: parameters,
            name: participant.name,
            remoteProducerId: participant.videoID,
            eventType: EventType.conference,
            videoStream: participant.stream,
            participant: participant,
          ),
        );
      },
    )
    
  2. Local Camera Preview:

    VideoCard(
      options: VideoCardOptions(
        parameters: parameters,
        name: 'You',
        remoteProducerId: 'local',
        eventType: EventType.conference,
        videoStream: localStream,
        participant: localParticipant,
        doMirror: true,
        controlsPosition: 'bottomRight',
      ),
    )
    
  3. Host-Only Controls:

    VideoCard(
      options: VideoCardOptions(
        parameters: parameters,
        name: 'John Doe',
        remoteProducerId: 'producer-123',
        eventType: EventType.conference,
        videoStream: mediaStream,
        participant: participant,
        showControls: isHost,
        videoControlsComponent: isHost ? hostControls : null,
      ),
    )
    
  4. Custom Name Badge with Status:

    VideoCard(
      options: VideoCardOptions(
        parameters: parameters,
        name: 'John Doe',
        remoteProducerId: 'producer-123',
        eventType: EventType.conference,
        videoStream: mediaStream,
        participant: participant,
        infoBuilder: (context, nameBadge, waveform, defaultInfo) {
          return Stack(
            children: [
              defaultInfo,
              Positioned(
                bottom: 0,
                right: 0,
                child: Icon(Icons.fiber_manual_record, color: Colors.green, size: 12),
              ),
            ],
          );
        },
      ),
    )
    

Override Integration: Integrates with MediasfuUICustomOverrides for global styling:

overrides: MediasfuUICustomOverrides(
  videoCardOptions: ComponentOverride<VideoCardOptions>(
    builder: (existingOptions) => VideoCardOptions(
      parameters: existingOptions.parameters,
      name: existingOptions.name,
      remoteProducerId: existingOptions.remoteProducerId,
      eventType: existingOptions.eventType,
      videoStream: existingOptions.videoStream,
      participant: existingOptions.participant,
      containerDecoration: BoxDecoration(
        gradient: LinearGradient(colors: [Colors.blue, Colors.purple]),
        borderRadius: BorderRadius.circular(16),
      ),
      barColor: Colors.yellow,
      nameTextStyle: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
    ),
  ),
),

Performance Notes:

  • Animation controllers created once in initState (9 controllers)
  • Audio level polling every 2 seconds (not every frame)
  • Waveform hidden when video active (skips rendering 9 bars)
  • Video rendering delegated to CardVideoDisplay (uses RTCVideoRenderer)
  • Disposed controllers + video renderer cleaned up in dispose()

Permission Requirements:

  • Controlling others requires host or coHost with 'media' permission
  • Self-control always allowed
  • Socket connection required for remote control actions

Mirror Mode:

  • Applied via CSS transform: scaleX(-1) on video element
  • Typically used for local camera to match user expectation
  • Does not affect stream data, only display
Inheritance

Constructors

VideoCard.new({Key? key, required VideoCardOptions options})
const

Properties

hashCode int
The hash code for this object.
no setterinherited
key Key?
Controls how one widget replaces another widget in the tree.
finalinherited
options VideoCardOptions
final
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

createElement() StatefulElement
Creates a StatefulElement to manage this widget's location in the tree.
inherited
createState() → _VideoCardState
Creates the mutable state for this widget at a given location in the tree.
override
debugDescribeChildren() List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) String
Returns a one-line detailed description of the object.
inherited
toStringShort() String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited