# Demo App

## GitHub Link:

<https://github.com/Huddle-01/huddle01-flutter-example>

## Folder Structure

```dart
lib
 ┣ logic
 ┃ ┗ blocs
 ┃ ┃ ┣ consumers
 ┃ ┃ ┃ ┣ consumers_bloc.dart
 ┃ ┃ ┃ ┣ consumers_event.dart
 ┃ ┃ ┃ ┗ consumers_state.dart
 ┃ ┃ ┣ me
 ┃ ┃ ┃ ┣ me_bloc.dart
 ┃ ┃ ┃ ┣ me_event.dart
 ┃ ┃ ┃ ┗ me_state.dart
 ┃ ┃ ┣ media_devices
 ┃ ┃ ┃ ┣ media_devices_bloc.dart
 ┃ ┃ ┃ ┣ media_devices_event.dart
 ┃ ┃ ┃ ┗ media_devices_state.dart
 ┃ ┃ ┣ peers
 ┃ ┃ ┃ ┣ peers_bloc.dart
 ┃ ┃ ┃ ┣ peers_event.dart
 ┃ ┃ ┃ ┗ peers_state.dart
 ┃ ┃ ┣ producers
 ┃ ┃ ┃ ┣ producers_bloc.dart
 ┃ ┃ ┃ ┣ producers_event.dart
 ┃ ┃ ┃ ┗ producers_state.dart
 ┃ ┃ ┗ room
 ┃ ┃ ┃ ┣ room_bloc.dart
 ┃ ┃ ┃ ┣ room_event.dart
 ┃ ┃ ┃ ┗ room_state.dart
 ┣ presentation
 ┃ ┣ components
 ┃ ┃ ┣ list_media_devices
 ┃ ┃ ┃ ┗ list_media_devices.dart
 ┃ ┃ ┣ me
 ┃ ┃ ┃ ┣ microphone.dart
 ┃ ┃ ┃ ┣ renderMe.dart
 ┃ ┃ ┃ ┗ webcam.dart
 ┃ ┃ ┗ others
 ┃ ┃ ┃ ┣ other.dart
 ┃ ┃ ┃ ┗ renderOthers.dart
 ┃ ┣ enter_page.dart
 ┃ ┣ room.dart
 ┃ ┗ voice_audio_settings.dart
 ┗ main.dart
```

## main.dart

```dart
import 'dart:developer' as dev;
import 'dart:math';

import 'package:english_words/english_words.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/consumers/consumers_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/me/me_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/media_devices/media_devices_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/peers/peers_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/producers/producers_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/room/room_bloc.dart';
import 'package:huddle01_flutter_example/presentation/enter_page.dart';
import 'package:huddle01_flutter_example/presentation/room.dart';
import 'package:random_string/random_string.dart';

import 'package:flutter/material.dart';

void main() {
  runApp(BlocProvider<MediaDevicesBloc>(
      create: (context) => MediaDevicesBloc()..add(MediaDeviceLoadDevices()),
      lazy: false,
      child: MyApp()));
}

class MyApp extends StatelessWidget {
  _setupEventListeners({
    required ConsumersBloc consumersBloc,
    required ProducersBloc producersBloc,
    required PeersBloc peersBloc,
    required MeBloc meBloc,
  }) {
    emitter.on('addConsumer', (consumer) {
      consumersBloc.add(ConsumerAdd(consumer: consumer));
    });
    emitter.on('removeConsumer', (consumerId) {
      consumersBloc.add(ConsumerRemove(consumerId: consumerId));
    });
    emitter.on('addProducer', (producer) {
      producersBloc.add(ProducerAdd(producer: producer));
      if (producer.source == 'webcam') {
        meBloc.add(MeSetWebcamInProgress(progress: true));
      }
    });
    emitter.on('removeProducer', (source) {
      producersBloc.add(ProducerRemove(source: source));
      if (source == 'webcam') {
        meBloc.add(MeSetWebcamInProgress(progress: false));
      }
    });
    emitter.on('addPeer', (peer) {
      peersBloc.add(PeerAdd(newPeer: peer));
    });
    emitter.on('removePeer', (peerId) {
      peersBloc.add(PeerRemove(peerId: peerId));
    });
    emitter.on('addPeerConsumer', (consumer) {
      peersBloc.add(
          PeerAddConsumer(peerId: consumer.peerId, consumerId: consumer.id));
    });
    emitter.on('removePeerConsumer', (peerId, consumerId) {
      peersBloc.add(PeerRemoveConsumer(peerId: peerId, consumerId: consumerId));
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      // ignore: missing_return
      onGenerateRoute: (settings) {
        if (settings.name == EnterPage.RoutePath) {
          return MaterialPageRoute(
            builder: (context) => EnterPage(),
          );
        }
        if (settings.name == '/room') {
          return MaterialPageRoute(
            builder: (context) => MultiBlocProvider(
                // just like multiprovider, we are providing all the blocs except the media devices one to the Room
                providers: [
                  BlocProvider<ProducersBloc>(
                    lazy: false,
                    create: (context) => ProducersBloc(),
                  ),
                  BlocProvider<ConsumersBloc>(
                    lazy: false,
                    create: (context) => ConsumersBloc(),
                  ),
                  BlocProvider<PeersBloc>(
                    lazy: false,
                    create: (context) => PeersBloc(
                      consumersBloc: context.read<ConsumersBloc>(),
                    ),
                  ),
                  BlocProvider<MeBloc>(
                    lazy: false,
                    create: (context) => MeBloc(
                        displayName: nouns[Random.secure().nextInt(2500)],
                        id: randomAlpha(8)),
                  ),
                  BlocProvider<RoomBloc>(
                    lazy: false,
                    create: (context) =>
                        RoomBloc(settings.arguments.toString()),
                  ),
                ],
                child: RepositoryProvider<HuddleClientRepository>(
                  // provider to provide room client as an object to all the child widgets of this
                  lazy: false,
                  create: (context) {
                    _setupEventListeners(
                      consumersBloc: context.read<ConsumersBloc>(),
                      producersBloc: context.read<ProducersBloc>(),
                      peersBloc: context.read<PeersBloc>(),
                      meBloc: context.read<MeBloc>(),
                    );
                    MediaDevicesBloc mediaDevicesBloc =
                        context.read<MediaDevicesBloc>();
                    String audioInputDeviceId =
                        mediaDevicesBloc.state.selectedAudioInput!.deviceId;
                    String videoInputDeviceId =
                        mediaDevicesBloc.state.selectedVideoInput!.deviceId;
                    final meState = context.read<MeBloc>().state;
                    String displayName = meState.displayName;
                    String id = meState.id;
                    final roomState = context.read<RoomBloc>().state;
                    String roomId = roomState.roomId!;

                    return HuddleClientRepository(
                      peerId: id,
                      displayName: displayName,
                      apiKey: "i4pzqbpxza8vpijQMwZsP1H7nZZEH0TN3vR4NdNS",
                      roomId: roomId,
                      audioInputDeviceId: audioInputDeviceId,
                      videoInputDeviceId: videoInputDeviceId,
                    )..join();
                  },
                  child: Room(),
                )),
          );
        }
      },
    );
  }
}

```

