Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The unmute event is called whenever the speaker is toggled, but the microphone remains muted. #220

Open
Punit30 opened this issue Feb 19, 2024 · 4 comments

Comments

@Punit30
Copy link

Punit30 commented Feb 19, 2024

Issue Summary

I am developing a custom UI for calling, and I'm facing an issue where the unmute event is triggered when toggling the speaker, but the call remains muted. I am unsure why this is happening. Additionally, when checking with TwilioVoice.instance.call.isMuted(), it returns a false value after the speaker is toggled. Also, the function and Native Call UI of android is not in sync if trigger on native call UI it does not reflect on app UI. I am seeking assistance to understand and resolve these issues.

Steps to Reproduce

  1. Below it he code for my call ui and twiliovoice configuration:
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_libphonenumber/flutter_libphonenumber.dart';
import 'package:ionicons/ionicons.dart';
import 'package:softphone_mobile_app/screens/keypad/controller/keypad_controller.dart';
import 'package:softphone_mobile_app/screens/keypad/widgets/caller_info.dart';
import 'package:softphone_mobile_app/screens/keypad/widgets/circular_button.dart';
import 'package:softphone_mobile_app/screens/keypad/widgets/dial_pad_grid.dart';
import 'package:softphone_mobile_app/screens/keypad/widgets/phone_input_field.dart';
import 'package:softphone_mobile_app/themes/pallete.dart';
import 'package:twilio_voice/twilio_voice.dart';

class OutGoingView extends StatefulWidget {
  static route() =>
      MaterialPageRoute(builder: (context) => const OutGoingView());

  const OutGoingView({super.key});

  @override
  State<OutGoingView> createState() => _OutGoingViewState();
}

class _OutGoingViewState extends State<OutGoingView> {
  TextEditingController controller = TextEditingController();
  KeypadController keypadController = KeypadController();

  late final StreamSubscription<CallEvent> _subscription;
  final _events = <CallEvent>[];

  bool onMute = false;
  set stateMute(bool value) {
    setState(() {
      onMute = value;
    });
  }

  bool onHold = false;
  set stateHold(bool value) {
    setState(() {
      onHold = value;
    });
  }

  bool onSpeaker = false;
  set stateSpeaker(bool value) {
    setState(() {
      onSpeaker = value;
    });
  }

  bool onBluetooth = false;
  set stateBluetooth(bool value) {
    setState(() {
      onBluetooth = value;
    });
  }

  bool isKeypadActive = false;
  set stateKeypadActive(bool value) {
    setState(() {
      isKeypadActive = value;
    });
  }

  final _tv = TwilioVoice.instance;
  bool activeCall = false;

  @override
  void initState() {
    super.initState();
    _subscription = _tv.callEventsListener.listen((event) {
      _events.add(event);
      switch (event) {
        case CallEvent.unmute:
          print(" +++++++++++ unmute ++++++++");
          stateMute = false;
          break;
        case CallEvent.mute:
          print("---------- mute ---------");
          stateMute = true;
          break;
        case CallEvent.unhold:
        case CallEvent.hold:
        case CallEvent.speakerOn:
        case CallEvent.speakerOff:
        case CallEvent.bluetoothOn:
        case CallEvent.bluetoothOff:
          _updateStates();
          break;

        case CallEvent.connected:
          activeCall = true;
          _updateStates();
          break;

        case CallEvent.callEnded:
          activeCall = false;
          break;

        case CallEvent.incoming:
        case CallEvent.ringing:
        case CallEvent.declined:
        case CallEvent.answer:
        case CallEvent.missedCall:
        case CallEvent.returningCall:
        case CallEvent.reconnecting:
        case CallEvent.reconnected:
        case CallEvent.log:
        case CallEvent.permission:
          break;
      }
    });
    _updateStates();
  }

  void _updateStates() {
    // get all states from call
    _tv.call.isMuted().then((value) => stateMute = value ?? false);
    _tv.call.isHolding().then((value) => stateHold = value ?? false);
    _tv.call.isOnSpeaker().then((value) => stateSpeaker = value ?? false);
    _tv.call.isBluetoothOn().then((value) => stateBluetooth = value ?? false);
  }

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

