Customize agora video call UI in flutter

693 views Asked by At

my video call output

enter image description here

when logged another person to this video call then shows like this.But I want when someone logged to video call then my video should be small. like this.

enter image description here

and also when click my video then my video should be big and another person's video should be small. how to do that ? I couldn't find the any documentation how to do that

Code video call ui

// video view
  Widget _viewRows() {
    final views = _getRenderViews();
    switch (views.length) {
      case 1:
        return Column(
          children: <Widget>[_videoView(views[0])],
        );
      case 2:
        return Column(
          children: <Widget>[
            _expandedVideoRow([views[0]]),
            _expandedVideoRow([views[1]])
          ],
        );
      case 3:
        return Column(
          children: <Widget>[
            _expandedVideoRow(views.sublist(0, 2)),
            _expandedVideoRow(views.sublist(2, 3))
          ],
        );
      case 4:
        return Column(
          children: <Widget>[
            _expandedVideoRow(views.sublist(0, 2)),
            _expandedVideoRow(views.sublist(2, 4))
          ],
        );
      default:
    }
    return Container();
  }

How customize the video UI like as I mentioned?

error

enter image description here

enter image description here

1

There are 1 answers

5
Ramji On BEST ANSWER

To create the layout which you want, edit _viewRow(CallNotifier notifier) and _expandedVideoRow(List views) function with following code : -

Widget _viewRows(CallNotifier notifier) : -

case 2:
    return Container(
            margin: EdgeInsets.only(top: 100, bottom: 100),
            child: Stack(
              children: [
                _expandedVideoRow([views[secondScreen]]),
                Align(
                  alignment: Alignment.bottomRight,
                  child: Padding(
                    padding: const EdgeInsets.only(right: 10, bottom: 10),
                    child: GestureDetector(
                      onTap: () {
                        tempSwap = firstScreen;
                        firstScreen = secondScreen;
                        secondScreen = tempSwap;
                        setState(() {});
                      },
                      child: SizedBox(
                          height: 200,
                          width: 100,
                          child: _expandedVideoRow([views[firstScreen]])),
                    ),
                  ),
                ),
              ],
            ));

Above code contains _expandedVideoRow([views[secondScreen]]), which is just a simple Expandable Container and we are passing the index of the screen as a parameter. In our case, there are 2 screens hence 2 index that is 0 and 1. I have declared three integer variables here, int firstScreen = 0, int secondScreen = 1 and int tempSwap = 0. The second _expandedVideoRow([views[firstScreen]]) is wrapped by GesutreDector, so when the user taps on that screen the indexes of the variable are swapped which results in swapping the screens, SizedBox to reduce its width and height and Align widget to give the desired position to the second screen.

Widget _expandedVideoRow(List views) : -

Widget _expandedVideoRow(List<Widget> views) {
    final wrappedViews = views.map<Widget>(_videoView).toList();
    return Row(
      children: wrappedViews,
    );
}

Remove the Expanded widget that wraps the row like the above code because we can't use the Expanded under the Stack widget.

If you wish to change the bottom icons, then change _toolbar(CallNotifier notifier) function according to your need.

Widget _toolbar(CallNotifier notifier) {
return Container(
  alignment: Alignment.bottomCenter,
  padding: const EdgeInsets.symmetric(vertical: 20),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      RawMaterialButton(
        onPressed: () {
          _onToggleMute(notifier);
          setState(() {
            isMute = !isMute;
          });
        },
        child: Icon(
          isMute ? Icons.mic_off : Icons.mic,
          color: isMute ? Colors.white : Colors.teal,
          size: 20.0,
        ),
        shape: CircleBorder(),
        elevation: 2.0,
        fillColor: isMute ? Colors.teal : Colors.white,
        padding: const EdgeInsets.all(12.0),
      ),
      RawMaterialButton(
        onPressed: () => _onCallEnd(context),
        child: Icon(
          Icons.call_end,
          color: Colors.white,
          size: 20.0,
        ),
        shape: CircleBorder(),
        elevation: 2.0,
        fillColor: Colors.redAccent,
        padding: const EdgeInsets.all(15.0),
      ),
    ],
  ),
);
}