## UI and screens

{% tabs %}
{% tab title="room.dart" %}

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/media_devices/media_devices_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/room/room_bloc.dart';
import 'package:huddle01_flutter_example/presentation/components/me/renderMe.dart';
import 'package:huddle01_flutter_example/presentation/components/others/renderOthers.dart';
import 'package:huddle01_flutter_example/presentation/voice_audio_settings.dart';

class Room extends StatefulWidget {
  const Room({Key? key}) : super(key: key);

  @override
  _RoomState createState() => _RoomState();
}

class _RoomState extends State<Room> {
  late StreamSubscription<MediaDevicesState> _mediaDevicesBlocSubscription;
  late String audioInputDeviceId;
  late String videoInputDeviceId;

  @override
  void dispose() {
    super.dispose();
    _mediaDevicesBlocSubscription.cancel();
  }

  @override
  void initState() {
    super.initState();
    audioInputDeviceId =
        context.read<MediaDevicesBloc>().state.selectedAudioInput!.deviceId;
    videoInputDeviceId =
        context.read<MediaDevicesBloc>().state.selectedVideoInput!.deviceId;
    _mediaDevicesBlocSubscription = context
        .read<MediaDevicesBloc>()
        .stream
        .listen((MediaDevicesState state) async {
      if (state.selectedAudioInput != null &&
          state.selectedAudioInput!.deviceId != audioInputDeviceId) {
        await context.read<HuddleClientRepository>().disableMic();
        context.read<HuddleClientRepository>().enableMic();
      }

      if (state.selectedVideoInput != null &&
          state.selectedVideoInput!.deviceId != videoInputDeviceId) {
        await context.read<HuddleClientRepository>().disableWebcam();
        context.read<HuddleClientRepository>().enableWebcam();
      }
    });
  }

  setStreamListener(context) {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Builder(
          builder: (context) {
            String roomId = context.select((RoomBloc bloc) => bloc.state.roomId!);
            return Text(roomId);
          },
        ),
        actions: [
          IconButton(
            onPressed: () {
              String roomId = context.read<RoomBloc>().state.roomId!;
              Clipboard.setData(ClipboardData(text: roomId));
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: const Text('Room link copied to clipboard'),
                  duration: const Duration(seconds: 1),
                ),
              );
            },
            icon: Icon(Icons.copy),
          ),
          IconButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (_) => BlocProvider.value(
                    value: context.read<MediaDevicesBloc>(),
                    child: AudioVideoSettings(),
                  ),
                ),
              );
            },
            icon: Icon(Icons.settings),
          ),
        ],
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () {
            context.read<HuddleClientRepository>().close();
            Navigator.pop(context);
          },
        ),
      ),
      body: Stack(
        fit: StackFit.expand,
        children: [
          RenderOther(),
          RenderMe(),
        ],
      ),
    );
  }
}

```

{% endtab %}

{% tab title="enter\_page.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/media_devices/media_devices_bloc.dart';
import 'package:huddle01_flutter_example/presentation/components/list_media_devices/list_media_devices.dart';

class EnterPage extends StatefulWidget {
  static const String RoutePath = '/';

  @override
  _EnterPageState createState() => _EnterPageState();
}

class _EnterPageState extends State<EnterPage> {
  final TextEditingController _textEditingController = TextEditingController();

  @override
  void initState() {
    super.initState();

    _textEditingController.addListener(() {
      final String text = _textEditingController.text.toLowerCase();
      setState(() {
        _textEditingController.value = _textEditingController.value.copyWith(
          text: text,
        );
      });
    });
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('mediasoup-client-flutter'),
      ),
      body: Container(
        alignment: Alignment.center,
        child: Column(
          children: [
            TextField(
              autofocus: false,
              controller: _textEditingController,
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                floatingLabelBehavior: FloatingLabelBehavior.always,
                hintText: 'Room url (empty = random room)',
              ),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/room',
                  arguments: _textEditingController.value.text.toLowerCase(),
                );
              },
              child: Text('Join'),
            ),
            Text(
              'Audio Input',
              style: Theme.of(context).textTheme.headline5,
            ),
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> audioInputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.audioInputs);
                final MediaDeviceInfo? selectedAudioInput = context.select(
                    (MediaDevicesBloc bloc) => bloc.state.selectedAudioInput);

                return ListMediaDevices(
                  key: Key('audioinput'),
                  selectedDevice: selectedAudioInput,
                  devices: audioInputDevices,
                  onSelect: (MediaDeviceInfo device) => context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectAudioInput(device)),
                );
              },
            ),
            Text(
              'Audio Output',
              style: Theme.of(context).textTheme.headline5,
            ),
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> audioOutputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.audioOutputs);
                final MediaDeviceInfo? selectedAudioOutput = context.select(
                    (MediaDevicesBloc bloc) => bloc.state.selectedAudioOutput);

                return ListMediaDevices(
                  key: Key('audiooutput'),
                  selectedDevice: selectedAudioOutput,
                  devices: audioOutputDevices,
                  onSelect: (MediaDeviceInfo device) => context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectAudioOutput(device)),
                );
              },
            ),
            Text(
              'Video Input',
              style: Theme.of(context).textTheme.headline5,
            ),
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> videoInputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.videoInputs);
                final MediaDeviceInfo? selectedVideoInput = context.select(
                    (MediaDevicesBloc bloc) => bloc.state.selectedVideoInput);

                return ListMediaDevices(
                  key: Key('videoinput'),
                  selectedDevice: selectedVideoInput,
                  devices: videoInputDevices,
                  onSelect: (MediaDeviceInfo device) => context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectVideoInput(device)),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

```