  @override
  Widget build(BuildContext context) {
    double screenHeight = MediaQuery.of(context).size.height;

    return PopScope(
      canPop: false,
      child: Scaffold(
        body: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment(0.07, -1.00),
              end: Alignment(-0.7, 1),
              colors: [
                Pallete.darkNavyBlue,
                Pallete.deepNavyBlue,
              ],
            ),
          ),
          child: Padding(
            padding: EdgeInsets.only(
              top: screenHeight * 0.06,
              bottom: screenHeight * 0.04,
              left: 30,
              right: 30,
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                CallerInfo(
                  callType: _tv.call.activeCall?.callDirection ==
                          CallDirection.incoming
                      ? "INCOMING"
                      : "OUTGOING",
                  callerName: "",
                  callerNumber: formatNumberSync(
                    _tv.call.activeCall?.callDirection == CallDirection.incoming
                        ? _tv.call.activeCall!.from
                        : _tv.call.activeCall!.to,
                  ),
                  callerProfile: "",
                ),
                isKeypadActive
                    ? Center(
                        child: Padding(
                          padding: const EdgeInsets.only(
                              left: 8.0, right: 8.0, top: 10),
                          child: PhoneInputField(
                            controller: controller,
                            color: Pallete.blueGrey,
                            fontWeight: FontWeight.w500,
                            textSize: 22.0,
                            showPrefixIcon: false,
                          ),
                        ),
                      )
                    : Container(),
                isKeypadActive
                    ? Expanded(
                        child: DialPadGrid(
                        isDialPad: false,
                        aspectFactor: 3.1,
                        itemCount: 12,
                        primaryTextColor: Pallete.white,
                        secondaryTextColor: Pallete.slateBlue,
                        buttonColor: Pallete.lightSteelBlue,
                        controller: controller,
                        onKeyPress: (value) async {
                          print(value);
                          await _tv.call
                              .sendDigits(value)
                              .then((value) => print("digit sent succefully"));
                        },
                      ))
                    : Container(),
                const SizedBox(height: 24.0),
                Column(
                  children: [
                    !isKeypadActive
                        ? Container(
                            margin: const EdgeInsets.only(bottom: 24),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceAround,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                CircularButton(
                                    onPressed: () =>
                                        _tv.call.toggleSpeaker(!onSpeaker),
                                    icon: Ionicons.volume_high,
                                    color: !onSpeaker
                                        ? Pallete.white
                                        : Pallete.darkIndigo,
                                    backgroundColor: !onSpeaker
                                        ? Pallete.lightSteelBlue
                                        : Pallete.white),
                                CircularButton(
                                    onPressed: () {
                                      setState(() {
                                        if (!isKeypadActive) {
                                          setState(() {
                                            isKeypadActive = true;
                                            controller =
                                                TextEditingController(); // Create a new controller
                                          });
                                        } else {
                                          stateKeypadActive = false;
                                        }
                                      });
                                    },
                                    icon: Ionicons.keypad,
                                    backgroundColor: Pallete.lightSteelBlue),
                                CircularButton(
                                  onPressed: () => _tv.call.toggleMute(!onMute),
                                  icon:
                                      onMute ? Ionicons.mic_off : Ionicons.mic,
                                  backgroundColor: onMute
                                      ? Pallete.lightSteelBlue
                                      : Pallete.white,
                                  color: onMute
                                      ? Pallete.white
                                      : Pallete.darkIndigo,
                                ),
                              ],
                            ),
                          )
                        : Container(),
                    Padding(
                      padding: const EdgeInsets.only(left: 8.0, right: 8.0),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          isKeypadActive
                              ? Container(
                                  width: 80,
                                  padding: const EdgeInsets.all(0),
                                  child: TextButton(
                                    onPressed: () {
                                      setState(() {
                                        isKeypadActive = false;
                                      });
                                    },
                                    style: TextButton.styleFrom(
                                        padding: const EdgeInsets.all(0)),
                                    child: const Text(
                                      "Hide keypad",
                                      style: TextStyle(
                                          fontFamily: 'Figtree',
                                          fontSize: 14.0,
                                          fontWeight: FontWeight.w400,
                                          letterSpacing: 0.02,
                                          color: Pallete.lightBlueGray),
                                    ),
                                  ),
                                )
                              : Container(),
                          CircularButton(
                            onPressed: () {
                              final activeCall =
                                  TwilioVoice.instance.call.activeCall;
                              if (activeCall != null) {
                                TwilioVoice.instance.call.hangUp();
                              }
                            },
                            icon: Ionicons.close,
                            backgroundColor: Pallete.danger400,
                          ),
                          isKeypadActive
                              ? SizedBox(
                                  width: 80,
                                  child: IconButton(
                                    onPressed: () {
                                      keypadController.onInputErase(
                                          controller, false);
                                    },
                                    icon: const Icon(
                                      Ionicons.backspace,
                                      color: Pallete.blueGrey500,
                                      size: 32,
                                    ),
                                  ),
                                )
                              : Container()
                        ],
                      ),
                    ),
                  ],
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
I don't know if this is a bug or not but I'd like to know what changes one can make to have this thing work properly.

@Dev-Devarsh
Copy link

Any updates on this issue??

@Punit30
Copy link
Author

Punit30 commented Feb 21, 2024

Hi @Dev-Devarsh,
I am no longer syncing mute with the Twilio Voice package. Instead, I am managing mute at the widget level. If the "call.toggleMute()" function is successful, I handle mute accordingly. Similarly, I have applied the same approach for hold. Previously, checking for the isHold function resulted in triggering the hold/unhold event on iOS devices, leading to an infinite loop that persists until the call ends.

@Dev-Devarsh
Copy link

I am unable to understand managing mute at the widget level. Can please explain in brief..!

@Punit30
Copy link
Author

Punit30 commented Feb 25, 2024

I am no longer using "TwilioVoice.instance.call.isMuted()" function or check mute/unmute call event to get mute status. Instead, Directly checking success message from "TwilioVoice.instance.call.toggleMute()" and toggle isMute variable inside the widget itself.

await TwilioVoice.instance.call.toogleMute().then((value) {
    if(value == true) {
         setState(() {
              isMute = !isMute;
         };
    } else {
         showSnackBar("Unable to mute/unmute the call. Please try again.", "error", context);
    }
});

Hope the above code would be helpful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants