1対1ビデオ通話
1対1ビデオ通話を実装するサンプルコードです。
前提条件
開始する前に、次の作業が必要です。
- PlanetKitを初期化してください。
- 適切なアクセストークンを取得してください。
- 1対1通話フローにて、APIを使用するための全般的なプロセスを確認してください。
1対1ビデオ通話実装時の考慮事項
受信者側で通話の通知を受けるには、通知用システムを実装するか、APNs(Apple Push Notification service)といった外部のプッシュ通知システムを連携する必要があります。
また、受信者にどのような情報を渡すべきかを知っておく必要があります。アプリサーバーの役割にて、アプリケーションが渡すべきデータであるcc_param
について確認できます。
makeCall()
またはverifyCall()
を呼び出した後は、返却されたPlanetKitCallMakeResult
またはPlanetKitCallVerifyResult
でreason
を確認する必要があります。
reason
がPlanetKitStartFailReason.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通話セッションに関係なく、自分のビデオをプレビューできます。
ローカルユーザーのビデオプレビューを開始するには、PlanetKitCameraManager
のをstartPreview()
呼び出します。
func startPreviewExample(delegate: PlanetKitVideoOutputDelegate)
{
PlanetKitCameraManager.shared.startPreview(delegate)
}
ローカルユーザーのビデオプレビューを中止するには、PlanetKitCameraManager
のをstopPreview()
呼び出します。
func stopPreviewExample(delegate: PlanetKitVideoOutputDelegate)
{
PlanetKitCameraManager.shared.stopPreview(delegate)
}
通話の作成(発信側)
通話を作成するための手順は、次のとおりです。
PlanetKitMakeCallSettingBuilder
を通じて設定情報を作成します。PlanetKitCallDelegate
を含めてPlanetKitCallParam
を作成し、mediaType
プロパティを.audiovideo
に設定します。PlanetKitManager.shared.makeCall()
を呼び出します。PlanetKitCallMakeResult
を確認してください。
発信者のビデオの初期状態は、PlanetKitCallParam
のinitialMyVideoState
プロパティに設定できます。
initialMyVideoState
プロパティのデフォルト値は、PlanetKitInitialMyVideoState.resume
です。initialMyVideoState
プロパティをPlanetKitInitialMyVideoState.pause
に設定して発信者のビデオの初期状態が一時停止に設定された場合、発信者のビデオは送信されません。この場合、発信者のビデオは通話が接続された後、resumeMyVideo()
を呼び出して送信できます。
PlanetKit 6.0からiOSアプリケーションでは、通話設定を作成する時にPlanetKitCallKitSetting
にCallKitタイプを必ず指定する必要があるため、通話を作成するためのコードがiOSとmacOSの間で異なります。CallKitタイプの指定に関する詳細は、アプリケーションでCallKitタイプ設定を参照してください。
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
}
}
プッシュ通知の受信(受信側)
通話を受信した側はプッシュ通知システムを通じて新しい電話がかかってきたことに気づきます。このとき、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です。
PlanetKit 6.0からiOSアプリケーションでは、通話設定を作成する時にPlanetKitCallKitSetting
にCallKitタイプを必ず指定する必要があるため、通話を受信するためのコードがiOSとmacOSの間で異なります。CallKitタイプの指定に関する詳細は、アプリケーションでCallKitタイプ設定を参照してください。
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
}
}
通話の応答(受信側)
一般的に通話を受信した後、通話を受けるかどうかを決める時間が必要です。通話に応答するには、acceptCall()
を呼び出します。
- 受信者のビデオの初期状態は、
acceptCall()
のinitialMyVideoState
パラメーターで設定できます。initialMyVideoState
パラメーターのデフォルト値は、PlanetKitInitialMyVideoState.resume
です。PlanetKitInitialMyVideoState.pause
を引数として引き渡し、受信者のビデオの初期状態が一時停止に設定された場合、受信者のビデオは送信されません。この場合、受信者のビデオは通話が接続された後、resumeMyVideo()
を呼び出して送信できます。
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
)を作成し、ローカルビューにサブビューとして追加した後、PlanetKitVideoStream
のaddReceiver()
でローカルユーザーのビデオストリームを受信するように設定します。
ビデオのレンダリングは、ローカルユーザーのビデオ状態によって、開始または中止されます。
#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
)を作成し、ピアビューにサブビューとして追加した後、PlanetKitVideoStream
のaddReceiver()
でピアのビデオストリームを受信するように設定します。
ビデオのレンダリングは、ピアのビデオ状態によって、開始または中止されます。
#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()
}
}
アプリケーションでCallKitタイプ設定
CallKitは、通話インスタンスを提供するAppleのフレームワークです。PlanetKit SDKはCallKit連動のために以下3つのオプションを提供します。
- ユーザー定義のCallKit実装
- PlanetKit内部のCallKit実装
- CallKitを連動しない
PlanetKit SDKを使用して通話を作成または受信する際、PlanetKitCallKitSetting
にCallKitタイプを指定する必要があります。
PlanetKitCallKitType
がuser
またはplanetkit
に設定された場合、PlanetKitはマイクインジケーターの制御をCallKitに委任することに注意してください。
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
}
アプリケーションでApple CallKitを実装
次の例は、ユーザー定義のCallKit実装をPlanetKitに連動させる方法についてのガイドです。
ユーザー定義のCallKit実装を連動するとき、CallKitとPlanetKitの間でミュート状態などの通話状態を同期化することが重要です。
発信側の実装例
-
PlanetKitCallKitSetting
インスタンスを作成し、PlanetKitCallKitSetting
のtype
をuser
に設定します。 -
PlanetKitMakeCallSettingBuilder
のwithCallKitSettingsKey()
にインスタンスを引き渡し、通話作成設定(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
}
受信側の実装例
-
PlanetKitCallKitSetting
インスタンスを作成し、PlanetKitCallKitSetting
のtype
をuser
に設定します。 -
PlanetKitVerifyCallSettingBuilder
のwithCallKitSettingsKey()
にインスタンスを引き渡し、通話受信設定(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
}
発信側/受信側の共通実装の例
-
CallKit動作を処理するためのCallKitハンドラーを実装します。
call
変数は通話を作成したり受信したりする時に取得したPlanetKitCall
インスタンスです。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.
} -
CXProviderDelegate
のprovider(_:didActivate:)
でPlanetKitCall
のnotifyCallKitAudioActivation()
を呼び出します。extension YourCallKitHandler : CXProviderDelegate {
...
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
call.notifyCallKitAudioActivation()
}
// Implement other delegate functions to handle other CXCallActions
...
} -
CallKitで
CXCallAction
が実行される時に関連PlanetKitCall
関数を呼び出します。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
...
} -
CallKitとPlanetKitを同期化するためにCallKitを通じて関連
CXCallAction
を呼び出します。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)")
}
}
}
詳細は、CallKitドキュメントを参照してください。