{% endtab %}

{% tab title="voice\_audio\_settings.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/media_devices/media_devices_bloc.dart';
import 'package:huddle01_flutter_example/presentation/components/list_media_devices/list_media_devices.dart';

class AudioVideoSettings extends StatefulWidget {
  const AudioVideoSettings({Key? key}) : super(key: key);

  @override
  _AudioVideoSettingsState createState() => _AudioVideoSettingsState();
}

class _AudioVideoSettingsState extends State<AudioVideoSettings> {
  MediaDeviceInfo? selectedAudioInput;
  MediaDeviceInfo? selectedAudioOutput;
  MediaDeviceInfo? selectedVideoInput;

  @override
  void initState() {
    super.initState();

    final MediaDevicesState devicesInfo =
        context.read<MediaDevicesBloc>().state;
    setState(() {
      selectedAudioInput = devicesInfo.selectedAudioInput;
      selectedAudioOutput = devicesInfo.selectedAudioOutput;
      selectedVideoInput = devicesInfo.selectedVideoInput;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Audio & Video'),
        actions: [
          Builder(
            builder: (context) => IconButton(
              onPressed: () {
                final MediaDevicesState devicesInfo =
                    context.read<MediaDevicesBloc>().state;
                final MediaDeviceInfo? audioInput =
                    devicesInfo.selectedAudioInput;
                final MediaDeviceInfo? audioOutput =
                    devicesInfo.selectedAudioOutput;
                final MediaDeviceInfo? videoInput =
                    devicesInfo.selectedVideoInput;

                if (audioInput != selectedAudioInput) {
                  context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectAudioInput(selectedAudioInput!));
                }
                if (audioOutput != selectedAudioOutput) {
                  context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectAudioOutput(selectedAudioOutput));
                }
                if (videoInput != selectedVideoInput) {
                  context
                      .read<MediaDevicesBloc>()
                      .add(MediaDeviceSelectVideoInput(selectedVideoInput!));
                }
                Navigator.pop(context);
              },
              icon: Icon(
                Icons.save,
              ),
            ),
          ),
        ],
      ),
      body: Container(
        alignment: Alignment.center,
        child: Column(
          children: [
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> audioInputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.audioInputs);

                return ListMediaDevices(
                  key: Key('audioinput'),
                  selectedDevice: selectedAudioInput!,
                  devices: audioInputDevices,
                  onSelect: (MediaDeviceInfo device) => setState(() {
                    selectedAudioInput = device;
                  }),
                );
              },
            ),
            Text(
              'Audio Output',
              style: Theme.of(context).textTheme.headline5,
            ),
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> audioOutputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.audioOutputs);

                return ListMediaDevices(
                  key: Key('audiooutput'),
                  selectedDevice: selectedAudioOutput,
                  devices: audioOutputDevices,
                  onSelect: (MediaDeviceInfo device) => setState(() {
                    selectedAudioOutput = device;
                  }),
                );
              },
            ),
            Text(
              'Video Input',
              style: Theme.of(context).textTheme.headline5,
            ),
            Builder(
              builder: (context) {
                final List<MediaDeviceInfo> videoInputDevices = context
                    .select((MediaDevicesBloc bloc) => bloc.state.videoInputs);

                return ListMediaDevices(
                  key: Key('videoinput'),
                  selectedDevice: selectedVideoInput!,
                  devices: videoInputDevices,
                  onSelect: (MediaDeviceInfo device) => setState(() {
                    selectedVideoInput = device;
                  }),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

```

{% endtab %}

{% tab title="list\_media\_devices.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';

class ListMediaDevices extends StatelessWidget {
  final List<MediaDeviceInfo> devices;
  final MediaDeviceInfo? selectedDevice;
  final Function(MediaDeviceInfo) onSelect;
  const ListMediaDevices({
    Key? key,
    required this.devices,
    this.selectedDevice,
    required this.onSelect,
  }) : super(key: key);

  void selectDevice(int index) {
    if (selectedDevice != devices[index]) {
      onSelect(devices[index]);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (devices.isNotEmpty) {
      return ListView.builder(
        shrinkWrap: true,
        scrollDirection: Axis.vertical,
        itemCount: devices.length,
        itemBuilder: (context, index) => ListTile(
          key: Key(devices[index].deviceId),
          title: Text(devices[index].label),
          subtitle: Text('id: ${devices[index].deviceId}'),
          selected: selectedDevice == devices[index],
          onTap: () => selectDevice(index),
        ),
      );
    }

    return Text('No devices');
  }
}

```

{% endtab %}

{% tab title="others/other.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';

class Other extends StatelessWidget {
  final Peer peer;
  final Consumer? video;
  final RTCVideoRenderer? renderer;

  const Other({Key? key, required this.peer, this.video, this.renderer})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 250,
      width: 300,
      color: Colors.black87,
      child: Stack(
        fit: StackFit.expand,
        children: [
          if (video != null && renderer != null)
            RTCVideoView(renderer!)
          else
            Container(
              height: 250,
              width: 300,
              decoration: BoxDecoration(color: Colors.black54),
            ),
          Positioned(
              bottom: 5,
              left: 2,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    child: Text(
                      '${peer.displayName}\n${peer.device!.name} ${peer.device!.version}',
                      style: TextStyle(
                        color: Colors.white,
                      ),
                    ),
                    decoration: BoxDecoration(
                      color: Colors.black87,
                      borderRadius: const BorderRadius.all(Radius.circular(8)),
                    ),
                    padding: const EdgeInsets.all(8),
                  ),
                ],
              ))
        ],
      ),
    );
  }
}

