Skip to content

Overview ​

  • File: lib/views/call_page.dart

The CallPage is a Flutter widget that manages video and audio calls in the HVC Mobile application. It provides functionality for initiating, managing, and participating in video calls, including features like screen sharing, chat, and peripheral device integration.

Class Structure ​

Class Definition ​

dart
class CallPage extends StatefulWidget {
  final CallDetails callDetails;
  final bool isEventOwner;
  final bool isGuest;
  final bool isRapidConnect;

  const CallPage({
    Key? key,
    required this.callDetails,
    required this.isEventOwner,
    this.isGuest = false,
    this.isRapidConnect = false,
  }) : super(key: key);

  @override
  State<CallPage> createState() => _CallPageState();
}

Controllers ​

dart
final _localVideoRenderer = RTCVideoRenderer();
final _remoteVideoRenderer = RTCVideoRenderer();
final pexipController = Get.put(PexipController());
final MeetingsController meetingsController = Get.find();
final MediaController mediaController = Get.find();
final ContactsController contactsController = Get.find();
final EventsController eventsController = Get.find();
final MessagesController messagesController = Get.find();
final RealTimeController realTimeController = Get.find();
final AuthController authController = Get.find();

State Variables ​

dart
final String _sessionId = const Uuid().v1();
final DateTime _sessionDate = DateTime.now();
final Rx<Orientation> ort = Orientation.portrait.obs;
RxBool isPortrait = true.obs;
RxString imageUrl = ''.obs;
late final UserData user;
int? selectedContact;
int meetingId = -1;

Dependencies ​

Required Packages ​

  • flutter/material.dart - Core Flutter UI components
  • get/get.dart - State management and routing
  • flutter_webrtc - WebRTC implementation
  • font_awesome_flutter - Icon library
  • onesignal_flutter - Push notifications
  • uuid - Unique ID generation
  • wakelock_plus - Screen wake lock
  • dropdown_search - Dropdown search functionality

Internal Dependencies ​

  • pexip_controller.dart - Pexip integration
  • media_controller.dart - Media handling
  • contacts_controller.dart - Contact management
  • events_controller.dart - Event management
  • messages_controller.dart - Message handling
  • real_time_controller.dart - Real-time communication
  • auth_controller.dart - Authentication
  • hlk_helpers/ - Custom helper utilities
  • models/ - Data models
  • shared/ - Shared constants and configurations
  • widgets/ - Custom widgets

State Management ​

Initialization ​

dart
@override
void initState() {
  defaultPeri = selectedPeripheralType.value;
  meetingsController.isRapidConnectPageOpened = false;
  OneSignal.User.addTagWithKey('mId', widget.callDetails.meetingId);
  isInCall.value = true;
  WidgetsBinding.instance.addObserver(this);
  initRenderer();
  super.initState();
  user = UserData.fromMap(userData);
  logInfo('callDetails from the cal or meeting page');
  logInfo(widget.callDetails.toMap());
  pexipController.initializePexip(widget.callDetails);
  pexipController.isCallOwner = widget.isEventOwner;
  messagesController.selectedGroupId.value = 0;
  messagesController.messages.clear();
  logInfo('messagesController.messages = ${messagesController.messages}');
  WakelockPlus.enable();
  // Additional initialization code...
}

Reactive State ​

dart
Obx(() => pexipController.isInRoom.value ? callView : preview)

Call Management ​

Call Initialization ​

dart
void _handleStartCall() async {
  await pexipController.start();
  realTimeController.sendState('call');
  await SystemChrome.setPreferredOrientations([]);
}

Call Termination ​

dart
onPressed: () {
  pexipController.stop();
  realTimeController.resetSocket();
  Get.back();
}

Participant Management ​

dart
participantWatcher = ever(pexipController.participants, (callback) {
  var me = pexipController.participants
      .firstWhereOrNull((e) => getIdFromTag(e.call_tag) == user.id);
  isMyVideoMutedByAdmin.value = getBoolean(me?.is_video_muted);
  isPinnedByAdmin.value = getNumber(me?.spotlight) != 0;
  isMyAudioMutedByAdmin.value =
      getString(me?.is_muted).containsIgnoreCase('yes');
  pexipController.pexipRole.value = me?.role ?? 'GUEST';
  // Additional participant management code...
});

UI Components ​

Video Renderers ​

dart
final localVideo = Obx(() => RTCVideoView(
      _localVideoRenderer,
      mirror: pexipController.isMainCamera.isFalse,
      objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
    ));

final remoteVideo = RTCVideoView(
  _remoteVideoRenderer,
  objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
);

Control Buttons ​

dart
RoundIconButton(
  icon: Icon(pexipController.isAudioEnable.value
      ? Icons.mic
      : Icons.mic_off),
  onPressed: () async {
    await pexipController.toggleAudio();
  },
  btnColor: kAppColorWhite,
  size: btnSize,
)

Chat Window ​

