본문으로 건너뛰기
Version: 5.5

1대1 영상 통화

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

필수 조건

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

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

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

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

makeCall() 또는 verifyCall()을 호출한 다음에는 반환된 PlanetKitCallMakeResult 또는 PlanetKitCallVerifyResult에서 reason을 확인해야 합니다.

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

이벤트 대리자 구현

통화에서 사용될 이벤트 대리자(delegate)를 구현하세요.

  • PlanetKitCallDelegate는 통화 셋업과 관련된 이벤트를 처리하는 데 사용됩니다.
  • PlanetKitMyMediaStatusDelegate는 로컬 사용자의 미디어 상태 변경 이벤트를 처리하는 데 사용됩니다. 마이크가 음소거되거나 음소거 해제되는 경우나 오디오 설명이 업데이트되는 경우 또는 비디오 상태가 업데이트되는 경우 등 이벤트를 기반으로 로컬 사용자의 UI를 업데이트할 수 있습니다.
extension CallDelegateExample : PlanetKitCallDelegate {
func didWaitConnect(_ call: PlanetKitCall) {

// This is called after making a call on the caller side.
// Write your own code here.

}

func didVerify(_ call: PlanetKitCall, peerStartMessage: PlanetKitCallStartMessage?, peerUseResponderPreparation: Bool) {

// This is called after verifying a call on the callee side.
// Write your own code here.

}

func didConnect(_ call: PlanetKitCall, connected: PlanetKitCallConnectedParam) {

// This is called after the call is connected on both sides.
// Write your own code here.

}

func didDisconnect(_ call: PlanetKitCall, disconnected: PlanetKitDisconnectedParam) {

// This is called after the call is disconnected on both sides.
// Write your own code here.

}

func didFinishPreparation(_ call: PlanetKitCall) {

// This is called after making a call on the caller side.
// Write your own code here.

}

...

//
// Also, you should implement other methods defined in the PlanetKitCallDelegate protocol.
//
...
}

extension MyMediaStatusDelegateExample: PlanetKitMyMediaStatusDelegate {
func didMuteMic(_ myMediaStatus: PlanetKitMyMediaStatus) {

// This is called when the local user's audio is muted.
// Write your own code here.

}

func didUnmuteMic(_ myMediaStatus: PlanetKitMyMediaStatus) {

// This is called when the local user's audio is unmuted.
// Write your own code here.

}

func didUpdateAudioDescription(_ myMediaStatus: PlanetKitMyMediaStatus, description: PlanetKitMyAudioDescription) {

// This is called when the local user's audio description is updated.
// Write your own code here.

}

func didUpdateVideoStatus(_ myMediaStatus: PlanetKitMyMediaStatus, status: PlanetKitVideoStatus) {

// This is called when the local user's video status is updated.
// Write your own code here.

}
}

로컬 사용자의 비디오 미리보기

미리보기 기능을 사용하면 로컬 사용자가 1대1 통화 세션과 관계없이 자신의 비디오를 미리 볼 수 있습니다.

로컬 사용자의 비디오 미리보기를 시작하려면 PlanetKitCameraManagerstartPreview()를 호출하세요.

func startPreviewExample(delegate: PlanetKitVideoOutputDelegate)
{
PlanetKitCameraManager.shared.startPreview(delegate)
}

로컬 사용자의 비디오 미리보기를 중지하려면 PlanetKitCameraManagerstopPreview()를 호출하세요.

func stopPreviewExample(delegate: PlanetKitVideoOutputDelegate)
{
PlanetKitCameraManager.shared.stopPreview(delegate)
}

통화 생성(발신 측)

통화를 생성하기 위한 절차는 다음과 같습니다.

  1. PlanetKitMakeCallSettingBuilder를 통해 설정 정보를 만드세요.
  2. PlanetKitCallDelegate를 포함하여 PlanetKitCallParam을 생성하고, mediaType 속성을 .audiovideo로 설정하세요.
  3. PlanetKitManager.shared.makeCall()를 호출하세요.
  4. PlanetKitCallMakeResult를 확인하세요.