```

{% endtab %}

{% tab title="others/render\_other.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/consumers/consumers_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/peers/peers_bloc.dart';
import 'package:huddle01_flutter_example/presentation/components/others/other.dart';

class RenderOther extends StatelessWidget {
  const RenderOther({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final Map<String, Peer> peers =
        context.select((PeersBloc bloc) => bloc.state.peers);
    final Map<String, Consumer> consumers =
        context.select((ConsumersBloc bloc) => bloc.state.consumers);
    final Map<String, RTCVideoRenderer> renderers =
        context.select((ConsumersBloc bloc) => bloc.state.renderers);

    return GridView.count(
      crossAxisCount: 2,
      scrollDirection: Axis.vertical,
      shrinkWrap: true,
      children: peers.values.map((Peer peer) {
        if (peer.consumers.isNotEmpty) {
          String? id = peer.consumers.firstWhere(
            (cId) => consumers[cId]?.kind == 'video',
            orElse: () => 'null',
          );
          if (id != 'null') {
            return Other(
              key: Key(peer.id! + id),
              peer: peer,
              video: consumers[id]!,
              renderer: renderers[id]!,
            );
          }
        }
        return Other(
          key: Key(peer.id!),
          peer: peer,
        );
      }).toList(),
    );
  }
}

```

{% endtab %}

{% tab title="me/microphone.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/producers/producers_bloc.dart';

class Microphone extends StatefulWidget {
  const Microphone({Key? key}) : super(key: key);

  @override
  _MicrophoneState createState() => _MicrophoneState();
}

class _MicrophoneState extends State<Microphone> {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ProducersBloc, ProducersState>(
      builder: (context, state) {
        if (state.mic == null)
          return IconButton(
            icon: Icon(
              Icons.mic_off,
              color: Colors.grey,
            ),
            onPressed: () {},
          );
        return IconButton(
            onPressed: () {
              if (state.mic!.paused) {
                context.read<HuddleClientRepository>().unmuteMic();
                setState(() {});
              } else {
                context.read<HuddleClientRepository>().muteMic();
                setState(() {});
              }
            },
            icon: Icon(state.mic!.paused ? Icons.mic_off : Icons.mic));
      },
    );
  }
}

```

{% endtab %}

{% tab title="me/webcam.dart" %}

```dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/me/me_bloc.dart';
import 'package:huddle01_flutter_example/logic/blocs/producers/producers_bloc.dart';

class Webcam extends StatefulWidget {
  const Webcam({Key? key}) : super(key: key);

  @override
  _WebcamState createState() => _WebcamState();
}

class _WebcamState extends State<Webcam> {
  @override
  Widget build(BuildContext context) {
    bool inProgress =
        context.select((MeBloc bloc) => bloc.state.webcamInProgress);

    return BlocBuilder<ProducersBloc, ProducersState>(
      builder: (context, state) {
        // if (state.webcam == null) return IconButton(icon: Icon(Icons.videocam_off, color: Colors.grey,),);
        return IconButton(
            onPressed: () {
              // if (inProgress) {
              //   return;
              // }
              if (state.webcam != null) {
                context.read<HuddleClientRepository>().disableWebcam();
                setState(() {});
              } else {
                context.read<HuddleClientRepository>().enableWebcam();
                setState(() {});
              }
            },
            icon: Icon(
                state.webcam == null ? Icons.videocam_off : Icons.videocam));
      },
    );
  }
}

```

{% endtab %}

{% tab title="me/render\_me.dart" %}

```dart
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/producers/producers_bloc.dart';
import 'package:huddle01_flutter_example/presentation/components/me/microphone.dart';
import 'package:huddle01_flutter_example/presentation/components/me/webcam.dart';

class RenderMe extends StatefulWidget {
  const RenderMe({Key? key}) : super(key: key);

  @override
  _RenderMeState createState() => _RenderMeState();
}

class _RenderMeState extends State<RenderMe> {
  late RTCVideoRenderer renderer;

  @override
  void initState() {
    super.initState();
    initRenderers();
  }

  @override
  Widget build(BuildContext context) {
    try {
      log('starting render me');
      return BlocConsumer<ProducersBloc, ProducersState>(
        listener: (context, state) {
          log('inside listener');
          try {
            renderer.srcObject = state.webcam!.stream;
            log(renderer.toString());
          } catch (e) {
            log(e.toString());
          }
        },
        builder: (context, state) {
          log('inside bloc consumer');
          try {
            return Align(
              alignment: Alignment.bottomLeft,
              child: Stack(
                alignment: Alignment.centerRight,
                children: [
                  Container(
                    width: MediaQuery.of(context).size.width * .3,
                    height: MediaQuery.of(context).size.height * .3,
                    margin: const EdgeInsets.only(left: 5, bottom: 10),
                    decoration: BoxDecoration(
                      color: Colors.black54,
                      borderRadius: BorderRadius.circular(8),
                      shape: BoxShape.rectangle,
                      border: Border.all(
                        color: Colors.black,
                        width: 2.0,
                        style: BorderStyle.solid,
                      ),
                    ),
                    child: RTCVideoView(
                      renderer,
                      // mirror: true,
                    ),
                  ),
                  Positioned(
                    top: 5,
                    right: 5,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        Microphone(),
                        Webcam(),
                      ],
                    ),
                  )
                ],
              ),
            );
          } catch (e) {
            log(e.toString());
            return Container();
          }
        },
      );
    } catch (e) {
      log(e.toString());
      return Container();
    }
  }

  void initRenderers() async {
    renderer = RTCVideoRenderer();
    await renderer.initialize();
  }

  @override
  void dispose() {
    renderer.dispose();
    super.dispose();
  }
}

