你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

订阅 SDK 事件

建议订阅通话 SDK 事件。 Azure 通信服务 SDK 是动态的,包含可能会随时间变化的属性。 可以订阅这些事件,以便提前收到任何更改的通知。 按照本文中的说明订阅 Azure 通信服务 SDK 事件。

Azure 通信通话 SDK 上的事件

本部分介绍应用可以订阅的事件和属性更改。 订阅这些事件可使应用了解调用 SDK 中的状态更改并相应地做出响应。

跟踪事件至关重要,因为它使应用程序的状态能够与 Azure 通信服务呼叫框架的状态保持同步。 跟踪事件有助于在不对 SDK 对象实现拉取机制的情况下对更改进行堆栈。

本节假设您已完成快速入门,或者您实现了一个可以拨打和接听通话的应用程序。 如果尚未完成入门指南,请参阅 向应用添加语音呼叫

JavaScript 调用 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 数组,其中包含从集合中删除的值。

集合订阅示例

在以下示例中,我们订阅了通话对象 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 发生变化时触发。 当通话状态从 connecting 变为 connected 时,通话的 ID 将发生变化。 在通话完成连接后,通话的 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 角色发生变化时触发。 例如,当 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;
});

事件:isSpeakingChanged

isSpeakingChanged 事件将在通话的主导发言人发生变化时触发。

应用程序对事件的反应如何?

应用程序 UI 必须优先显示成为主导发言人的 RemoteParticipant

代码示例:

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

事件:videoStreamsUpdated

videoStreamsUpdated 事件将在远程参与者在通话中添加或移除 VideoStream 时触发。

应用程序对事件的反应如何?

如果应用程序正在处理已删除的流,则应用程序必须停止处理。 添加新的流时,我们建议应用程序开始呈现或处理它。

代码示例:

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() 添加到 buildscriptallprojects 下的存储库列表中:

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”

在本文中,无需创建测试。 请随意清除“包括测试”复选框。

显示用于在 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 条目,然后选择“打开为...”“源代码”>。 将以下代码行添加到顶层 <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

后续步骤