1대1 영상 통화
1대1 영상 통화를 구현하는 예제 코드입니다.
필수 조건
시작하기 전에 다음 작업을 수행해야 합니다.
- PlanetKit을 초기화하세요.
- 적절한 액세스 토큰을 획득하세요.
- 1대1 통화 흐름에서 전반적인 API 사용 과정을 확인하세요.
1대1 영상 통화 구현 시 고려 사항
착신자 측에서 통화 알림을 받으려면, 알림용 시스템을 구현하거나 APNs(Apple Push Notification service) 또는 FCM(Firebase Cloud Messaging) 같은 외부 푸시 알림 시스템을 연동해야 합니다.
또한 착신자에게 어떤 정보를 전달해야 하는지도 알아야 합니다. 앱 서버의 역할에서 애플리케이션이 전달해야 하는 데이터인 cc_param
에 관해 볼 수 있습니다.
makeCall()
또는 verifyCall()
을 호출한 다음에는 반환된 PlanetKitMakeCallResult
또는 PlanetKitVerifyCallResult
의 reason
속성을 확인해야 합니다.
reason
이PlanetKitStartFailReason.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
를 인자로PlanetKitMakeCallParamBuilder
의setMediaType()
을 호출하세요. - 발신자의 초기 비디오 상태는
PlanetKitMakeCallParamBuilder
의setInitialMyVideoState()
로 설정할 수 있습니다.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()
를 호출하세요.
_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 연동을 위한 추가 구현(선택 사항)
이 단계는 iOS 애플리케이션에만 적용됩니다.
CallKit 프레임워크를 사용하는 경우 PlanetKitMakeCallParam
및 PlanetKitVerifyCallParam
에서 callKitType
을 PlanetKitCallKitType.user
로 설정해야 합니다.
그런 다음 CXProviderDelegate.provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)
에서 PlanetKitCall.notifyCallKitAudioActivation()
을 호출하여 PlanetKit에서 오디오가 활성화되도록 해야 합니다.