Code which I use in my app, full code : -

class _CallScreenState extends State<CallScreen> {
  double globalHeight;
  int firstScreen = 0;
  int secondScreen = 1;
  int tempSwap = 0;
  bool isMute = false;

  void initState() {
    super.initState();
  }

  List<Widget> _getRenderViews(CallNotifier model) {
    final List<StatefulWidget> list = [];
    list.add(RtcLocalView.SurfaceView());
    model.users
        .forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));
    return list;
  }

  Widget _videoView(view) {
    return Expanded(child: Container(child: view));
  }

  Widget _expandedVideoRow(List<Widget> views) {
    final wrappedViews = views.map<Widget>(_videoView).toList();
    return Row(
      children: wrappedViews,
    );
  }

  Widget _viewRows(CallNotifier notifier) {
    final views = _getRenderViews(notifier);
    switch (views.length) {
      case 1:
        return Container(
            margin: EdgeInsets.only(top: 100, bottom: 100),
            child: Column(
              children: <Widget>[_videoView(views[0])],
            ));
      case 2:
        return Container(
            margin: EdgeInsets.only(top: 100, bottom: 100),
            child: Stack(
              children: [
                _expandedVideoRow([views[secondScreen]]),
                Align(
                  alignment: Alignment.bottomRight,
                  child: Padding(
                    padding: const EdgeInsets.only(right: 10, bottom: 10),
                    child: GestureDetector(
                      onTap: () {
                        tempSwap = firstScreen;
                        firstScreen = secondScreen;
                        secondScreen = tempSwap;
                        setState(() {});
                      },
                      child: SizedBox(
                          height: 200,
                          width: 100,
                          child: _expandedVideoRow([views[firstScreen]])),
                    ),
                  ),
                ),
              ],
            ));

      default:
    }
    return Container();
  }

  Widget _toolbar(CallNotifier notifier) {
    return Container(
      alignment: Alignment.bottomCenter,
      padding: const EdgeInsets.symmetric(vertical: 20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          RawMaterialButton(
            onPressed: () {
              _onToggleMute(notifier);
              setState(() {
                isMute = !isMute;
              });
            },
            child: Icon(
              isMute ? Icons.mic_off : Icons.mic,
              color: isMute ? Colors.white : Colors.teal,
              size: 20.0,
            ),
            shape: CircleBorder(),
            elevation: 2.0,
            fillColor: isMute ? Colors.teal : Colors.white,
            padding: const EdgeInsets.all(12.0),
          ),
          RawMaterialButton(
            onPressed: () => _onCallEnd(context),
            child: Icon(
              Icons.call_end,
              color: Colors.white,
              size: 20.0,
            ),
            shape: CircleBorder(),
            elevation: 2.0,
            fillColor: Colors.redAccent,
            padding: const EdgeInsets.all(15.0),
          ),
        ],
      ),
    );
  }

  void _onCallEnd(BuildContext context) {
    Navigator.pop(context);
  }

  void _onToggleMute(CallNotifier notifier) {
    notifier.isMuted = notifier.isMuted;
    notifier.engine.muteLocalAudioStream(notifier.isMuted);
  }

  @override
  Widget build(BuildContext context) {
    return BaseWidget<CallNotifier>(
        model: CallNotifier(),
        onModelReady: (model) => model.init(widget.channelName, widget.token),
        builder: (context, notifier, child) {
          return Scaffold(
            backgroundColor: Colors.black,
            body: SafeArea(
              child: Stack(
                children: <Widget>[
                  _viewRows(notifier),
                  Align(
                      alignment: Alignment.bottomCenter,
                      child: _toolbar(notifier)),
                ],
              ),
            ),
          );
        });
  }
}