本文にスキップする
Under translation
このページは現在翻訳中です。
Version: 6.0

1-to-1 video call

This page provides a code example for making 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).

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 PlanetKitCallMakeResult or PlanetKitCallVerifyResult.

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

Implement event delegates

Implement the event delegates to be used on the call.

  • PlanetKitCallDelegate is used to handle events related to call setup.
  • PlanetKitMyMediaStatusDelegate 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.
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.

}
}

Preview the local user's video

The preview feature allows the local user to preview their own video regardless of the 1-to-1 call session.

To starting previewing the local user's video, call startPreview() of PlanetKitCameraManager.

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

To stop previewing the local user's video, call stopPreview() of PlanetKitCameraManager.

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

Make an outgoing call (caller side)

The steps for making a video call are as follows:

  1. Build a setting through PlanetKitMakeCallSettingBuilder.
  2. Create PlanetKitCallParam with PlanetKitCallDelegate, and set the mediaType property to .audiovideo.
  3. Call PlanetKitManager.shared.makeCall().
  4. Check the PlanetKitCallMakeResult.

The caller's initial video state can be set with the initialMyVideoState property of PlanetKitCallParam.

  • The default value of the initialMyVideoState property is PlanetKitInitialMyVideoState.resume.
  • If the caller's initial video state is set to paused by setting initialMyVideoState to PlanetKitInitialMyVideoState.pause, the caller's video is not transmitted. The caller's video can be transmitted by calling resumeMyVideo() after the call is connected.
Note

Starting with PlanetKit 6.0, you must specify the CallKit type in PlanetKitCallKitSetting when building call settings for iOS applications. For more information on specifying the CallKit type, refer to Configure CallKit type in the application.

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)

// Configure CallKitSetting
let callKitSetting = createCallKitSetting()
var settingsBuilder = PlanetKitMakeCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)
let settings = try! settingsBuilder.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
}
}

Receive a push notification (callee side)

The call recipient comes to know that a new call has arrived through a push notification. The application must parse cc_param from the userInfo.

After receiving the push notification for a new call, you must respond to the call to continue.

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)
}
}

Respond to an incoming call (callee side)

Now you have to respond to the call by calling PlanetKitManager.shared.verifyCall().

The following verifyCallExample() shows how to respond to the call. Note that myId is the callee's user ID and myServiceId is the callee's service ID.

Note

Starting with PlanetKit 6.0, you must specify the CallKit type in PlanetKitCallKitSetting when building call settings for iOS applications. For more information on specifying the CallKit type, refer to Configure CallKit type in the application.

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

...

extension CalleeExample
{
func verifyCallExample(myId: String, myServiceId: String, ccParam: PlanetKitCCParam, delegate: PlanetKitCallDelegate) {
// Configure CallKitSetting
let callKitSetting = createCallKitSetting()
var settingsBuilder = PlanetKitVerifyCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)
let settings = try! settingsBuilder.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
}
}

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 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. The callee's video can be transmitted by calling resumeMyVideo() after the call is connected.
Tip

The verifiedCall variable is the PlanetKitCall instance that has been verified by verifyCall().

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

Set the media status event delegate for the local user

Once the call is connected, add the media status event delegate for the local user.

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

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

Add a video view for the local user

To add a video view for the local user, create a PlanetKitMTKView instance (myVideoView), add it as a subview to the local view, and set it as the receiver of the local user's video stream using addReceiver() of PlanetKitVideoStream.

The rendering of the video starts or stops according to the local user's video status.

#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)
}
}

Add a video view for the peer

To add a video view for the peer, create a PlanetKitMTKView instance (peerVideoView), add it as a subview to the peer view, and set it as the receiver of the peer's video stream using addReceiver() of PlanetKitVideoStream.

The rendering of the video starts or stops according to the peer's video status.

#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)
}
}

End a call

To end a call, call endCall().

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

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

Configure CallKit type in the application

CallKit is an Apple framework that provides a calling interface. The PlanetKit SDK supports three CallKit integration options:

  1. User-defined CallKit implementation
  2. PlanetKit's internal CallKit implementation
  3. No CallKit integration

