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

订阅 SDK 事件

Azure 通信服务 SDK 是动态的,包含很多属性。 当这些属性更改时,作为开发人员,你可能想要了解何时发生了变更,更重要的是发生了哪些更改。 以下是操作方法!

Azure 通信通话 SDK 上的事件

本指南介绍应用可以订阅的各种事件或属性变化。 订阅这些事件可让应用了解通话 SDK 中的状态变化,并相应地做出反应。

跟踪事件至关重要,因为它使应用程序的状态能够与 ACSCalling 框架的状态保持同步,而无需你在 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 (incomimgCallEvent) => {
    try {
        // Store a reference to the call object
        incomingCall = incomimgCallEvent.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', () => {
    showdDisableCameraButton(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
        //   - unsubcribe 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";
});

RemoteParticipant 对象上的事件

事件:roleChanged

roleChanged 事件将在通话中的 RemotePartipant 角色发生变化时触发。 例如,当 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 应优先显示成为主导发言人的 RemotePartipant

代码示例:

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 实例,必须对 CallClient 实例调用 createCallAgent 方法。 此调用将异步返回 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

若要从 CallClient 创建 CallAgent 实例,必须使用 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

后续步骤