발신자의 초기 비디오 상태는 PlanetKitCallParaminitialMyVideoState 속성으로 설정할 수 있습니다.

  • initialMyVideoState 속성의 기본값은 PlanetKitInitialMyVideoState.resume입니다.
  • initialMyVideoState 속성을 PlanetKitInitialMyVideoState.pause로 설정하여 발신자의 초기 비디오 상태가 일시 중지로 설정된 경우 발신자의 비디오는 전송되지 않습니다. 이 경우 발신자의 비디오는 통화가 연결된 후 resumeMyVideo()를 호출하여 전송할 수 있습니다.
class CallerExample
{
var call: PlanetKitCall?
var myMediaStatusDelegate: MyMediaStatusDelegateExample
}

...

extension CallerExample
{
func makeCallExample(myId: String, myServiceId: String, peerId: String, peerServiceId: String, accessToken: String, delegate: PlanetKitCallDelegate, initialMyVideoState: PlanetKitInitialMyVideoState) {
let myUserId = PlanetKitUserId(id: myId, serviceId: myServiceId)
let peerUserId = PlanetKitUserId(id: peerId, serviceId: peerServiceId)

let settings = try! PlanetKitMakeCallSettingBuilder().build()
let param = PlanetKitCallParam(myUserId: myUserId, peerUserId: peerUserId, delegate: delegate, accessToken: accessToken)
param.mediaType = .audiovideo
param.initialMyVideoState = initialMyVideoState

let result = PlanetKitManager.shared.makeCall(param: param, settings: settings)

guard result.reason == .none else {
NSLog("Failed reason: result. \(result.reason)")
return
}

// The result.call instance is a call instance for calling other APIs from now on.
// You must keep this instance in your own context.
// In this example, the "call" variable holds the instance.
call = result.call
}
}

푸시 알림 수신(착신 측)

통화를 수신한 쪽은 푸시 알림 시스템을 통해 새로운 전화가 왔음을 알게 됩니다. 이때 userInfo로부터 cc_param을 파싱해야 합니다.

새로운 전화가 왔음을 알리는 푸시 알림을 받은 다음 사용자가 통화할 수 있도록 그 요청을 수신해야 합니다.

extension YourPushHandler : PKPushRegistryDelegate {

...

var callee: CalleeExample
var callDelegate: PlanetKitCallDelegate

func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
// Update the push token for the application's push server.
}

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {

let message = payload.dictionaryPayload as NSDictionary as! [String: AnyObject]
let param = message["cc_param"] as! String
let ccParam = PlanetKitCCParam(ccParam: param)

// Now we received a push notification.
// We should write code here to respond to the new call.
// For convenience, we assume this is done in verifyCallExample()

let myUserId = "Callee User ID value"
let myServiceId = "Callee Service ID value"

callee.verifyCallExample(myId: myUserId, myServiceId: myServiceId, ccParam: ccParam, delegate: PlanetKitCallDelegate)
}
}

통화 수신(착신 측)

이제 PlanetKitManager.shared.verifyCall()를 호출해서 통화를 수신해 봅시다.

아래 코드의 verifyCallExample()에서 통화를 수신하는 방법을 볼 수 있습니다. 이때, myId는 착신자의 사용자 ID이고, myServiceId는 착신자의 서비스 ID입니다.

class CalleeExample
{
var verifiedCall: PlanetKitCall?
var myMediaStatusDelegate: MyMediaStatusDelegateExample
}

...