When using the PlanetKit SDK to make or verify calls, you must specify the CallKit type in PlanetKitCallKitSetting.

Please note that when PlanetKitCallKitType is set to either user or planetkit, PlanetKit delegates control of the microphone indicator to CallKit.

Example for configuring PlanetKitCallKitType

func createCallKitSetting() -> PlanetKitCallKitSetting  {
// Select the appropriate CallKit integration option from the examples below
// when integrating the user CallKit implementation.
let callKitSetting = PlanetKitCallKitSetting(type: .user, param: nil)

// when using PlanetKit internal CallKit implementation.
let callkitParam = PlanetKitCallKitParam(appName: "Example app", callerName: "caller name", isVideo: true, ringtoneSound: nil, icon: "example icon", addCallToList: true, supportsHolding: true)
let callKitSetting = PlanetKitCallKitSetting(type: .planetkit, param: callkitParam)

// when CallKit is not used in the app.
let callKitSetting = PlanetKitCallKitSetting(type: .none, param: nil)

return callKitSetting
}

Implement Apple CallKit in the application

The following example is a guide on how to integrate user implementation of CallKit to PlanetKit.

It's crucial to synchronize call states—such as the mute state—between CallKit and PlanetKit when integrating the user-defined CallKit implementation.

Caller example

  1. Create a PlanetKitCallKitSetting instance and set type of PlanetKitCallKitSetting to user.

  2. Pass the instance to withCallKitSettingsKey() of PlanetKitMakeCallSettingBuilder and build settings for making a call (makeCallSettings).

    // Code for Step 1 and Step 2

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

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

    let makeCallSettings = try! settingsBuilder.build()

    return makeCallSettings
    }

Callee example

  1. Create a PlanetKitCallKitSetting instance and set type of PlanetKitCallKitSetting to user.

  2. Pass the instance to withCallKitSettingsKey() of PlanetKitVerifyCallSettingBuilder and build settings for verifying a call (verifyCallSettings).

    // Code for Step 1 and Step 2

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

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

    let verifyCallSettings = try! settingsBuilder.build()

    return verifyCallSettings
    }

Caller/Callee common example

  1. Implement a CallKit handler to handle CallKit operations. The call variable is the PlanetKitCall instance obtained from making or verifying a call.

    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.
    }
  2. On provider(_:didActivate:) of CXProviderDelegate, call notifyCallKitAudioActivation() of PlanetKitCall.

    extension YourCallKitHandler : CXProviderDelegate {

    ...

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    call.notifyCallKitAudioActivation()
    }

    // Implement other delegate functions to handle other CXCallActions
    ...
    }
  3. Call the relevant PlanetKitCall functions when a CXCallAction is performed with CallKit.

    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
    guard success else {
    action.fail()
    return
    }
    action.fulfill()
    }
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
    if action.isOnHold {
    call.hold(reason: nil) { success in
    guard success else {
    action.fail()
    return
    }
    action.fulfill()
    }
    }
    else {
    call.unhold() { success in
    guard success else {
    action.fail()
    return
    }
    action.fulfill()
    }
    }
    }
    // Implement other delegate functions to handle other CXCallActions
    ...
    }
  4. Request the relevant CXCallAction through CallKit to synchronize CallKit and PlanetKit.

    extension YourCallViewController {
    @IBAction func muteCall(_ sender: UIButton) {
    call.muteMyAudio() { success in
    if success {
    YourCallKitHandler.shared.muteCall()
    }
    }
    }
    @IBAction func holdCall(_sender: UIButton) {
    call.hold(reason: nil) { success in
    if success {
    YourCallKitHandler.shared.holdCall()
    }
    }
    }
    }

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

    func holdCall() {
    let action = CXSetHeldCallAction(call: call.uuid, onHold: true)
    callController.requestTransaction(with: action) { error in
    NSLog("\(error)")
    }
    }
    }

For more information, see CallKit documentation.