```

{% endtab %}
{% endtabs %}

## Logic/Blocs

### room bloc

{% tabs %}
{% tab title="room\_bloc.dart" %}

```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:random_string/random_string.dart';

part 'room_event.dart';
part 'room_state.dart';

class RoomBloc extends Bloc<RoomEvent, RoomState> {
  RoomBloc(String roomId)
      : super(
          RoomState(
            roomId: roomId != null && roomId.isNotEmpty
                ? roomId
                : randomAlpha(8).toLowerCase(),
          ),
        );

  @override
  Stream<RoomState> mapEventToState(
    RoomEvent event,
  ) async* {
    if (event is RoomSetActiveSpeakerId) {
      yield* _mapRoomSetActiveSpeakerIdToState(event);
    }
  }

  Stream<RoomState> _mapRoomSetActiveSpeakerIdToState(
      RoomSetActiveSpeakerId event) async* {
    yield RoomState.newActiveSpeaker(state, activeSpeakerId: event.speakerId);
  }
}

```

{% endtab %}

{% tab title="room\_state.dart" %}

```dart
part of 'room_bloc.dart';

class RoomState extends Equatable {
  final String? activeSpeakerId;
  final String? state;
  final String? roomId;
  const RoomState({this.activeSpeakerId, this.state, this.roomId});

  static RoomState newActiveSpeaker(
    RoomState old, {
    String? activeSpeakerId,
  }) {
    return RoomState(
        roomId: old.roomId, state: old.state, activeSpeakerId: activeSpeakerId);
  }

  @override
  List<Object> get props => [activeSpeakerId!, state!, roomId!];
}

```

{% endtab %}

{% tab title="room\_event.dart" %}

```dart
part of 'room_bloc.dart';

abstract class RoomEvent extends Equatable {
  const RoomEvent();
}

class RoomSetActiveSpeakerId extends RoomEvent {
  final String speakerId;

  const RoomSetActiveSpeakerId({required this.speakerId});

  @override
  List<Object> get props => [speakerId];
}

```

{% endtab %}
{% endtabs %}

### producers bloc

{% tabs %}
{% tab title="producers\_bloc.dart" %}

```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';

part 'producers_event.dart';
part 'producers_state.dart';

class ProducersBloc extends Bloc<ProducersEvent, ProducersState> {
  ProducersBloc() : super(ProducersState());

  @override
  Stream<ProducersState> mapEventToState(
    ProducersEvent event,
  ) async* {
    if (event is ProducerAdd) {
      yield* _mapProducerAddToState(event);
    } else if (event is ProducerRemove) {
      yield* _mapProducerRemoveToState(event);
    } else if (event is ProducerResumed) {
      yield* _mapProducerResumeToState(event);
    } else if (event is ProducerPaused) {
      yield* _mapProducerPausedToState(event);
    }
  }

  Stream<ProducersState> _mapProducerAddToState(ProducerAdd event) async* {
    switch (event.producer.source) {
      case 'mic':
        {
          yield ProducersState.copy(state, mic: event.producer);
          break;
        }
      case 'webcam':
        {
          yield ProducersState.copy(state, webcam: event.producer);
          break;
        }
      case 'screen':
        {
          yield ProducersState.copy(state, screen: event.producer);
          break;
        }
      default:
        break;
    }
  }

  Stream<ProducersState> _mapProducerRemoveToState(
      ProducerRemove event) async* {
    switch (event.source) {
      case 'mic':
        {
          state.mic?.close.call();
          yield ProducersState.removeMic(state);
          break;
        }
      case 'webcam':
        {
          state.webcam?.close.call();
          yield ProducersState.removeWebcam(state);
          break;
        }
      case 'screen':
        {
          state.screen?.close.call();
          yield ProducersState.removeScreen(state);
          break;
        }
      default:
        break;
    }
  }

  Stream<ProducersState> _mapProducerResumeToState(
      ProducerResumed event) async* {
    switch (event.source) {
      case 'mic':
        {
          state.mic?.resume.call();
          yield ProducersState.copy(state);
          break;
        }
      case 'webcam':
        {
          state.webcam?.resume.call();
          yield ProducersState.copy(state);
          break;
        }
      case 'screen':
        {
          state.screen?.resume.call();
          yield ProducersState.copy(state);
          break;
        }
      default:
        break;
    }
  }

  Stream<ProducersState> _mapProducerPausedToState(
      ProducerPaused event) async* {
    switch (event.source) {
      case 'mic':
        {
          state.mic?.pause.call();
          yield ProducersState.copy(state);
          break;
        }
      case 'webcam':
        {
          state.webcam?.pause.call();
          yield ProducersState.copy(state);
          break;
        }
      case 'screen':
        {
          state.screen?.pause.call();
          yield ProducersState.copy(state);
          break;
        }
      default:
        break;
    }
  }
}

```

{% endtab %}

{% tab title="producers\_state.dart" %}

```dart
part of 'producers_bloc.dart';

class ProducersState extends Equatable {
  final Producer? mic;
  final Producer? webcam;
  final Producer? screen;

  const ProducersState({
    this.mic,
    this.webcam,
    this.screen,
  });

  static ProducersState copy(
    ProducersState old, {
    Producer? mic,
    Producer? webcam,
    Producer? screen,
  }) {
    return ProducersState(
      mic: mic ?? old.mic,
      webcam: webcam ?? old.webcam,
      screen: screen ?? old.screen,
    );
  }