extension CalleeExample
{
func verifyCallExample(myId: String, myServiceId: String, ccParam: PlanetKitCCParam, delegate: PlanetKitCallDelegate) {
let settings = try! PlanetKitVerifyCallSettingBuilder().build()

let myUserId = PlanetKitUserId(id: myId, serviceId: myServiceId)

let result = PlanetKitManager.shared.verifyCall(myUserId: myUserId, ccParam: ccParam, delegate: delegate)

guard result.reason == .none else {
NSLog("Failed reason: result. \(result.reason)")
return
}

// The result.call instance is the verified call instance.
// You have to keep this instance in an application layer to call other APIs provided after call setup.
// This example keeps the verified call instance in the following verifiedCall variable.
verifiedCall = result.call
}
}

통화 응답(착신 측)

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

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

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

extension CalleeExample
{
func acceptCallExample(initialMyVideoState: PlanetKitInitialMyVideoState) {
verifiedCall.acceptCall(startMessage: nil, useResponderPreparation: false, initialMyVideoState: initialMyVideoState)
}
}

로컬 사용자에 대한 미디어 상태 이벤트 대리자 설정

통화가 연결되면 로컬 사용자에 대한 미디어 상태 이벤트 대리자를 설정하세요.

// Caller side
extension CallerExample
{
func addMyMediaStatusHandlerExample() {
call.myMediaStatus.addHandler(myMediaStatusDelegate) {
// completion callback
}
}
}

// Callee side
extension CalleeExample
{
func addMyMediaStatusHandlerExample() {
verifiedCall.myMediaStatus.addHandler(myMediaStatusDelegate) {
// completion callback
}
}
}

로컬 사용자를 위한 비디오 뷰 추가

로컬 사용자를 위한 비디오 뷰를 추가하려면 PlanetKitMTKView 인스턴스(myVideoView)를 생성하고 로컬 뷰에 하위 뷰로 추가한 다음 PlanetKitVideoStreamaddReceiver()로 로컬 사용자의 비디오 스트림을 받도록 설정하세요.

비디오 렌더링은 로컬 사용자의 비디오 상태에 따라 시작되거나 중지됩니다.

#if os(macOS)
typealias UIView = NSView
#endif

extension YourCallViewController {

@IBOutlet weak var localView: UIView!

var call: PlanetKitCall! // call or verifiedCall

...

func addMyVideoViewExample()
{
let myVideoView = PlanetKitMTKView(frame: localView.bounds, device: nil)
localView.addSubview(myVideoView)

call.myVideoStream.addReceiver(myVideoView)
}
}

피어를 위한 비디오 뷰 추가

피어를 위한 비디오 뷰를 추가하려면 PlanetKitMTKView 인스턴스(peerVideoView)를 생성하고 피어 뷰에 하위 뷰로 추가한 다음 PlanetKitVideoStreamaddReceiver()로 피어의 비디오 스트림을 받도록 설정하세요.

비디오 렌더링은 피어의 비디오 상태에 따라 시작되거나 중지됩니다.

#if os(macOS)
typealias UIView = NSView
#endif

extension YourCallViewController {

@IBOutlet weak var peerView: UIView!

var call: PlanetKitCall! // call or verifiedCall

...

func addPeerVideoViewExample()
{
let peerVideoView = PlanetKitMTKView(frame: peerView.bounds, device: nil)
peerView.addSubview(peerVideoView)

call.peerVideoStream.addReceiver(peerVideoView)
}
}

통화 종료

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

// Caller side
extension CallerExample
{
func endCallExample() {
call.endCall()
}
}

// Callee side
extension CalleeExample
{
func endCallExample() {
verifiedCall.endCall()
}
}

애플리케이션에서 Apple CallKit 구현

Note

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

CallKit은 통화 인터페이스를 제공하는 Apple의 프레임워크 중 하나입니다. PlanetKit SDK는 CallKit에 대해 사용자 CallKit 구현 연동, PlanetKit 내부 구현 연동, 연동하지 않음이라는 세 가지 옵션을 지원합니다.

