Skip to main content
Version: 1.0

1-to-1 video call

This page provides a code example for implementing a 1-to-1 video call.

Prerequisites

Before you begin, you must do the following:

Considerations for implementing a 1-to-1 video call

To receive call notifications on the callee side, you must implement a notification system or integrate an external push notification system such as Apple Push Notification service (APNs) or Firebase Cloud Messaging (FCM).

You also need to know what information must be delivered to the callee. Refer to Role of the app server, which describes cc_param, the data that must be delivered by your application.

After calling makeCall() or verifyCall(), you must check the reason property of the returned PlanetKitMakeCallResult or PlanetKitVerifyCallResult.

  • If the reason is PlanetKitStartFailReason.none, it means success.
  • Otherwise, it means failure and you must take an appropriate action based on the reason.

Request device permissions

Use Dart permission_handler package to request microphone, phone, Bluetooth connection, and camera permission.

import 'package:permission_handler/permission_handler.dart';
final status = await [Permission.microphone, Permission.phone, Permission.bluetoothConnect, Permission.camera].request();

Prepare variables

Prepare variables for key properties and event handlers.

  • PlanetKitCallEventHandler is used to handle status change events of a 1-to-1 call. For more information, see 1-to-1 call flow.
  • PlanetKitMyMediaStatusHandler is used to handle the local user's media status change events. You can update the local user's UI based on these events, such as when the mic becomes muted or unmuted, when the audio description is updated, or when the video status is updated.
final String _myUserId = "test user id";
final String _serviceId = "test service id";
PlanetKitCall? _call;

bool isMyAudioMuted = false;
bool isMyVideoPaused = false;
bool isPeerMicMuted = false;
bool isPeerVideoPaused = false;
bool isVideoEnabled = false;

int volume = 0;

final _eventHandler = PlanetKitCallEventHandler(
onWaitConnected: (call) => print("wait connected"),
onVerified: (call, peerUseResponderPreparation) => print("verified")),
onConnected: (call, isInResponderPreparation, shouldFinishPreparation) => print("connected"),
onDisconnected: (call, reason, source, byRemote) => print("disconnected $reason"),
onPeerMicMuted: (call) => isPeerMicMute = true,
onPeerMicUnmuted: (call) => isPeerMicMute = false,
onPeerVideoPaused: (call, reason) => isPeerVideoPaused = true,
onPeerVideoResumed: (call) => isPeerVideoPaused = false,
onVideoEnabledByPeer: (call) {
isVideoEnabled = true;
isPeerVideoPaused = false;
},
onVideoDisabledByPeer: (call, reason) => isVideoEnabled = false);

final _myMediaStatusHandler = PlanetKitMyMediaStatusHandler(
onAudioDescriptionUpdate: (status, averageVolumeLevel) => volume = averageVolumeLevel,
onMicMute: (status) => isMyAudioMuted = true,
onMicUnmute: (status) => isMyAudioMuted = false,
onVideoStatusUpdate: (status, videoStatus) {
if (videoStatus.state == PlanetKitVideoState.enabled) {
isMyVideoPaused = false;
isVideoEnabled = true;
} else if (videoStatus.state == PlanetKitVideoState.disabled) {
isVideoEnabled = false;
} else if (videoStatus.state == PlanetKitVideoState.paused) {
isMyVideoPaused = true;
isVideoEnabled = true;
}
});

Make an outgoing call (caller side)

To make a call, call PlanetKitManager.instance.makeCall() with a PlanetKitMakeCallParam that you can create using PlanetKitMakeCallParamBuilder().

  • To make a video call, call setMediaType() of PlanetKitMakeCallParamBuilder with PlanetKitMediaType.audiovideo.
  • The caller's initial video state can be set with setInitialMyVideoState() of PlanetKitMakeCallParamBuilder.
    • The default value of the initialMyVideoState property is PlanetKitInitialMyVideoState.resume.
    • If the caller's initial video state is set to paused by calling setInitialMyVideoState() with PlanetKitInitialMyVideoState.pause, the caller's video is not transmitted. To transmit the caller's video after the call is connected, call resumeMyVideo().
Future<bool> makeCall(String peerId, String accessToken, PlanetKitInitialMyVideoState initialMyVideoState) async {
final builder = PlanetKitMakeCallParamBuilder()
.setMyUserId(_myUserId)
.setMyServiceId(_serviceId)
.setPeerUserId(peerId)
.setPeerServiceId(_serviceId)
.setAccessToken(accessToken)
.setMediaType(PlanetKitMediaType.audiovideo)
.setInitialMyVideoState(initialMyVideoState);

PlanetKitMakeCallParam? param;
try {
param = builder.build();
} catch (error) {
print("failed to build make call param $error");
return false;
}

final result = await PlanetKitManager.instance.makeCall(param, _eventHandler);

if (result.reason != PlanetKitStartFailReason.none) {
print("make call failed. reason: $result.reason");
return false;
}

// Store call instance and set MyMediaStatusHandler.
_call = result.call;
_call?.myMediaStatus.setHandler(_myMediaStatusHandler);
return true;
}

