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:
- Initialize PlanetKit.
- Get a proper access token.
- Check the overall API usage flow in 1-to-1 call flow.
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
isPlanetKitStartFailReason.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()
ofPlanetKitMakeCallParamBuilder
withPlanetKitMediaType.audiovideo
. - The caller's initial video state can be set with
setInitialMyVideoState()
ofPlanetKitMakeCallParamBuilder
.- The default value of the
initialMyVideoState
property isPlanetKitInitialMyVideoState.resume
. - If the caller's initial video state is set to paused by calling
setInitialMyVideoState()
withPlanetKitInitialMyVideoState.pause
, the caller's video is not transmitted. To transmit the caller's video after the call is connected, callresumeMyVideo()
.
- The default value of the
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 ofacceptCall()
.- The default value of the
initialMyVideoState
parameter isPlanetKitInitialMyVideoState.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, callresumeMyVideo()
.
- The default value of the
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)
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.