  static ProducersState removeMic(ProducersState old) {
    return ProducersState(
      mic: null,
      webcam: old.webcam,
      screen: old.screen,
    );
  }

  static ProducersState removeWebcam(ProducersState old) {
    return ProducersState(
      mic: old.mic,
      webcam: null,
      screen: old.screen,
    );
  }

  static ProducersState removeScreen(ProducersState old) {
    return ProducersState(
      mic: old.mic,
      webcam: old.webcam,
      screen: null,
    );
  }

  @override
  List<Object> get props => [
        if (this.mic != null) this.mic!,
        if (this.webcam != null) this.webcam!,
        if (this.screen != null) this.screen!
      ];
}

```

{% endtab %}

{% tab title="producers\_event.dart" %}

```dart
part of 'producers_bloc.dart';

abstract class ProducersEvent extends Equatable {
  const ProducersEvent();
}

class ProducerAdd extends ProducersEvent {
  final Producer producer;

  const ProducerAdd({required this.producer});

  @override
  List<Object> get props => throw UnimplementedError();
}

class ProducerRemove extends ProducersEvent {
  final String source;

  const ProducerRemove({required this.source});

  @override
  List<Object> get props => [source];
}

class ProducerPaused extends ProducersEvent {
  final String source;

  const ProducerPaused({required this.source});

  @override
  List<Object> get props => [source];
}

class ProducerResumed extends ProducersEvent {
  final String source;

  const ProducerResumed({required this.source});

  @override
  List<Object> get props => [source];
}

```

{% endtab %}
{% endtabs %}

### peers bloc

{% tabs %}
{% tab title="peers\_bloc" %}

```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';
import 'package:huddle01_flutter_example/logic/blocs/consumers/consumers_bloc.dart';

part 'peers_event.dart';
part 'peers_state.dart';

class PeersBloc extends Bloc<dynamic, PeersState> {
  final ConsumersBloc consumersBloc;
  PeersBloc({required this.consumersBloc}) : super(PeersState());

  @override
  Stream<PeersState> mapEventToState(
    dynamic event,
  ) async* {
    if (event is PeerAdd) {
      yield* _mapPeerAddToState(event);
    } else if (event is PeerRemove) {
      yield* _mapPeerRemoveToState(event);
    } else if (event is PeerAddConsumer) {
      yield* _mapConsumerAddToState(event);
    } else if (event is PeerRemoveConsumer) {
      yield* _mapConsumerRemoveToState(event);
    }
  }

  Stream<PeersState> _mapPeerAddToState(PeerAdd event) async* {
    final Map<String, Peer> newPeers = Map<String, Peer>.of(state.peers);
    final Peer newPeer = Peer.fromMap(event.newPeer);
    newPeers[newPeer.id!] = newPeer;

    yield PeersState(peers: newPeers);
  }

  Stream<PeersState> _mapPeerRemoveToState(PeerRemove event) async* {
    final Map<String, Peer> newPeers = Map<String, Peer>.of(state.peers);
    newPeers.remove(event.peerId);

    yield PeersState(peers: newPeers);
  }

  Stream<PeersState> _mapConsumerAddToState(PeerAddConsumer event) async* {
    final Map<String, Peer> newPeers = Map<String, Peer>.of(state.peers);
    newPeers[event.peerId] = Peer.copy(newPeers[event.peerId]);
    newPeers[event.peerId]!.consumers.add(event.consumerId);

    yield PeersState(peers: newPeers);
  }

  Stream<PeersState> _mapConsumerRemoveToState(
      PeerRemoveConsumer event) async* {
    final Map<String, Peer> newPeers = Map<String, Peer>.of(state.peers);
    newPeers[event.peerId]!
        .consumers
        .removeWhere
        .call((c) => c == event.consumerId);
    // final Map<String, Peer> newPeers = state.peers.map((key, value) {
    //   if (value.consumers.contains(event.consumerId)) {
    //     return MapEntry(key, Peer.copy(
    //       value,
    //       consumers: value
    //           .consumers
    //           .where((c) => c != event.consumerId)
    //           .toList(),
    //     ));
    //   }
    //   return MapEntry(key, value);
    // });

    yield PeersState(peers: newPeers);
  }
}

```

{% endtab %}

{% tab title="peer\_state.dart" %}

```dart
part of 'peers_bloc.dart';

class PeersState extends Equatable {
  final Map<String, Peer> peers;

  const PeersState({this.peers = const <String, Peer>{}});

  @override
  List<Object> get props => [peers];
}

```

{% endtab %}

{% tab title="peers\_event.dart" %}

```dart
part of 'peers_bloc.dart';

abstract class PeersEvent extends Equatable {
  const PeersEvent();
}

class PeerAdd extends PeersEvent {
  final Map<String, dynamic> newPeer;

  const PeerAdd({required this.newPeer});

  @override
  List<Object> get props => [newPeer];
}

class PeerAddConsumer extends PeersEvent {
  final String consumerId;
  final String peerId;

  const PeerAddConsumer({required this.consumerId, required this.peerId});

  @override
  List<Object> get props => [consumerId, peerId];
}

class PeerRemoveConsumer extends PeersEvent {
  final String consumerId;
  final String peerId;

  const PeerRemoveConsumer({required this.consumerId, required this.peerId});

  @override
  List<Object> get props => [consumerId, peerId];
}

class PeerRemove extends PeersEvent {
  final String peerId;

  const PeerRemove({required this.peerId});

  @override
  List<Object> get props => [peerId];
}

```

{% endtab %}
{% endtabs %}

### media devices bloc

{% tabs %}
{% tab title="media\_devices\_bloc.dart" %}

```dart
import 'dart:async';
import 'dart:developer';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';

part 'media_devices_event.dart';
part 'media_devices_state.dart';

class MediaDevicesBloc extends Bloc<MediaDevicesEvent, MediaDevicesState> {
  MediaDevicesBloc() : super(MediaDevicesState());