dart
ChatWindow(
  scrollController: messagesController.scrollController,
  showNotification: false,
  onNotificationChange: (show) {},
  messageList: messagesController.messages,
  width: getDisplayWidth(),
  height: getDisplayHeight(),
  sendMessage: (text) async {
    var msg = await realTimeController.callSendMessage(
      content: text,
      contentType: 'text',
      user: user,
      meetingId: meetingId,
      meetingParticipantId: selectedContact == null
          ? null
          : getNumber(selectedContact) as int,
    );
    messagesController.scrollToBottom();
    if (msg != null) {
      messagesController.selectedGroupId.value = msg.groupId;
      messagesController.addMessageToQueue(msg);
    }
  },
  // Additional chat window properties...
)

Media Handling ​

Audio Device Management ​

dart
void setDefault() async {
  var bluetooth = pexipController.outputAudios.firstWhereOrNull((e) =>
      e.label.containsIgnoreCase('blu') ||
      e.deviceId.containsIgnoreCase('blu'));
  var wiredHeadset = pexipController.outputAudios.firstWhereOrNull((e) =>
      e.label.containsIgnoreCase('wired') ||
      e.deviceId.containsIgnoreCase('headset'));
  if (bluetooth != null) {
    await Helper.selectAudioOutput(bluetooth.deviceId);
    pexipController.currentOutAudio.value = bluetooth;
  } else if (wiredHeadset != null) {
    await Helper.selectAudioOutput(wiredHeadset.deviceId);
    pexipController.currentOutAudio.value = wiredHeadset;
  } else {
    pexipController.currentOutAudio.value = pexipController.outputAudios
        .firstWhere((e) =>
            e.label.containsIgnoreCase('speaker') ||
            e.deviceId.containsIgnoreCase('speaker'));
  }
  setState(() {});
}

Camera Management ​

dart
Obx(
  () => pexipController.hasManyCameras.value
      ? RoundIconButton(
          icon: const Icon(Icons.flip_camera_ios),
          onPressed: () {
            pexipController.toggleCamera();
          },
          btnColor: kAppColorWhite,
        )
      : const SizedBox.shrink(),
)

Permission Handling ​

Permission Checks ​

dart
if (!hasPermission(HvcPermissions.TakePhoto)) {
  HlkDialog.showErrorSnackBar('Not permitted to perform action');
  return;
}

Permission-Protected Actions ​

  1. Taking photos
  2. Recording calls
  3. Managing participants

Peripheral Integration ​

Peripheral Management ​

dart
Future<void> _handlePeripheralChange(
    PeripheralType p, bool isSelected) async {
  var inputDeviceNames =
      pexipController.inputAudios.map((e) => e.label).toList();

  logInfo('inputDeviceNames = $inputDeviceNames');

  if (isSelected) {
    var res =
        await meetingsController.getPeripheralDevice(p.id, inputDeviceNames);

    if (res.success) {
      lastSelectedDevice = res.data;
      await pexipController.enableLowLevelSound(isSelected);

      audioDeviceId = pexipController.inputAudios
              .firstWhereOrNull((el) => el.label == res.data)
              ?.deviceId ??
          defaultAudioInId();
      logInfo('audioDeviceId = $audioDeviceId');
      if (audioDeviceId.isNotEmpty) {
        await Helper.selectAudioInput(audioDeviceId);
      }
      var ok = await realTimeController.startPeripheral(
        meetingId,
        lastSelectedDevice,
      );
      if (ok)
        HlkDialog.showSnackBar(
          title: 'Info',
          message: 'Peripheral enabled successfully',
          color: Colors.white30,
        );
    } else {
      HlkDialog.showErrorSnackBar(res.message ?? 'Failed to get device');
    }
  } else {
    // Disable peripheral code...
  }
  logInfo('audioDeviceId = $audioDeviceId');
  setState(() {});
}

Peripheral UI ​

dart
final peripheralsView = Container(
    color: kAppColorWhite,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        verticalSpace(0.2),
        Expanded(
          child: ListView.builder(
            padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
            itemCount: peripherals.length,
            itemBuilder: (BuildContext context, int index) {
              final p = peripherals[index];
              var isCurrent = p.id == selectedPeripheralType.value.id;
              final iconCode = int.tryParse('0x${p.iconCode}') ?? -1;
              return Card(
                shape: RoundedRectangleBorder(
                  side: BorderSide(color: Colors.black, width: 1),
                  borderRadius: BorderRadius.circular(5),
                ),
                child: RadioListTile<PeripheralType>(
                  toggleable: true,
                  title: Text(p.name),
                  secondary: FaIcon(FontAwesomeIcons.stethoscope,
                      color: kAppColorBlue),
                  value: p,
                  selected: isCurrent,
                  groupValue: selectedPeripheralType.value,
                  onChanged: (PeripheralType? value) async {
                    var isSelected = value != null;
                    selectedPeripheralType.value =
                        isSelected ? value : defaultPeri;
                    await _handlePeripheralChange(p, isSelected);
                  },
                ),
              );
            },
          ),
        ),
      ],
    ));

Released under the MIT License.