본문으로 건너뛰기
Version: 1.0

1대1 영상 통화

1대1 영상 통화를 구현하는 예제 코드입니다.

필수 조건

시작하기 전에 다음 작업을 수행해야 합니다.

1대1 영상 통화 구현 시 고려 사항

착신자 측에서 통화 알림을 받으려면, 알림용 시스템을 구현하거나 APNs(Apple Push Notification service) 또는 FCM(Firebase Cloud Messaging) 같은 외부 푸시 알림 시스템을 연동해야 합니다.

또한 착신자에게 어떤 정보를 전달해야 하는지도 알아야 합니다. 앱 서버의 역할에서 애플리케이션이 전달해야 하는 데이터인 cc_param에 관해 볼 수 있습니다.

makeCall() 또는 verifyCall()을 호출한 다음에는 반환된 PlanetKitMakeCallResult 또는 PlanetKitVerifyCallResultreason 속성을 확인해야 합니다.

  • reasonPlanetKitStartFailReason.NONE인 경우 성공을 의미합니다.
  • 그렇지 않으면 실패를 의미하며 reason에 따라 적절한 조치를 취해야 합니다.

장치 권한 요청

Dart permission_handler 패키지를 사용하여 마이크, 전화, Bluetooth 연결, 카메라 권한을 요청하세요.

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

변수 준비

주요 속성 및 이벤트 핸들러에 대한 변수를 준비합니다.

  • PlanetKitCallEventHandler는 1대1 통화의 상태 변경 이벤트를 처리하는 데 사용됩니다. 자세한 내용은 1대1 통화 흐름을 참조하세요.
  • PlanetKitMyMediaStatusHandler는 로컬 사용자의 미디어 상태 변경 이벤트를 처리하는 데 사용됩니다. 마이크가 음소거되거나 음소거 해제되는 경우, 오디오 설명이 업데이트되는 경우, 비디오 상태가 변경되는 경우 등 이벤트를 기반으로 로컬 사용자의 UI를 업데이트할 수 있습니다.
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;
}
});

통화 생성(발신 측)

통화를 생성하려면 PlanetKitMakeCallParamBuilder()를 사용해 PlanetKitMakeCallParam를 만들어 이를 인자로 PlanetKitManager.instance.makeCall()을 호출하세요.

  • 영상 통화를 생성하도록 PlanetKitMediaType.audiovideo를 인자로 PlanetKitMakeCallParamBuildersetMediaType()을 호출하세요.
  • 발신자의 초기 비디오 상태는 PlanetKitMakeCallParamBuildersetInitialMyVideoState()로 설정할 수 있습니다.
    • initialMyVideoState 속성의 기본값은 PlanetKitInitialMyVideoState.resume입니다.
    • PlanetKitInitialMyVideoState.pause를 인자로 setInitialMyVideoState()를 호출하여 발신자의 초기 비디오 상태가 일시 중지로 설정된 경우 발신자의 비디오는 전송되지 않습니다. 통화 연결 후 발신자의 비디오를 전송하려면 resumeMyVideo()를 호출하세요.
Future<bool> makeCall(String peerId, String accessToken, initialMyVideoState: PlanetKitInitialMyVideoState) 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;
}

통화 수신(착신 측)

앱 서버로부터 푸시 메시지를 받으면 메시지로부터 cc_param을 파싱하여 ccParam을 생성해야 합니다. 이 데이터는 통화를 수신할 때 전달해야 하는 필수 인자입니다.

통화를 수신하려면 적절한 PlanetKitVerifyCallParam과 함께 PlanetKitManager.instance.verifyCall()을 호출하세요. PlanetKitVerifyCallParam을 만들려면 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;
}

통화 응답(착신 측)

일반적으로 통화를 수신한 후 통화를 받을 것인지 아닌지 결정할 시간이 필요합니다. 통화에 응답하려면 PlanetKitCall.acceptCall()을 호출하세요.

  • 착신자의 초기 비디오 상태는 acceptCall()initialMyVideoState 파라미터로 설정할 수 있습니다.
    • initialMyVideoState 파라미터의 기본값은 PlanetKitInitialMyVideoState.resume입니다.
    • PlanetKitInitialMyVideoState.pause를 인자로 전달하여 착신자의 초기 비디오 상태가 일시 중지로 설정된 경우 착신자의 비디오는 전송되지 않습니다. 통화 연결 후 착신자의 비디오를 전송하려면 resumeMyVideo()를 호출하세요.
Tip

_call 변수는 verifyCall()에서 검증을 마친 PlanetKit 인스턴스입니다.

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

비디오 렌더링

로컬 비디오(로컬 사용자의 비디오)와 원격 비디오(피어의 비디오)를 렌더링하려면 PlanetKitVideoViewBuilder를 사용하여 PlanetKitVideoView를 생성하고 PlanetKitCall에 추가해야 합니다.

다음 코드에서는 비디오가 설정된 상태로 PlanetKitCall 인스턴스가 생성되었다고 가정합니다.

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)는 로컬 사용자의 뷰에 해당하는 PlanetKitVideoView가 생성되었을 때 호출되어야 하며, PlanetKitCall.removeMyVideoView(viewId)는 이 뷰가 해제(dispose)되었을 때 호출되어야 합니다.

PlanetKitCall.addPeerVideoView(viewId)는 피어의 뷰에 해당하는 PlanetKitVideoView가 생성되었을 때 호출되어야 하며, PlanetKitCall.removePeerVideoView(viewId)는 이 뷰가 해제되었을 때 호출되어야 합니다.

통화 종료

통화를 종료하려면 endCall()을 호출하세요.

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

CallKit 연동을 위한 추가 구현(선택 사항)

Note

이 단계는 iOS 애플리케이션에만 적용됩니다.

CallKit 프레임워크를 사용하는 경우 PlanetKitMakeCallParamPlanetKitVerifyCallParam에서 callKitTypePlanetKitCallKitType.user로 설정해야 합니다.

그런 다음 CXProviderDelegate.provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)에서 PlanetKitCall.notifyCallKitAudioActivation()을 호출하여 PlanetKit에서 오디오가 활성화되도록 해야 합니다.

관련 예제 코드

관련 문서