  @override
  Stream<MediaDevicesState> mapEventToState(
    MediaDevicesEvent event,
  ) async* {
    if (event is MediaDeviceLoadDevices) {
      yield* _mapLoadDevicesToState(event);
    } else if (event is MediaDeviceSelectAudioInput) {
      yield* _mapSelectAudioInputToState(event);
    } else if (event is MediaDeviceSelectAudioOutput) {
      yield* _mapSelectAudioOutputToState(event);
    } else if (event is MediaDeviceSelectVideoInput) {
      yield* _mapSelectVideoInputToState(event);
    }
  }

  Stream<MediaDevicesState> _mapSelectAudioInputToState(
      MediaDeviceSelectAudioInput event) async* {
    yield state.copyWith(
      selectedAudioInput: event.device,
    );
  }

  Stream<MediaDevicesState> _mapSelectAudioOutputToState(
      MediaDeviceSelectAudioOutput event) async* {
    yield state.copyWith(
      selectedAudioOutput: event.device,
    );
  }

  Stream<MediaDevicesState> _mapSelectVideoInputToState(
      MediaDeviceSelectVideoInput event) async* {
    yield state.copyWith(
      selectedVideoInput: event.device,
    );
  }

  Stream<MediaDevicesState> _mapLoadDevicesToState(
      MediaDeviceLoadDevices event) async* {
    try {
      final List<MediaDeviceInfo> devices =
          await navigator.mediaDevices.enumerateDevices();
      final List<MediaDeviceInfo> audioInputs = [];
      final List<MediaDeviceInfo> audioOutputs = [];
      final List<MediaDeviceInfo> videoInputs = [];

      devices.forEach((device) {
        switch (device.kind) {
          case 'audioinput':
            audioInputs.add(device);
            break;
          case 'audiooutput':
            audioOutputs.add(device);
            break;
          case 'videoinput':
            videoInputs.add(device);
            break;
          default:
            break;
        }
      });
      MediaDeviceInfo? selectedAudioInput;
      MediaDeviceInfo? selectedAudioOutput;
      MediaDeviceInfo? selectedVideoInput;
      if (audioInputs.isNotEmpty) {
        selectedAudioInput = audioInputs.first;
      }
      if (audioOutputs.isNotEmpty) {
        selectedAudioOutput = audioOutputs.first;
      }
      if (videoInputs.isNotEmpty) {
        selectedVideoInput = videoInputs.first;
      }

      yield MediaDevicesState(
        audioInputs: audioInputs,
        audioOutputs: audioOutputs,
        videoInputs: videoInputs,
        selectedAudioInput: selectedAudioInput,
        selectedAudioOutput: selectedAudioOutput,
        selectedVideoInput: selectedVideoInput,
      );
    } catch (e) {
      log(e.toString());
    }
  }
}

```

{% endtab %}

{% tab title="media\_devices\_state.dart" %}

```dart
part of 'media_devices_bloc.dart';

class MediaDevicesState extends Equatable {
  final List<MediaDeviceInfo> audioInputs;
  final List<MediaDeviceInfo> audioOutputs;
  final List<MediaDeviceInfo> videoInputs;
  final MediaDeviceInfo? selectedAudioInput;
  final MediaDeviceInfo? selectedAudioOutput;
  final MediaDeviceInfo? selectedVideoInput;

  const MediaDevicesState({
    this.audioInputs = const [],
    this.audioOutputs = const [],
    this.videoInputs = const [],
    this.selectedAudioInput,
    this.selectedAudioOutput,
    this.selectedVideoInput,
  });

  @override
  List<Object> get props => [
        audioInputs,
        audioOutputs,
        videoInputs,
        if (selectedAudioInput != null) selectedAudioInput!,
        if (selectedAudioOutput != null) selectedAudioOutput!,
        if (selectedVideoInput != null) selectedVideoInput!,
      ];

  MediaDevicesState copyWith({
    List<MediaDeviceInfo>? audioInputs,
    List<MediaDeviceInfo>? audioOutputs,
    List<MediaDeviceInfo>? videoInputs,
    MediaDeviceInfo? selectedAudioInput,
    MediaDeviceInfo? selectedAudioOutput,
    MediaDeviceInfo? selectedVideoInput,
  }) {
    return MediaDevicesState(
      audioInputs: audioInputs != null
          ? audioInputs
          : List<MediaDeviceInfo>.of(this.audioInputs),
      audioOutputs: audioOutputs != null
          ? audioOutputs
          : List<MediaDeviceInfo>.of(this.audioOutputs),
      videoInputs: videoInputs != null
          ? videoInputs
          : List<MediaDeviceInfo>.of(this.videoInputs),
      selectedAudioInput: selectedAudioInput != null
          ? selectedAudioInput
          : this.selectedAudioInput,
      selectedAudioOutput: selectedAudioOutput != null
          ? selectedAudioOutput
          : this.selectedAudioOutput,
      selectedVideoInput: selectedVideoInput != null
          ? selectedVideoInput
          : this.selectedVideoInput,
    );
  }
}

```

{% endtab %}

{% tab title="media\_devices\_event.dart" %}

```dart
part of 'media_devices_bloc.dart';

abstract class MediaDevicesEvent extends Equatable {
  const MediaDevicesEvent();
}

class MediaDeviceLoadDevices extends MediaDevicesEvent {
  @override
  List<Object> get props => [];
}

class MediaDeviceSelectAudioInput extends MediaDevicesEvent {
  final MediaDeviceInfo device;

  const MediaDeviceSelectAudioInput(this.device);

  @override
  List<Object> get props => [];
}

class MediaDeviceSelectAudioOutput extends MediaDevicesEvent {
  final MediaDeviceInfo? device;

  const MediaDeviceSelectAudioOutput(this.device);

  @override
  List<Object> get props => [];
}

class MediaDeviceSelectVideoInput extends MediaDevicesEvent {
  final MediaDeviceInfo device;

  const MediaDeviceSelectVideoInput(this.device);