기본적으로 PlanetKit SDK는 CallKit 연동 없이 통화를 생성하거나 수신합니다. 자체 CallKit 구현을 연동하려면 PlanetKit SDK로 통화를 생성하거나 수신할 때 PlanetKitCallKitSettingtypeuser로 설정해야 합니다.

발신 측 구현 예제

  1. PlanetKitCallKitSetting 인스턴스를 생성하고 PlanetKitCallKitSettingtypeuser로 설정하세요.

  2. PlanetKitMakeCallSettingBuilderwithCallKitSettingsKey()에 인스턴스를 전달하고 통화 생성 설정(makeCallSettings)을 만드세요.

    // Code for Step 1 and Step 2

    func createMakeCallSettingsWithUserCallKitExample() -> [String: Any] {
    let callKitSetting = PlanetKitCallKitSetting(type: .user, param: nil)

    var settingsBuilder = try! PlanetKitMakeCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)

    let makeCallSettings = settingsBuilder.build()
    }
  3. CXProviderDelegateprovider(_:didActivate:)에서 PlanetKitCallnotifyCallKitAudioActivation()을 호출하세요.

    // Code for Step 3

    extension YourCallKitHandler : CXProviderDelegate {
    ...

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    // PlanetKitCall instance created from PlanetKitManager.makeCall()
    call.notifyCallKitAudioActivation()
    }

    ...
    }

착신 측 구현 예제

  1. PlanetKitCallKitSetting 인스턴스를 생성하고 PlanetKitCallKitSettingtypeuser로 설정하세요.

  2. PlanetKitVerifyCallSettingBuilderwithCallKitSettingsKey()에 인스턴스를 전달하고 통화 수신 설정(verifyCallSettings)을 만드세요.

    // Code for Step 1 and Step 2

    func createVerifyCallSettingsWithUserCallKitExample() -> [String: Any] {
    let callKitSetting = PlanetKitCallKitSetting(type: .user, param: nil)

    var settingsBuilder = try! PlanetKitVerifyCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)

    let verifyCallSettings = settingsBuilder.build()
    }
  3. CallKit 동작을 처리하기 위한 CallKit 핸들러를 구현하세요.

    class YourCallKitHandler {
    static let shared = YourCallKitHandler()

    private var provider: CXProvider?
    private var callController: CXCallController?
    private var call: PlanetKitCall?
    ...
    // Implement initialization and other features to handle CallKit operations.
    }
  4. CXProviderDelegateprovider(_:perform:)CXCallAction을 기반으로 PlanetKitCallacceptCall()endCall()을 호출하세요.

    extension YourCallKitHandler : CXProviderDelegate {

    ...

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    call.acceptCall(useResponderPreparation: false, startMessage: nil)
    action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
    call.endCall()
    action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
    call.muteMyAudio(action.isMuted) { success in }
    action.fulfill()
    }

    // Implement other delegate functions to handle other CXCallActions
    ...
    }
  5. CXProviderDelegateprovider(_:didActivate:)에서 PlanetKitCallnotifyCallKitAudioActivation()을 호출하세요.

    extension YourCallKitHandler : CXProviderDelegate {

    ...

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    // PlanetKitCall instance created from PlanetKitManager.makeCall()
    call.notifyCallKitAudioActivation()
    }

    // Implement other delegate functions to handle other CXCallActions
    ...
    }
  6. CallKit과 PlanetKit을 동기화하기 위해 CallKit을 통해 PlanetKitCallmuteMyAudio(), hold()unhold()를 호출하도록 UI 코드를 수정하세요.

    extension YourCallViewController {
    @IBAction func muteCall(_ sender: UIButton) {
    YourCallKitHandler.shared.muteCall()
    }
    }

    extension YourCallKitHandler {
    func muteCall() {
    let action = CXSetMutedCallAction(call: call.uuid , muted: true)
    callController.requestTransaction(with: action) { error in
    NSLog("\(error)")
    }
    }
    }

상세한 정보는 CallKit 문서를 참고하세요.

관련 예제 코드

관련 문서