Respond to an incoming call (callee side)

When you receive a push message from the app server, you must parse the cc_param from the message and create a ccParam. This data is a required argument that must be passed when responding to a call.

To respond to a call, call PlanetKitManager.instance.verifyCall() with a proper PlanetKitVerifyCallParam. To build a PlanetKitVerifyCallParam, use PlanetKitVerifyCallParamBuilder().

Future<bool> verifyCall(String ccParamString) async {
final ccParam = await PlanetKitCcParam.createCcParam(ccParamString);
if (ccParam == null) {
print("failed to create ccparam string $ccParamString");
return false;
}

if (ccParam.mediaType == PlanetKitMediaType.audiovideo ||
ccParam.mediaType == PlanetKitMediaType.video) {
isVideoEnabled = true;
}

final builder = PlanetKitVerifyCallParamBuilder()
.setMyUserId(_myUserId)
.setMyServiceId(_serviceId)
.setCcParam(ccParam);

PlanetKitVerifyCallParam? param;
try {
param = builder.build();
} catch (error) {
print("failed to build verify call param $error");
return false;
}

final result =
await PlanetKitManager.instance.verifyCall(param, _eventHandler);

if (result.reason != PlanetKitStartFailReason.none) {
print("make call result $result.reason");
return false;
}

// Store call instance and set MyMediaStatusHandler.
_call = result.call;
_call?.myMediaStatus.setHandler(_myMediaStatusHandler);
return true;
}

Accept an incoming call (callee side)

In general, the recipient needs time to decide whether to accept a new call after responding to the call. To accept a call, call PlanetKitCall.acceptCall().

  • The callee's initial video state can be set with the initialMyVideoState parameter of acceptCall().
    • The default value of the initialMyVideoState parameter is PlanetKitInitialMyVideoState.resume.
    • If the callee's initial video state is set to paused by passing PlanetKitInitialMyVideoState.pause, the callee's video is not transmitted. To transmit the callee's video after the call is connected, call resumeMyVideo().
Tip

The _call variable is a PlanetKitCall instance that has been verified by verifyCall().

Future<void> acceptCall(PlanetKitInitialMyVideoState initialMyVideoState) async {
return await _call?.acceptCall(false, initialMyVideoState) ?? false;
}

Render video

To render the local video (local user's video) and the remote video (peer's video), you must use PlanetKitVideoViewBuilder to create PlanetKitVideoView and add it to PlanetKitCall.

The following code assumes that a PlanetKitCall instance has been created with video configuration.

class CallVideoView extends StatelessWidget {
const CallVideoView({super.key, this.call});
final PlanetKitCall? call;


Widget build(BuildContext context) {
final myVideoView = PlanetKitVideoViewBuilder.instance.create();
final peerVideoView = PlanetKitVideoViewBuilder.instance.create();

myVideoView.onCreate.listen((id) {
call?.addMyVideoView(id);
});

myVideoView.onDispose.listen((id) {
call?.removeMyVideoView(id);
});

peerVideoView.onCreate.listen((id) {
call?.addPeerVideoView(id);
});

peerVideoView.onDispose.listen((id) {
call?.removePeerVideoView(id);
});


return Row(
children: [
myVideoView,
peerVideoView
]
);
}
}

PlanetKitCall.addMyVideoView(viewId) must be called when the PlanetKitVideoView for the local user's view has been created and PlanetKitCall.removeMyVideoView(viewId) must be called when the view has been disposed.

PlanetKitCall.addPeerVideoView(viewId) must be called when the PlanetKitVideoView for the peer's view has been created and PlanetKitCall.removePeerVideoView(viewId) must be called when the view has been disposed.

End a call

To end a call, call endCall().

Future<bool> endCall() async {
return await _call?.endCall() ?? false;
}

Additional implementation for CallKit integration (Optional)

Note

This step applies to iOS applications only.

When using the CallKit framework, you must set callKitType to PlanetKitCallKitType.user in PlanetKitMakeCallParam and PlanetKitVerifyCallParam.

Then, you must call PlanetKitCall.notifyCallKitAudioActivation() on CXProviderDelegate.provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) to ensure audio activation in PlanetKit.