次の方法で共有


SDK イベントをサブスクライブする

Calling SDK イベントをサブスクライブすることをお勧めします。 Azure Communication Services SDK は動的であり、時間の経過と伴って変化する可能性のあるプロパティが含まれています。 これらのイベントをサブスクライブして、変更の前に通知を受け取ることができます。 この記事の手順に従って、Azure Communication Services SDK イベントをサブスクライブします。

Azure Communication Calling SDK でのイベント

このセクションでは、アプリがサブスクライブできるイベントとプロパティの変更について説明します。 これらのイベントをサブスクライブすると、呼び出し元 SDK の状態変化についてアプリに通知され、それに応じて対応できるようになります。

イベントの追跡は、アプリケーションの状態を Azure Communication Services Calling フレームワークの状態と同期し続けるので非常に重要です。 イベントの追跡は、SDK オブジェクトにプル メカニズムを実装せずに変更を取り込むのに役立ちます。

このセクションでは、クイック スタートを実行した場合、または呼び出しを行って受信できるアプリケーションを実装していることを前提としています。 ファースト ステップ ガイドを完了していない場合は、「 音声通話をアプリに追加する」を参照してください。

JavaScript Calling SDK の各オブジェクトには、 propertiescollectionsがあります。 これらの値は、オブジェクトの有効期間を通じて変化します。

オブジェクト イベントをサブスクライブするには、 on() メソッドを使用します。 オブジェクト イベントのサブスクライブを解除するには、 off() メソッドを使用します。

プロパティ

'<property>Changed' イベントをサブスクライブして、プロパティの値の変更をリッスンできます。

プロパティのサブスクリプションの例

この例では、isLocalVideoStarted プロパティの値の変更をサブスクライブします。

call.on('isLocalVideoStartedChanged', () => {
    // At that point the value call.isLocalVideoStarted is updated
    console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
});

コレクション

\<collection>Updated イベントをサブスクライブして、オブジェクト コレクション内の変更に関する通知を受け取ることができます。 \<collection>Updated イベントは、監視しているコレクションに要素が追加または削除されるたびにトリガーされます。

  • '<collection>Updated' イベントのペイロードには、コレクションに追加された値が格納される added 配列があります。
  • また、'<collection>Updated' イベントのペイロードには、コレクションから削除された値が格納される removed 配列もあります。

コレクションのサブスクリプションの例

この例では、Call オブジェクト LocalVideoStream の値の変更をサブスクライブします。

call.on('localVideoStreamsUpdated', updateEvent => {
    updateEvent.added.forEach(async (localVideoStream) => {
        // Contains an array of LocalVideoStream that were added to the call
        // Add a preview and start any processing if needed
        handleAddedLocalVideoStream(localVideoStream )
    });
    updateEvent.removed.forEach(localVideoStream => {
        // Contains an array of LocalVideoStream that were removed from the call
        // Remove the preview and stop any processing if needed
        handleRemovedLocalVideoStream(localVideoStream ) 
    });
});

CallAgent オブジェクトのイベント

イベント名: incomingCall

incomingCall イベントは、クライアントが着信呼び出しを受信したときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションは、着信呼び出しを受信者に通知する必要があります。 通知プロンプトでは、受信者が通話を受け入れるか拒否できるようにする必要があります。

コード サンプル:

callClient.on('incomingCall', (async (incomingCallEvent) => {
    try {
        // Store a reference to the call object
        incomingCall = incomingCallEvent.incomingCall; 
        // Update your UI to allow
        acceptCallButton.disabled = false; 
        callButton.disabled = true;
    } catch (error) {
        console.error(error);
    }
});

イベント名: callsUpdated

callsUpdatedの更新イベントは、通話が通話エージェントから削除されるか、または追加されたときに発生します。 このイベントは、ユーザーが通話をする、受信する、または終了する際に発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、CallAgent インスタンスのアクティブな呼び出しの数に基づいて UI を更新する必要があります。

イベント名: connectionStateChanged

connectionStateChanged イベントは、CallAgent のシグナル状態が更新される際に発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、新しい状態に基づいて UI を更新する必要があります。 使用可能な接続状態の値は、 ConnectedDisconnectedです。

コード サンプル:

callClient.on('connectionStateChanged', (async (connectionStateChangedEvent) => {
    if (connectionStateChangedEvent.newState === "Connected") {
        enableCallControls() // Enable all UI element that allow user to make a call
    }

    if (connectionStateChangedEvent.newState === 'Disconnected') {
        if (typeof connectionStateChangedEvent.reason !== 'undefined') {
            alert(`Disconnected reason: ${connectionStateChangedEvent.reason}`)
        } 
        disableCallControls() // Disable all the UI element that allows the user to make a call
    }
});

Call オブジェクトのイベント

イベント名: stateChanged

stateChanged イベントは、呼び出し状態が変化したときに発生します。 たとえば、呼び出しが connected から disconnected になるときです。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、それに応じて UI を更新する必要があります。 新しい呼び出し状態に基づいて、適切なボタンやその他の UI 要素を無効または有効にします。

コード サンプル:

call.on('stateChanged', (async (connectionStateChangedEvent) => {
  if(call.state === 'Connected') {
      connectedLabel.hidden = false;
      acceptCallButton.disabled = true;
      startCallButton.disabled = true;
      startVideoButton.disabled = false;
      stopVideoButton.disabled = false
  } else if (call.state === 'Disconnected') {
      connectedLabel.hidden = true;
      startCallButton.disabled = false;
      console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
  }
});

イベント: idChanged

idChanged イベントは、呼び出しの ID が変更されたときに発生します。 呼び出しの ID は、呼び出しが connecting 状態から connected に移行すると変更されます。 呼び出しが接続されると、呼び出しの ID は同じままになります。

アプリケーションはイベントにどのように反応しますか?

アプリケーションは、新しい呼び出し ID を保存することも、必要に応じて後で呼び出しオブジェクトから取得することもできます。

コード サンプル:

let callId = "";
call.on('idChanged', (async (callIdChangedEvent) => {
  callId = call.id; // You can log it as the call ID is useful for debugging call issues
});

イベント: isMutedChanged

isMutedChanged イベントは、ローカル オーディオがミュートまたはミュート解除されたときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、ミュート/ミュート解除ボタンを適切な状態に更新する必要があります。

コード サンプル:

call.on('isMutedChanged', (async (isMutedChangedEvent) => {
    microphoneButton.disabled = call.isMuted;       
});

イベント: isScreenSharingOnChanged

isScreenSharingOnChanged イベントは、ローカル ユーザーの画面共有が有効または無効になったときに発生します。

アプリケーションはイベントにどのように反応しますか?

画面共有がオンになっている場合は、アプリケーションでプレビューや警告をユーザーに表示する必要があります。

画面共有がオフの場合、アプリケーションはプレビューと警告を削除する必要があります。

コード サンプル:

call.on('isScreenSharingOnChanged', () => {
  if (!this.call.isScreenSharing) {
      displayStartScreenSharingButton();
      hideScreenSharingWarning()
      removeScreenSharingPreview();    
  } else {
      displayScreenSharingWarning()
      displayStopScreenSharingButton();
      renderScreenSharingPreview(); 
  }
});

イベント: isLocalVideoStartedChanged

isLocalVideoStartedChanged イベントは、ユーザーがローカルビデオを有効または無効にしたときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでローカル ビデオのプレビューを表示し、カメラのアクティブ化ボタンを有効または無効にする必要があります。

コード サンプル:

call.on('isLocalVideoStartedChanged', () => {
    showDisableCameraButton(call.isLocalVideoStarted);
});

イベント: remoteParticipantsUpdated

アプリケーションでは、追加された各 RemoteParticipants のイベントをサブスクライブし、通話を終了する参加者のイベントのサブスクライブを解除する必要があります。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでローカル ビデオのプレビューを表示し、カメラのアクティブ化ボタンを有効または無効にする必要があります。

コード サンプル:

call.on('remoteParticipantsUpdated', (remoteParticipantsUpdatedEvent) => {
    remoteParticipantsUpdatedEvent.added.forEach(participant => {
        // handleParticipant should
        //   - subscribe to the remote participants events 
        //   - update the UI 
        handleParticipant(participant);
    });
    
    remoteParticipantsUpdatedEvent.removed.forEach(participant => {
        // removeParticipant should
        //   - unsubscribe from the remote participants events 
        //   - update the UI  
        removeParticipant(participant);
    });
});

イベント: localVideoStreamsUpdated

localVideoStreamsUpdated イベントは、ローカル ビデオ ストリームの一覧が変更されたときに発生します。 これらの変更は、ユーザーがビデオ ストリームを開始または削除したときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、追加された各 LocalVideoStream のプレビューを表示する必要があります。 アプリケーションでプレビューを削除し、削除された各 LocalVideoStream の処理を停止する必要があります。

コード サンプル:

call.on('localVideoStreamsUpdated', (localVideoStreamUpdatedEvent) => {
    localVideoStreamUpdatedEvent.added.forEach(addedLocalVideoStream => { 
        // Add a preview and start any processing if needed
        handleAddedLocalVideoStream(addedLocalVideoStream) 
    });

    localVideoStreamUpdatedEvent.removed.forEach(removedLocalVideoStream => {
         // Remove the preview and stop any processing if needed
        this.handleRemovedLocalVideoStream(removedLocalVideoStream) 
    });
});

イベント: remoteAudioStreamsUpdated

remoteAudioStreamsUpdated イベントは、リモート オーディオ ストリームの一覧が変更されたときに発生します。 これらの変更は、リモート参加者が通話にオーディオ ストリームを追加または削除したときに発生します。

アプリケーションはイベントにどのように反応しますか?

ストリームが処理中で、現在削除されている場合は、処理を停止する必要があります。 一方、ストリームが追加された場合、イベント受信は、新しいオーディオ ストリームの処理を開始するのに適した場所です。

イベント: totalParticipantCountChanged

totalParticipantCountChanged は、呼び出しで totalParticipant の数が変更されたときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションで参加者カウンターが表示されている場合、アプリケーションはイベントの受信時にその参加者カウンターを更新できます。

コード サンプル:

call.on('totalParticipantCountChanged', () => {
    participantCounterElement.innerText = call.totalParticipantCount;
});

イベント: roleChanged

roleChanged 参加者は、localParticipant ロールが呼び出しで変更されたときに発生します。 たとえば、ローカル参加者が通話の発表者 ACSCallParticipantRolePresenter になる場合です。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、ユーザーの新しいロールに基づいてボタンを有効または無効にする必要があります。

コード サンプル:

call.on('roleChanged', () => {
    this.roleElement = call.role;
});

イベント: mutedByOthers

mutedByOthers イベントは、ローカル参加者が通話の他の参加者をミュートしたときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションは、ミュートされたことを通知するメッセージをユーザーに表示する必要があります。

コード サンプル:

call.on('mutedByOthers', () => {
    messageBanner.innerText = "You have been muted by other participant in this call";
});

イベント: callerInfoChanged

callerInfoChanged イベントは、呼び出し元の情報が更新されたときに発生します。 これは、呼び出し元が表示名を変更したときに発生します。

アプリケーションはイベントにどのように反応しますか? アプリケーションで、呼び出し元の情報を更新できます。

コード サンプル:

call.on('callerInfoChanged', () => {
    showCallerInfo(call.callerInfo)
});

イベント: transferorInfoChanged

transferorInfoChanged イベントは、転送者の情報が更新されたときに発生します。 これは、転送元が表示名を変更したときに発生します。

アプリケーションはイベントにどのように反応しますか? アプリケーションで、転送者の情報を更新できます。

コード サンプル:

call.on('transferorInfoChanged', () => {
    showTransferorInfo(call.transferorInfo)
});

RemoteParticipant オブジェクトのイベント

イベント: roleChanged

roleChanged イベントは、呼び出しの中で RemoteParticipant ロールが変更されたときに発生します。 たとえば、リモート参加者が通話の発表者 ACSCallParticipantRolePresenter になる場合です。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、新しいロール RemoteParticipant 基づいて UI を更新する必要があります。

コード サンプル:

remoteParticipant.on('roleChanged', () => {
    updateRole(remoteParticipant);
});

イベント: isMutedChanged

isMutedChanged イベントは、いずれかの RemoteParticipant がマイクをミュートまたはミュート解除したときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションは、参加者を表示するビューの近くにアイコンを表示できます。

コード サンプル:

remoteParticipant.on('isMutedChanged', () => {
    updateMuteStatus(remoteParticipant); // Update the UI based on the mute state of the participant
});

イベント: displayNameChanged

displayNameChanged の名前が更新されたときの RemoteParticipant

アプリケーションはイベントにどのように反応しますか?

アプリケーションが UI に表示されている場合は、参加者の名前を更新する必要があります。

コード サンプル:

remoteParticipant.on('displayNameChanged', () => {
    remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
});
remoteParticipant.on('displayNameChanged', (args: {newValue?: string, oldValue?: string, reason?: DisplayNameChangedReason}) => {
    remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
    console.log(`Display name changed from ${oldValue} to ${newValue} due to ${reason}`);
});

イベント: isSpeakingChanged

通話の主要な発話者が変更されたときの isSpeakingChanged

アプリケーションはイベントにどのように反応しますか?

主要な話者になった RemoteParticipant を表示するには、アプリケーション UI が優先される必要があります。

コード サンプル:

remoteParticipant.on('isSpeakingChanged', () => {
    showAsRemoteSpeaker(remoteParticipant) // Display a speaking icon near the participant
});

イベント: videoStreamsUpdated

リモート参加者が呼び出しに対して VideoStream を追加または削除したときの videoStreamsUpdated

アプリケーションはイベントにどのように反応しますか?

アプリケーションが削除されたストリームを処理していた場合、アプリケーションは処理を停止する必要があります。 新しいストリームが追加されたら、アプリケーションでストリームのレンダリングまたは処理を開始することをお勧めします。

コード サンプル:

remoteParticipant.on('videoStreamsUpdated', (videoStreamsUpdatedEvent) => {

     videoStreamsUpdatedEvent.added.forEach(addedRemoteVideoStream => { 
       // Remove a renderer and start processing the stream if any processing is needed
        handleAddedRemoteVideoStream(addedRemoteVideoStream) 
    });

    videoStreamsUpdatedEvent.removed.forEach(removedRemoteVideoStream => {
        // Remove the renderer and stop processing the stream if any processing is ongoing
        this.handleRemovedRemoteVideoStream(removedRemoteVideoStream) 
    });
});

AudioEffectsFeature オブジェクトのイベント

イベント: effectsStarted

このイベントは、選択したオーディオ効果がオーディオ ストリームに適用されるときに発生します。 たとえば、誰かがノイズ抑制をオンにすると、effectsStarted が動作します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、ユーザーがオーディオ効果を無効にできるボタンを表示または有効にできます。

コード サンプル:

audioEffectsFeature.on('effectsStarted', (effects) => {
    stopEffectButton.style.visibility = "visible"; 
});

イベント: effectsStopped

このイベントは、選択したオーディオ効果がオーディオ ストリームに適用されるときに発生します。 たとえば、誰かがノイズ抑制をオフにしたとき、effectsStopped が発火します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションでは、ユーザーがオーディオ効果を有効にできるボタンを表示または有効にできます。

コード サンプル:

audioEffectsFeature.on('effectsStopped', (effects) => {
    startEffectButton.style.visibility = "visible"; 
});

イベント: effectsError

このイベントは、オーディオ効果の開始または適用中にエラーが発生したときに発生します。

アプリケーションはイベントにどのように反応しますか?

アプリケーションで、オーディオ効果が期待どおりに動作していないことを示すアラートまたはエラー メッセージを表示する必要があります。

コード サンプル:

audioEffectsFeature.on('effectsError', (error) => {
    console.log(`Error with the audio effect ${error}`);
    alert(`Error with the audio effect`);
});

SDK のインストール

プロジェクト レベルの build.gradle ファイルを見つけて、mavenCentral()buildscript の下のリポジトリの一覧に allprojects を追加します。

buildscript {
    repositories {
    ...
        mavenCentral()
    ...
    }
}
allprojects {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

次に、モジュール レベルの build.gradle ファイルで、次の行を dependencies セクションに追加します。

dependencies {
    ...
    implementation 'com.azure.android:azure-communication-calling:1.0.0'
    ...
}

必要なオブジェクトを初期化する

CallAgent インスタンスを作成するには、createCallAgent インスタンス上で CallClient メソッドを呼び出す必要があります。 この呼び出しは、CallAgent インスタンス オブジェクトを非同期に返します。

createCallAgent メソッドは、CommunicationUserCredentialをカプセル化する を引数として受け取ります。

DeviceManager にアクセスするには、まず callAgent インスタンスを作成する必要があります。 それから、CallClient.getDeviceManager メソッドを使用して DeviceManager を取得することができます。

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

呼び出し元の表示名を設定するには、この代替メソッドを使用します。

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();

Android SDK をインストールしたら、ほとんどのプロパティとコレクションをサブスクライブして、値が変更されたときに通知を受け取ることができます。

プロパティ

property changed イベントをサブスクライブするには:

// subscribe
PropertyChangedListener callStateChangeListener = new PropertyChangedListener()
{
    @Override
    public void onPropertyChanged(PropertyChangedEvent args)
    {
        Log.d("The call state has changed.");
    }
}
call.addOnStateChangedListener(callStateChangeListener);

//unsubscribe
call.removeOnStateChangedListener(callStateChangeListener);

同じクラス内で定義されているイベント リスナーを使用する場合は、リスナーを変数にバインドします。 リスナー メソッドを追加および削除するには、変数を引数として渡します。

リスナーを引数として直接渡すと、そのリスナーへの参照が失われます。 Java ではこれらのリスナーの新しいインスタンスが作成され、以前に作成されたものは参照されません。 これらは引き続き適切に起動しますが、参照がなくなったため削除できません。

コレクション

collection updated イベントをサブスクライブするには:

LocalVideoStreamsChangedListener localVideoStreamsChangedListener = new LocalVideoStreamsChangedListener()
{
    @Override
    public void onLocalVideoStreamsUpdated(LocalVideoStreamsEvent localVideoStreamsEventArgs) {
        Log.d(localVideoStreamsEventArgs.getAddedStreams().size());
        Log.d(localVideoStreamsEventArgs.getRemovedStreams().size());
    }
}
call.addOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);
// To unsubscribe
call.removeOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);

システムを設定する

次の手順のようにして、システムを設定します。

Xcode プロジェクトを作成する

Xcode で、新しい iOS プロジェクトを作成し、[単一ビュー アプリ] テンプレートを選択します。 この記事では SwiftUI フレームワークを使うので、[言語][Swift] に、[インターフェイス][SwiftUI] に設定する必要があります。

この記事では、テストは作成しません。 [Include Tests] チェック ボックスはオフにしてもかまいません。

Xcode 内にプロジェクトを作成するためのウィンドウを示すスクリーンショット。

CocoaPods を使用してパッケージと依存関係をインストールする

  1. この例のように、アプリケーション用の Podfile を作成します。

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. pod install を実行します。

  3. Xcode を使用して .xcworkspace を開きます。

マイクへのアクセスを要求する

デバイスのマイクにアクセスするには、NSMicrophoneUsageDescription を使用してアプリの情報プロパティ一覧を更新する必要があります。 関連付けられる値には、システムがユーザーにアクセスを要求するために使うダイアログに含まれる文字列を設定します。

プロジェクト ツリーの [Info.plist] エントリを右クリックし、[Open As]> を選択します。 最上位の <dict> セクションに以下の行を追加してから、ファイルを保存します。

<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>

アプリのフレームワークを設定する

プロジェクトの ContentView.swift ファイルを開きます。 ファイルの先頭に import 宣言を追加して、AzureCommunicationCalling ライブラリをインポートします。 さらに、AVFoundation をインポートします。 これは、コードでのオーディオ アクセス許可の要求に必要です。

import AzureCommunicationCalling
import AVFoundation

CallAgent を初期化する

CallAgent から CallClient インスタンスを作成するには、初期化された後に callClient.createCallAgent オブジェクトを非同期に返す CallAgent メソッドを使用する必要があります。

通話クライアントを作成するには、CommunicationTokenCredential オブジェクトを渡します。

import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
    let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
    userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
    updates("Couldn't created Credential object", false)
    initializationDispatchGroup!.leave()
    return
}

// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
    let newToken = self.tokenProvider!.fetchNewToken()
    onCompletion(newToken, nil)
}

作成した CommunicationTokenCredential オブジェクトを CallClient に渡し、表示名を設定します。

self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"

self.callClient!.createCallAgent(userCredential: userCredential!,
    options: callAgentOptions) { (callAgent, error) in
        if error == nil {
            print("Create agent succeeded")
            self.callAgent = callAgent
        } else {
            print("Create agent failed")
        }
})

iOS SDK をインストールしたら、ほとんどのプロパティとコレクションをサブスクライブして、値が変更されたときに通知を受け取ることができます。

プロパティ

property changed イベントをサブスクライブするには、次のコードを使用します。

call.delegate = self
// Get the property of the call state by getting on the call's state member
public func call(_ call: Call, didChangeState args: PropertyChangedEventArgs) {
{
    print("Callback from SDK when the call state changes, current state: " + call.state.rawValue)
}

// to unsubscribe
self.call.delegate = nil

コレクション

collection updated イベントをサブスクライブするには、次のコードを使用します。

call.delegate = self
// Collection contains the streams that were added or removed only
public func call(_ call: Call, didUpdateLocalVideoStreams args: LocalVideoStreamsUpdatedEventArgs) {
{
    print(args.addedStreams.count)
    print(args.removedStreams.count)
}
// to unsubscribe
self.call.delegate = nil

次のステップ