Knowledge Base
  • Welcome to Huddle01 SYNC
  • Web-SDK
    • Client
    • Demo App
  • Flutter SDK
    • Client
    • Demo App
  • Android-Native
    • Client
    • Demo App
  • React-Native
    • Client
    • Demo App
Powered by GitBook
On this page
  • GitHub Link:
  • Folder Structure
  • main.dart
  • UI and screens
  • Logic/Blocs
  • room bloc
  • producers bloc
  • peers bloc
  • media devices bloc
  • me bloc
  • consumers bloc

Was this helpful?

  1. Flutter SDK

Demo App

Here is the code for a sample application made using this package.

PreviousClientNextClient

Last updated 3 years ago

Was this helpful?

GitHub Link:

Folder Structure

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

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

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(),
        ],
      ),
    );
  }
}
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)),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}
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;
                  }),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}
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');
  }
}
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),
                  ),
                ],
              ))
        ],
      ),
    );
  }
}
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(),
    );
  }
}
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));
      },
    );
  }
}
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));
      },
    );
  }
}
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();
  }
}

Logic/Blocs

room bloc

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);
  }
}
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!];
}
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];
}

producers bloc

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;
    }
  }
}
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!
      ];
}
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];
}

peers bloc

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);
  }
}
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];
}
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];
}

media devices bloc

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());
    }
  }
}
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,
    );
  }
}
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 => [];
}

me bloc

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);
  }
}
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,
      ];
}
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 => [];
}

consumers bloc

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();
  }
}

For any help, reach out to us on Slack. We are available 24*7 at: .

https://github.com/Huddle-01/huddle01-flutter-example
Huddle01 Community