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.