  @override
  List<Object> get props => [];
}

class MediaDeviceSelectVideoOut extends MediaDevicesEvent {
  final MediaDeviceInfo device;

  const MediaDeviceSelectVideoOut(this.device);

  @override
  List<Object> get props => [];
}

```

{% endtab %}
{% endtabs %}

### me bloc

{% tabs %}
{% tab title="me\_bloc.dart" %}

```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

part 'me_event.dart';
part 'me_state.dart';

class MeBloc extends Bloc<MeEvent, MeState> {
  MeBloc({required String id, required String displayName})
      : super(MeState(
          webcamInProgress: false,
          shareInProgress: false,
          id: id,
          displayName: displayName,
        ));

  @override
  Stream<MeState> mapEventToState(
    MeEvent event,
  ) async* {
    if (event is MeSetWebcamInProgress) {
      yield* _mapMeSetWebCamInProgressToState(event);
    }
  }

  Stream<MeState> _mapMeSetWebCamInProgressToState(
      MeSetWebcamInProgress event) async* {
    yield MeState.copy(state, webcamInProgress: event.progress);
  }
}

```

{% endtab %}

{% tab title="me\_state.dart" %}

```dart
part of 'me_bloc.dart';

class MeState extends Equatable {
  final String displayName;
  final String id;
  final bool shareInProgress;
  final bool webcamInProgress;

  const MeState({
    required this.displayName,
    required this.id,
    required this.shareInProgress,
    required this.webcamInProgress,
  });

  static MeState copy(
    MeState old, {
    String? displayName,
    String? id,
    bool? shareInProgress,
    bool? webcamInProgress,
  }) {
    return MeState(
      displayName: displayName ?? old.displayName,
      id: id ?? old.id,
      shareInProgress:
          shareInProgress != null ? shareInProgress : old.shareInProgress,
      webcamInProgress:
          webcamInProgress != null ? webcamInProgress : old.webcamInProgress,
    );
  }

  @override
  List<Object> get props => [
        displayName,
        id,
        shareInProgress,
        webcamInProgress,
      ];
}

```

{% endtab %}

{% tab title="me\_event.dart" %}

```dart
part of 'me_bloc.dart';

abstract class MeEvent extends Equatable {
  const MeEvent();
}

class MeSetWebcamInProgress extends MeEvent {
  final bool progress;

  const MeSetWebcamInProgress({required this.progress});

  @override
  List<Object> get props => [];
}

```

{% endtab %}
{% endtabs %}

### consumers bloc

{% tabs %}
{% tab title="consumers\_bloc.dart" %}

```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:huddle01_flutter/huddle01_flutter.dart';

part 'consumers_event.dart';
part 'consumers_state.dart';

class ConsumersBloc extends Bloc<ConsumersEvent, ConsumersState> {
  StreamController<ConsumersEvent>? subs;
  ConsumersBloc() : super(ConsumersState()) {
    subs = StreamController<ConsumersEvent>();
  }

  @override
  Stream<ConsumersState> mapEventToState(
    ConsumersEvent event,
  ) async* {
    if (event is ConsumerAdd) {
      yield* _mapConsumersAddToState(event);
      subs?.add(event);
    } else if (event is ConsumerRemove) {
      yield* _mapConsumersRemoveToState(event);
      subs?.add(event);
    } else if (event is ConsumerResumed) {
      yield* _mapConsumerResumedToState(event);
    } else if (event is ConsumerPaused) {
      yield* _mapConsumerPausedToState(event);
    }
  }

  Stream<ConsumersState> _mapConsumersAddToState(ConsumerAdd event) async* {
    final Map<String, Consumer> newConsumers =
        Map<String, Consumer>.of(state.consumers);
    final Map<String, RTCVideoRenderer> newRenderers =
        Map<String, RTCVideoRenderer>.of(state.renderers);
    newConsumers[event.consumer.id] = event.consumer;
    if (event.consumer.kind == 'video') {
      newRenderers[event.consumer.id] = RTCVideoRenderer();
      await newRenderers[event.consumer.id]!.initialize();
      newRenderers[event.consumer.id]!.srcObject =
          newConsumers[event.consumer.id]!.stream;
    }

    yield ConsumersState(consumers: newConsumers, renderers: newRenderers);
  }

  Stream<ConsumersState> _mapConsumersRemoveToState(
      ConsumerRemove event) async* {
    final Map<String, Consumer> newConsumers =
        Map<String, Consumer>.of(state.consumers);
    final Map<String, RTCVideoRenderer> newRenderers =
        Map<String, RTCVideoRenderer>.of(state.renderers);
    newConsumers.remove(event.consumerId);
    await newRenderers[event.consumerId]?.dispose();
    newRenderers.remove(event.consumerId);

    yield ConsumersState(consumers: newConsumers, renderers: newRenderers);
  }

  Stream<ConsumersState> _mapConsumerResumedToState(
      ConsumerResumed event) async* {
    final Map<String, Consumer> newConsumers =
        Map<String, Consumer>.of(state.consumers);
    newConsumers[event.consumerId]?.resume();

    yield ConsumersState(consumers: newConsumers, renderers: state.renderers);
  }

  Stream<ConsumersState> _mapConsumerPausedToState(
      ConsumerPaused event) async* {
    final Map<String, Consumer> newConsumers =
        Map<String, Consumer>.of(state.consumers);
    newConsumers[event.consumerId]?.pause();

    yield ConsumersState(consumers: newConsumers, renderers: state.renderers);
  }

  @override
  Future<void> close() async {
    await subs?.close();
    for (var r in state.renderers.values) {
      await r.dispose();
    }
    return super.close();
  }
}

```

{% endtab %}
{% endtabs %}

For any help, reach out to us on Slack. We are available 24\*7 at: [Huddle01 Community](https://bit.ly/3AsIsT7).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huddle01-2.gitbook.io/huddle01/flutter-sdk/demo-app.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
