您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

快速入门:使用通信服务呼叫 SDKQuickstart: Use the Communication Services Calling SDK

使用通信服务呼叫 SDK 向应用添加语音和视频呼叫,开启 Azure 通信服务使用旅程。Get started with Azure Communication Services by using the Communication Services Calling SDK to add voice and video calling to your app.

先决条件Prerequisites

安装 SDKInstall the SDK

备注

本文档使用 ACS 通话 Web SDK。This document uses ACS Calling Web SDK.

使用 npm install 命令安装适用于 JavaScript 的 Azure 通信服务通话 SDK 和通用 SDK。Use the npm install command to install the Azure Communication Services calling and common SDKs for JavaScript.

npm install @azure/communication-common --save
npm install @azure/communication-calling --save

对象模型Object model

以下类和接口用于处理 Azure 通信服务通话 SDK 的某些主要功能:The following classes and interfaces handle some of the major features of the Azure Communication Services Calling SDK:

名称Name 说明Description
CallClient 通话 SDK 的主入口点。The main entry point to the Calling SDK.
CallAgent 用于启动和管理通话。Used to start and manage calls.
DeviceManager 用于管理媒体设备。Used to manage media devices.
AzureCommunicationTokenCredential 实现用于实例化 callAgentCommunicationTokenCredential 接口。Implements the CommunicationTokenCredential interface, which is used to instantiate callAgent.

初始化 CallClient 实例,创建 CallAgent 实例,以及访问 deviceManagerInitialize a CallClient instance, create a CallAgent instance, and access deviceManager

创建新的 CallClient 实例。Create a new CallClient instance. 可使用自定义选项(例如 Logger 实例)对其进行配置。You can configure it with custom options like a Logger instance.

有了 CallClient 实例后,你可以通过在 CallClient 实例上调用 createCallAgent 方法来创建 CallAgent 实例。When you have a CallClient instance, you can create a CallAgent instance by calling the createCallAgent method on the CallClient instance. 这将异步返回 CallAgent 实例对象。This asynchronously returns a CallAgent instance object.

createCallAgent 方法使用 CommunicationTokenCredential 作为参数。The createCallAgent method uses CommunicationTokenCredential as an argument. 它接受用户访问令牌It accepts a user access token.

可在 CallClient 实例上使用 getDeviceManager 方法来访问 deviceManagerYou can use the getDeviceManager method on the CallClient instance to access deviceManager.

// Set the logger's log level
setLogLevel('verbose');
// Redirect logger output to wherever desired. By default it logs to console
AzureLogger.log = (...args) => { console.log(...args) };
const userToken = '<user token>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional ACS user name'});
const deviceManager = await callClient.getDeviceManager()

拨打电话Place a call

若要创建并启动通话,请在 callAgent 上使用其中一个 API,并提供你通过通信服务标识 SDK 创建的用户。To create and start a call, use one of the APIs on callAgent and provide a user that you've created through the Communication Services identity SDK.

创建和发起呼叫的操作是同步的。Call creation and start are synchronous. 可以通过通话实例订阅通话事件。The call instance allows you to subscribe to call events.

向用户或 PSTN 发起一对 N 通话Place a 1:n call to a user or PSTN

若要呼叫另一个通信服务用户,请在 callAgent 上使用 startCall 方法,并传递你使用通信服务管理库创建的接收人的 CommunicationUserIdentifierTo call another Communication Services user, use the startCall method on callAgent and pass the recipient's CommunicationUserIdentifier that you created with the Communication Services administration library.

const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const oneToOneCall = callAgent.startCall([userCallee]);

若要向公共交换电话网络 (PSTN) 发起通话,请在 callAgent 上使用 startCall 方法并传递接收方的 PhoneNumberIdentifierTo place a call to a public switched telephone network (PSTN), use the startCall method on callAgent and pass the recipient's PhoneNumberIdentifier. 必须将通信服务资源配置为允许 PSTN 通话。Your Communication Services resource must be configured to allow PSTN calling.

呼叫 PSTN 号码时,需要指定备用呼叫方 ID。When you call a PSTN number, specify your alternate caller ID. 备用呼叫方 ID 是 PSTN 通话中用于标识呼叫方的电话号码(基于 E.164 标准)。An alternate caller ID is a phone number (based on the E.164 standard) that identifies the caller in a PSTN call. 这是通话接收方看到的来电的电话号码。It's the phone number the call recipient sees for an incoming call.

备注

PSTN 通话目前提供个人预览版。PSTN calling is currently in private preview. 若要进行访问,请申请早期采用者计划For access, apply to the early adopter program.

对于 1:1 通话,请使用以下代码:For a 1:1 call, use the following code:

const pstnCalee = { phoneNumber: '<ACS_USER_ID>' }
const alternateCallerId = {alternateCallerId: '<Alternate caller Id>'};
const oneToOneCall = callAgent.startCall([pstnCallee], {alternateCallerId});

对于 1:n 通话,请使用以下代码:For a 1:n call, use the following code:

const userCallee = { communicationUserId: <ACS_USER_ID> }
const pstnCallee = { phoneNumber: <PHONE_NUMBER>};
const alternateCallerId = {alternateCallerId: '<Alternate caller Id>'};
const groupCall = callAgent.startCall([userCallee, pstnCallee], {alternateCallerId});

发起启用相机的一对一通话Place a 1:1 call with video camera

重要

当前最多只能有一个传出本地视频流。There can currently be no more than one outgoing local video stream.

若要发起视频通话,必须使用 deviceManager 中的 getCameras() 方法枚举本地相机。To place a video call, you have to enumerate local cameras by using the getCameras() method in deviceManager.

选择相机后,请使用它来构造 LocalVideoStream 实例。After you select a camera, use it to construct a LocalVideoStream instance. videoOptions 中将该实例作为 localVideoStream 数组中的项传递到 startCall 方法。Pass it within videoOptions as an item within the localVideoStream array to the startCall method.

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const call = callAgent.startCall(['acsUserId'], placeCallOptions);

在通话接通后,它就会自动将来自所选相机的视频流发送给其他参与者。When your call connects, it automatically starts sending a video stream from the selected camera to the other participant. 这也适用于 Call.Accept() 视频选项和 CallAgent.join() 视频选项。This also applies to the Call.Accept() video options and CallAgent.join() video options.

加入群组通话Join a group call

备注

groupId 参数被视为系统元数据,Microsoft 可能会使用它来执行运行系统所需的操作。The groupId parameter is considered system metadata and may be used by Microsoft for operations that are required to run the system. 不要在 groupId 值中包含个人数据。Don't include personal data in the groupId value. Microsoft 不会将此参数视为个人数据,其内容可能会显示给 Microsoft 员工,也可能会被长期存储。Microsoft doesn't treat this parameter as personal data and its content may be visible to Microsoft employees or stored long-term.

groupId 参数要求数据采用 GUID 格式。The groupId parameter requires data to be in GUID format. 建议使用随机生成的 GUID,这些 GUID 在系统中不会被视为个人数据。We recommend using randomly generated GUIDs that aren't considered personal data in your systems.

若要发起新的群组通话或加入正在进行的群组通话,请使用 join 方法并传递带有 groupId 属性的对象。To start a new group call or join an ongoing group call, use the join method and pass an object with a groupId property. groupId 值必须为 GUID。The groupId value has to be a GUID.


const context = { groupId: <GUID>}
const call = callAgent.join(context);

加入 Teams 会议Join a Teams Meeting

备注

此 API 以预览状态提供给开发者,可能根据我们收到的反馈更改。This API is provided as a preview for developers and may change based on feedback that we receive. 请勿在生产环境中使用此 API。Do not use this API in a production environment. 若要使用此 API,请使用 ACS 通话 Web SDK 的 beta 版本To use this api please use 'beta' release of ACS Calling Web SDK

若要加入 Teams 会议,请使用 join 方法并传递会议链接或会议坐标。To join a Teams meeting, use the join method and pass a meeting link or a meeting's coordinates.

使用会议链接加入:Join by using a meeting link:

const locator = { meetingLink: <meeting link>}
const call = callAgent.join(locator);

使用会议坐标加入:Join by using meeting coordinates:

const locator = {
    threadId: <thread id>,
    organizerId: <organizer id>,
    tenantId: <tenant id>,
    messageId: <message id>
}
const call = callAgent.join(locator);

接听来电Receive an incoming call

当登录的标识收到来电时,callAgent 实例会发出 incomingCall 事件。The callAgent instance emits an incomingCall event when the logged-in identity receives an incoming call. 若要侦听此事件,请使用以下选项之一进行订阅:To listen to this event, subscribe by using one of these options:

const incomingCallHander = async (args: { incomingCall: IncomingCall }) => {
    const incomingCall = args.incomingCall; 
    // Get incoming call ID
    var incomingCallId = incomingCall.id
    // Get information about this Call. This API is provided as a preview for developers
    // and may change based on feedback that we receive. Do not use this API in a production environment.
    // To use this api please use 'beta' release of ACS Calling Web SDK
    var callInfo = incomingCall.info;

    // Get information about caller
    var callerInfo = incomingCall.callerInfo

    // Accept the call
    var call = await incomingCall.accept();

    // Reject the call
    incomingCall.reject();

    // Subscribe to callEnded event and get the call end reason
     incomingCall.on('callEnded', args => {
        console.log(args.callEndReason);
    });

    // callEndReason is also a property of IncomingCall
    var callEndReason = incomingCall.callEndReason;
};
callAgentInstance.on('incomingCall', incomingCallHander);

incomingCall 事件包括你可以接受或拒绝的一个 incomingCall 实例。The incomingCall event includes an incomingCall instance that you can accept or reject.

管理通话Manage calls

在通话期间,你可以访问通话属性并管理视频和音频设置。During a call, you can access call properties and manage video and audio settings.

检查通话属性Check call properties

获取通话的唯一 ID(字符串):Get the unique ID (string) for a call:

 const callId: string = call.id;

获取通话信息:Get information about the call:

备注

此 API 以预览状态提供给开发者,可能根据我们收到的反馈更改。This API is provided as a preview for developers and may change based on feedback that we receive. 请勿在生产环境中使用此 API。Do not use this API in a production environment. 若要使用此 API,请使用 ACS 通话 Web SDK 的 beta 版本To use this api please use 'beta' release of ACS Calling Web SDK

const callInfo = call.info;

通过检查“call”实例上的 remoteParticipants 集合了解通话中的其他参与者:Learn about other participants in the call by inspecting the remoteParticipants collection on the 'call' instance:

const remoteParticipants = call.remoteParticipants;

识别来电的呼叫方:Identify the caller of an incoming call:

const callerIdentity = call.callerInfo.identifier;

identifierCommunicationIdentifier 类型之一。identifier is one of the CommunicationIdentifier types.

获取通话状态:Get the state of a call:

const callState = call.state;

这会返回一个表示当前通话状态的字符串:This returns a string representing the current state of a call:

  • None:初始通话状态。None: Initial call state.
  • Connecting:拨打或接听电话后的初始过渡状态。Connecting: Initial transition state when a call is placed or accepted.
  • Ringing:对于去电,表示远程参与者的电话正在响铃。Ringing: For an outgoing call, indicates that a call is ringing for remote participants. 在远程参与者端,它是 IncomingIt's Incoming on their side.
  • EarlyMedia:表示在接通电话前播放通知的状态。EarlyMedia: Indicates a state in which an announcement is played before the call is connected.
  • Connected:指示通话已连接。Connected: Indicates that the call is connected.
  • LocalHold:指示通话被本地参与者暂停。LocalHold: Indicates that the call is put on hold by a local participant. 本地终结点与远程参与者之间没有媒体流动。No media is flowing between the local endpoint and remote participants.
  • RemoteHold:指示通话被远程参与者暂停。RemoteHold: Indicates that the call was put on hold by remote participant. 本地终结点与远程参与者之间没有媒体流动。No media is flowing between the local endpoint and remote participants.
  • InLobby:指示用户位于会议厅中。InLobby: Indicates that user is in lobby.
  • Disconnecting:在通话进入 Disconnected 状态之前的过渡状态。Disconnecting: Transition state before the call goes to a Disconnected state.
  • Disconnected:最终通话状态。Disconnected: Final call state. 如果网络连接断开,则两分钟后状态将变为 DisconnectedIf the network connection is lost, the state changes to Disconnected after two minutes.

检查 callEndReason 属性来查明通话结束的原因:Find out why a call ended by inspecting the callEndReason property:

const callEndReason = call.callEndReason;
const callEndReasonCode = callEndReason.code // (number) code associated with the reason
const callEndReasonSubCode = callEndReason.subCode // (number) subCode associated with the reason

通过检查 direction 属性了解当前通话是来电还是去电。Learn if the current call is incoming or outgoing by inspecting the direction property. 它将返回 CallDirectionIt returns CallDirection.

 const isIncoming = call.direction == 'Incoming';
 const isOutgoing = call.direction == 'Outgoing';

检查当前麦克风是否已静音。Check if the current microphone is muted. 它将返回 BooleanIt returns Boolean.

const muted = call.isMuted;

通过检查 isScreenSharingOn 属性了解屏幕共享流是否来自给定终结点。Find out if the screen sharing stream is being sent from a given endpoint by checking the isScreenSharingOn property. 它将返回 BooleanIt returns Boolean.

const isScreenSharingOn = call.isScreenSharingOn;

通过查看 localVideoStreams 集合检查活动视频流。Inspect active video streams by checking the localVideoStreams collection. 它返回 LocalVideoStream 对象。It returns LocalVideoStream objects.

const localVideoStreams = call.localVideoStreams;

静音和取消静音Mute and unmute

若要使本地终结点静音或取消静音,可使用 muteunmute 异步 API:To mute or unmute the local endpoint, you can use the mute and unmute asynchronous APIs:


//mute local device
await call.mute();

//unmute local device
await call.unmute();

开始和停止发送本地视频Start and stop sending local video

若要开始发送视频,必须在 deviceManager 对象上使用 getCameras 方法来枚举相机。To start a video, you have to enumerate cameras using the getCameras method on the deviceManager object. 接下来使用所需相机创建一个新的 LocalVideoStream 实例,然后将 LocalVideoStream 对象传递给 startVideo 方法:Then create a new instance of LocalVideoStream with the desired camera and then pass the LocalVideoStream object into the startVideo method:

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

当你成功开始发送视频后,系统会将一个 LocalVideoStream 实例添加到通话实例上的 localVideoStreams 集合。After you successfully start sending video, a LocalVideoStream instance is added to the localVideoStreams collection on a call instance.

call.localVideoStreams[0] === localVideoStream;

若要停止本地视频,请传递 localVideoStreams 集合中可用的 localVideoStream 实例:To stop local video, pass the localVideoStream instance that's available in the localVideoStreams collection:

await call.stopVideo(localVideoStream);

localVideoStream 实例调用 switchSource 来发送视频时,可切换到不同的相机设备:You can switch to a different camera device while a video is sending by invoking switchSource on a localVideoStream instance:

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

管理远程参与者Manage remote participants

所有远程参与者均以 RemoteParticipant 类型表示,可通过通话实例上的 remoteParticipants 集合获得。All remote participants are represented by RemoteParticipant type and available through remoteParticipants collection on a call instance.

列出通话参与者List the participants in a call

remoteParticipants 集合返回通话中远程参与者的列表:The remoteParticipants collection returns a list of remote participants in a call:

call.remoteParticipants; // [remoteParticipant, remoteParticipant....]

访问远程参与者属性Access remote participant properties

远程参与者有一组关联的属性和集合:Remote participants have a set of associated properties and collections:

  • CommunicationIdentifier:获取远程参与者的标识符。CommunicationIdentifier: Get the identifier for a remote participant. 标识是 CommunicationIdentifier 类型之一:Identity is one of the CommunicationIdentifier types:

    const identifier = remoteParticipant.identifier;
    

    它可能是下列 CommunicationIdentifier 类型之一:It can be one of the following CommunicationIdentifier types:

    • { communicationUserId: '<ACS_USER_ID'> }:一个对象,表示 ACS 用户。{ communicationUserId: '<ACS_USER_ID'> }: Object representing the ACS user.
    • { phoneNumber: '<E.164>' }:一个对象,表示采用 E.164 格式的电话号码。{ phoneNumber: '<E.164>' }: Object representing the phone number in E.164 format.
    • { microsoftTeamsUserId: '<TEAMS_USER_ID>', isAnonymous?: boolean; cloud?: "public" | "dod" | "gcch" }:一个对象,表示 Teams 用户。{ microsoftTeamsUserId: '<TEAMS_USER_ID>', isAnonymous?: boolean; cloud?: "public" | "dod" | "gcch" }: Object representing the Teams user.
    • { id: string }:一个对象,表示不属于其他标识符类型的标识符{ id: string }: object repredenting identifier that doesn't fit any of the other identifier types
  • state:获取远程参与者的状态。state: Get the state of a remote participant.

    const state = remoteParticipant.state;
    

    此状态可能是:The state can be:

    • Idle:初始状态。Idle: Initial state.
    • Connecting:参与者正在连接到通话时的过渡状态。Connecting: Transition state while a participant is connecting to the call.
    • Ringing:参与者电话正在响铃。Ringing: Participant is ringing.
    • Connected:参与者已连接到通话。Connected: Participant is connected to the call.
    • Hold:参与者已暂停通话。Hold: Participant is on hold.
    • EarlyMedia:在参与者连接到通话之前播放的通知。EarlyMedia: Announcement that plays before a participant connects to the call.
    • InLobby:指示远程参与者位于会议厅中。InLobby: Indicates that remote participant is in lobby.
    • Disconnected:最终状态。Disconnected: Final state. 参与者已断开通话连接。The participant is disconnected from the call. 如果远程参与者断开了其网络连接,则两分钟后其状态将变为 DisconnectedIf the remote participant loses their network connectivity, their state changes to Disconnected after two minutes.
  • callEndReason:若要了解参与者退出通话的原因,请检查 callEndReason 属性:callEndReason: To learn why a participant left the call, check the callEndReason property:

    const callEndReason = remoteParticipant.callEndReason;
    const callEndReasonCode = callEndReason.code // (number) code associated with the reason
    const callEndReasonSubCode = callEndReason.subCode // (number) subCode associated with the reason
    
  • isMuted 状态:若要了解远程参与者是否已静音,请检查 isMuted 属性。isMuted status: To find out if a remote participant is muted, check the isMuted property. 它将返回 BooleanIt returns Boolean.

    const isMuted = remoteParticipant.isMuted;
    
  • isSpeaking 状态:若要了解远程参与者是否正在讲话,请检查 isSpeaking 属性。isSpeaking status: To find out if a remote participant is speaking, check the isSpeaking property. 它将返回 BooleanIt returns Boolean.

    const isSpeaking = remoteParticipant.isSpeaking;
    
  • videoStreams:若要检查给定参与者在此通话中发送的所有视频流,请检查 videoStreams 集合。videoStreams: To inspect all video streams that a given participant is sending in this call, check the videoStreams collection. 它包含 RemoteVideoStream 对象。It contains RemoteVideoStream objects.

    const videoStreams = remoteParticipant.videoStreams; // [RemoteVideoStream, ...]
    
  • displayName:若要获取此远程参与者的显示名称,请检查 displayName 属性。它将返回字符串。displayName: To get display name for this remote participant, inspect displayName property it return string.

    const displayName = remoteParticipant.displayName;
    

向通话添加参与者Add a participant to a call

若要向通话添加参与者(用户或电话号码),可以使用 addParticipantTo add a participant (either a user or a phone number) to a call, you can use addParticipant. 提供 Identifier 类型之一。Provide one of the Identifier types. 它将同步返回 remoteParticipant 实例。It synchronously returns the remoteParticipant instance. 当参与者成功添加到通话中时,通话中将引发 remoteParticipantsUpdated 事件。The remoteParticipantsUpdated event from Call is raised when a participant is successfully added to the call.

const userIdentifier = { communicationUserId: <ACS_USER_ID> };
const pstnIdentifier = { phoneNumber: <PHONE_NUMBER>}
const remoteParticipant = call.addParticipant(userIdentifier);
const remoteParticipant = call.addParticipant(pstnIdentifier, {alternateCallerId: '<Alternate Caller ID>'});

删除通话参与者Remove a participant from a call

若要从通话中删除参与者(用户或电话号码),可以调用 removeParticipantTo remove a participant (either a user or a phone number) from a call, you can invoke removeParticipant. 你必须传递 Identifier 类型之一。You have to pass one of the Identifier types. 从通话中删除参与者后,就会以异步方式解决此问题。This resolves asynchronously after the participant is removed from the call. 还将从 remoteParticipants 集合中删除该参与者。The participant is also removed from the remoteParticipants collection.

const userIdentifier = { communicationUserId: <ACS_USER_ID> };
const pstnIdentifier = { phoneNumber: <PHONE_NUMBER>}
await call.removeParticipant(userIdentifier);
await call.removeParticipant(pstnIdentifier);

呈现远程参与者视频流Render remote participant video streams

若要列出远程参与者的视频流和屏幕共享流,请检查 videoStreams 集合:To list the video streams and screen sharing streams of remote participants, inspect the videoStreams collections:

const remoteVideoStream: RemoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType: MediaStreamType = remoteVideoStream.mediaStreamType;

若要呈现 RemoteVideoStream,你必须订阅它的 isAvailableChanged 事件。To render RemoteVideoStream, you have to subscribe to it's isAvailableChanged event. 如果该 isAvailable 属性更改为 true,则远程参与者正在发送流。If the isAvailable property changes to true, a remote participant is sending a stream. 发生该情况后,请创建一个新的 VideoStreamRenderer 实例,然后使用异步 createView 方法创建一个新的 VideoStreamRendererView 实例。After that happens, create a new instance of VideoStreamRenderer, and then create a new VideoStreamRendererView instance by using the asynchronous createView method. 然后,可以将 view.target 附加到任何 UI 元素。You can then attach view.target to any UI element.

每当远程流的可用性发生变化时,可以选择销毁整个 VideoStreamRenderer、特定的 VideoStreamRendererView,或将其保留,但这将导致显示空白的视频帧。Whenever availability of a remote stream changes you can choose to destroy the whole VideoStreamRenderer, a specific VideoStreamRendererView or keep them, but this will result in displaying blank video frame.

function subscribeToRemoteVideoStream(remoteVideoStream: RemoteVideoStream) {
    let videoStreamRenderer: VideoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    const displayVideo = () => {
        const view = await videoStreamRenderer.createView();
        htmlElement.appendChild(view.target);
    }
    remoteVideoStream.on('isAvailableChanged', async () => {
        if (remoteVideoStream.isAvailable) {
            displayVideo();
        } else {
            videoStreamRenderer.dispose();
        }
    });
    if (remoteVideoStream.isAvailable) {
        displayVideo();
    }
}

远程视频流属性Remote video stream properties

远程视频流具有以下属性:Remote video streams have the following properties:

  • id:远程视频流的 ID。id: The ID of a remote video stream.

    const id: number = remoteVideoStream.id;
    
  • mediaStreamType:可以是 VideoScreenSharingmediaStreamType: Can be Video or ScreenSharing.

    const type: MediaStreamType = remoteVideoStream.mediaStreamType;
    
  • isAvailable:指示远程参与者终结点是否正在主动发送流。isAvailable: Whether a remote participant endpoint is actively sending a stream.

    const type: boolean = remoteVideoStream.isAvailable;
    

VideoStreamRenderer 方法和属性VideoStreamRenderer methods and properties

创建一个 VideoStreamRendererView 实例,该实例可以附加到应用程序 UI 中来呈现远程视频流;使用异步 createView() 方法,该方法在流准备好呈现时进行解析,并返回一个具有 target 属性(表示 video 元素)的对象,该对象可以附加到 DOM 树中的任何位置Create a VideoStreamRendererView instance that can be attached in the application UI to render the remote video stream, use asynchronous createView() method, it resolves when stream is ready to render and returns an object with target property that represents video element that can be appended anywhere in the DOM tree

videoStreamRenderer.createView()

处置 videoStreamRenderer 和所有关联的 VideoStreamRendererView 实例:Dispose of videoStreamRenderer and all associated VideoStreamRendererView instances:

videoStreamRenderer.dispose()

VideoStreamRendererView 方法和属性VideoStreamRendererView methods and properties

创建 VideoStreamRendererView 时,你可以指定 scalingModeisMirrored 属性。When you create a VideoStreamRendererView, you can specify the scalingMode and isMirrored properties. scalingMode 可以是 StretchCropFitscalingMode can be Stretch, Crop, or Fit. 如果指定了 isMirrored,则呈现的流会垂直翻转。If isMirrored is specified, the rendered stream is flipped vertically.

const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });

每个 VideoStreamRendererView 实例都有一个表示呈现图面的 target 属性。Every VideoStreamRendererView instance has a target property that represents the rendering surface. 在应用程序 UI 中附加此属性:Attach this property in the application UI:

htmlElement.appendChild(view.target);

可以调用 updateScalingMode 方法来更新 scalingModeYou can update scalingMode by invoking the updateScalingMode method:

view.updateScalingMode('Crop')

设备管理Device management

deviceManager 中,你可以枚举可在通话中传输音频和视频流的本地设备。In deviceManager, you can enumerate local devices that can transmit your audio and video streams in a call. 还可使用它来请求访问本地设备的麦克风和相机的权限。You can also use it to request permission to access the local device's microphones and cameras.

可以调用 callClient.getDeviceManager() 方法来访问 deviceManagerYou can access deviceManager by calling the callClient.getDeviceManager() method:

const deviceManager = await callClient.getDeviceManager();

获取本地设备Get local devices

若要访问本地设备,可在 deviceManager 上使用枚举方法。To access local devices, you can use enumeration methods on deviceManager. 枚举是异步操作Enumeration is an asynchronous action

//  Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

设置默认麦克风和扬声器Set the default microphone and speaker

deviceManager 中,你可以设置将用来启动通话的默认设备。In deviceManager, you can set a default device that you'll use to start a call. 如果未设置客户端默认值,则通信服务会使用操作系统默认值。If client defaults aren't set, Communication Services uses operating system defaults.

// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;

// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);

// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);

本地相机预览Local camera preview

可使用 deviceManagerVideoStreamRenderer 开始呈现来自本地相机的流。You can use deviceManager and VideoStreamRenderer to begin rendering streams from your local camera. 此流不会发送给其他参与者;这是一项本地预览源。This stream won't be sent to other participants; it's a local preview feed.

const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localCameraStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localCameraStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

请求对相机和麦克风的权限Request permission to camera and microphone

提示用户授予相机和麦克风权限:Prompt a user to grant camera and microphone permissions:

const result = await deviceManager.askDevicePermission({audio: true, video: true});

这将使用一个对象进行解析,该对象指示是否授予了 audiovideo 权限:This resolves with an object that indicates whether audio and video permissions were granted:

console.log(result.audio);
console.log(result.video);

录制通话Record calls

备注

此 API 以预览状态提供给开发者,可能根据我们收到的反馈更改。This API is provided as a preview for developers and may change based on feedback that we receive. 请勿在生产环境中使用此 API。Do not use this API in a production environment. 若要使用此 API,请使用 ACS 通话 Web SDK 的 beta 版本To use this api please use 'beta' release of ACS Calling Web SDK

通话录制是核心 Call API 的扩展功能。Call recording is an extended feature of the core Call API. 首先需要获取录制功能 API 对象:You first need to obtain the recording feature API object:

const callRecordingApi = call.api(Features.Recording);

然后,若要检查是否正在录制通话,请检查 callRecordingApiisRecordingActive 属性。Then, to check if the call is being recorded, inspect the isRecordingActive property of callRecordingApi. 它将返回 BooleanIt returns Boolean.

const isResordingActive = callRecordingApi.isRecordingActive;

还可以订阅录制更改:You can also subscribe to recording changes:

const isRecordingActiveChangedHandler = () => {
  console.log(callRecordingApi.isRecordingActive);
};

callRecordingApi.on('isRecordingActiveChanged', isRecordingActiveChangedHandler);

转移呼叫Transfer calls

备注

此 API 以预览状态提供给开发者,可能根据我们收到的反馈更改。This API is provided as a preview for developers and may change based on feedback that we receive. 请勿在生产环境中使用此 API。Do not use this API in a production environment. 若要使用此 API,请使用 ACS 通话 Web SDK 的 beta 版本To use this api please use 'beta' release of ACS Calling Web SDK

呼叫转移是核心 Call API 的扩展功能。Call transfer is an extended feature of the core Call API. 首先需要获取转移功能 API 对象:You first need to get the transfer feature API object:

const callTransferApi = call.api(Features.Transfer);

呼叫转移涉及三个参与方:Call transfers involve three parties:

  • 转移方:发起转移请求的人员。Transferor: The person who initiates the transfer request.
  • 被转移方:被转移的人员。Transferee: The person who is being transferred.
  • 转移目标:被转移到的人员。Transfer target: The person who is being transferred to.

转移遵循以下步骤:Transfers follow these steps:

  1. 转移方和被转移方之间已经存在接通的电话。 There's already a connected call between the transferor and the transferee. 转移方决定将呼叫从被转移方转移到转移目标。The transferor decides to transfer the call from the transferee to the transfer target.
  2. 转移方调用 transfer API。The transferor calls the transfer API.
  3. 被转移方通过使用 transferRequested 事件来决定是 accept 还是 reject 到转移目标的转移请求。The transferee decides whether to accept or reject the transfer request to the transfer target by using a transferRequested event.
  4. 只有当被转移方接受转移请求时,转移目标才会收到来电。The transfer target receives an incoming call only if the transferee accepts the transfer request.

若要转移当前呼叫,可以使用 transfer API。To transfer a current call, you can use the transfer API. transfer 接受可选的 transferCallOptions,这允许你设置 disableForwardingAndUnanswered 标志:transfer takes the optional transferCallOptions, which allows you to set a disableForwardingAndUnanswered flag:

  • disableForwardingAndUnanswered = false:如果转移目标未接听转移的呼叫,则转移方将遵循转移目标的转发设置和未接听设置。 disableForwardingAndUnanswered = false: If the transfer target doesn't answer the transfer call, the transfer follows the transfer target forwarding and unanswered settings.
  • disableForwardingAndUnanswered = true:如果转移目标未接听转移的呼叫,则转移尝试结束。disableForwardingAndUnanswered = true: If the transfer target doesn't answer the transfer call, the transfer attempt ends.
// transfer target can be an ACS user
const id = { communicationUserId: <ACS_USER_ID> };
// call transfer API
const transfer = callTransferApi.transfer({targetParticipant: id});

transfer API 允许你订阅 transferStateChangedtransferRequested 事件。The transfer API allows you to subscribe to transferStateChanged and transferRequested events. transferRequested 事件来自 call 实例;transferStateChanged 事件与转移 stateerror 来自 transfer 实例。A transferRequested event comes from a call instance; a transferStateChanged event and transfer state and error come from a transfer instance.

// transfer state
const transferState = transfer.state; // None | Transferring | Transferred | Failed

// to check the transfer failure reason
const transferError = transfer.error; // transfer error code that describes the failure if a transfer request failed

被转移方可以通过在 transferRequestedEventArgs 中使用 accept()reject() 来接受或拒绝转移方在 transferRequested 事件中发起的转移请求。 The transferee can accept or reject the transfer request initiated by the transferor in the transferRequested event by using accept() or reject() in transferRequestedEventArgs. 你可以在 transferRequestedEventArgs 中访问 targetParticipant 信息和 acceptreject 方法。You can access targetParticipant information and accept or reject methods in transferRequestedEventArgs.

// Transferee to accept the transfer request
callTransferApi.on('transferRequested', args => {
  args.accept();
});

// Transferee to reject the transfer request
callTransferApi.on('transferRequested', args => {
  args.reject();
});

了解事件模型Learn about eventing models

检查当前值,并订阅更新事件以获取将来的值。Inspect current values and subscribe to update events for future values.

属性Properties

// Inspect the current value
console.log(object.property);

// Subscribe to value updates
object.on('propertyChanged', () => {
    // Inspect new value
    console.log(object.property)
});

// Unsubscribe from updates:
object.off('propertyChanged', () => {});



// Example for inspecting a call state
console.log(call.state);
call.on('stateChanged', () => {
    console.log(call.state);
});
call.off('stateChanged', () => {});

集合Collections

// Inspect the current collection
object.collection.forEach(v => {
    console.log(v);
});

// Subscribe to collection updates
object.on('collectionUpdated', e => {
    // Inspect new values added to the collection
    e.added.forEach(v => {
        console.log(v);
    });
    // Inspect values removed from the collection
    e.removed.forEach(v => {
        console.log(v);
    });
});

// Unsubscribe from updates:
object.off('collectionUpdated', () => {});

// Example for subscribing to remote participants and their video streams
call.remoteParticipants.forEach(p => {
    subscribeToRemoteParticipant(p);
})

call.on('remoteParticipantsUpdated', e => {
    e.added.forEach(p => { subscribeToRemoteParticipant(p) })
    e.removed.forEach(p => { unsubscribeFromRemoteParticipant(p) })
});

function subscribeToRemoteParticipant(p) {
    console.log(p.state);
    p.on('stateChanged', () => { console.log(p.state); });
    p.videoStreams.forEach(v => { subscribeToRemoteVideoStream(v) });
    p.on('videoStreamsUpdated', e => { e.added.forEach(v => { subscribeToRemoteVideoStream(v) }) })
}

重要

Azure 通信服务聊天 SDK(Android 和 iOS 版)目前提供公共预览版。The Azure Communication Services Android and iOS Calling SDKs are currently in public preview. 此预览版在提供时没有附带服务级别协议,不建议将其用于生产工作负荷。This preview version is provided without a service-level agreement, and it's not recommended for production workloads. 某些功能可能不受支持或者受限。Certain features might not be supported or might have constrained capabilities. 有关详细信息,请参阅 Microsoft Azure 预览版补充使用条款For more information, see Supplemental Terms of Use for Microsoft Azure Previews.

先决条件Prerequisites

设置Setting up

安装包Install the package

备注

本文档使用版本 1.0.0-beta.8 的通话 SDK。This document uses version 1.0.0-beta.8 of the Calling SDK.

找到项目级别 build.gradle,确保将 mavenCentral() 添加到 buildscriptallprojects 下的存储库列表中Locate your project level build.gradle and make sure to add mavenCentral() to the list of repositories under buildscript and allprojects

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

然后,在模块级别的 build.gradle 中,将以下行添加到 dependencies 部分Then, in your module level build.gradle add the following lines to the dependencies section

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

对象模型Object model

以下类和接口用于处理 Azure 通信服务通话 SDK 的某些主要功能:The following classes and interfaces handle some of the major features of the Azure Communication Services Calling SDK:

名称Name 说明Description
CallClientCallClient CallClient 是通话 SDK 的主入口点。The CallClient is the main entry point to the Calling SDK.
CallAgentCallAgent CallAgent 用于启动和管理呼叫。The CallAgent is used to start and manage calls.
CommunicationTokenCredentialCommunicationTokenCredential CommunicationTokenCredential 用作实例化 CallAgent 的令牌凭据。The CommunicationTokenCredential is used as the token credential to instantiate the CallAgent.
CommunicationIdentifierCommunicationIdentifier CommunicationIdentifier 用作可参与通话的不同类型的参与者。The CommunicationIdentifier is used as different type of participant that would could be part of a call.

初始化 CallClient、创建 CallAgent 和访问 DeviceManagerInitialize the CallClient, create a CallAgent, and access the DeviceManager

若要创建 CallAgent 实例,必须对 CallClient 实例调用 createCallAgent 方法。To create a CallAgent instance you have to call the createCallAgent method on a CallClient instance. 这将异步返回 CallAgent 实例对象。This asynchronously returns a CallAgent instance object. createCallAgent 方法采用 CommunicationUserCredential 作为参数来封装访问令牌The createCallAgent method takes a CommunicationUserCredential as an argument, which encapsulates an access token. 若要访问 DeviceManager,必须先创建一个 callAgent 实例,然后可使用 CallClient.getDeviceManager 方法获取 DeviceManager。To access the DeviceManager, a callAgent instance must be created first, and then you can use the CallClient.getDeviceManager method to get the 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().get();

若要为主叫方设置显示名称,请使用以下替代方法:To set a display name for the caller, use this alternative method:

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");
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();
DeviceManager deviceManager = callClient.getDeviceManager().get();

拨出呼叫和加入群组通话Place an outgoing call and join a group call

若要创建和发起呼叫,需要调用 CallAgent.startCall() 方法并为被叫方提供 IdentifierTo create and start a call you need to call the CallAgent.startCall() method and provide the Identifier of the callee(s). 若要加入群组通话,需要调用 CallAgent.join() 方法并提供 groupId。To join a group call you need to call the CallAgent.join() method and provide the groupId. 组 ID 必须采用 GUID 或 UUID 格式。Group Ids must be in GUID or UUID format.

创建和发起呼叫的操作是同步的。Call creation and start are synchronous. 通过呼叫实例,可订阅呼叫中的所有事件。The call instance allows you to subscribe to all events on the call.

向用户发出一对一呼叫Place a 1:1 call to a user

若要向另一位通信服务用户发出呼叫,请对 callAgent 调用 call 方法,并传递带有 communicationUserId 键的对象。To place a call to another Communication Services user, invoke the call method on callAgent and pass an object with communicationUserId key.

StartCallOptions startCallOptions = new StartCallOptions();
Context appContext = this.getApplicationContext();
CommunicationUserIdentifier acsUserId = new CommunicationUserIdentifier(<USER_ID>);
CommunicationUserIdentifier participants[] = new CommunicationUserIdentifier[]{ acsUserId };
call oneToOneCall = callAgent.startCall(appContext, participants, startCallOptions);

向用户和 PSTN 发起一对多通话Place a 1:n call with users and PSTN

警告

PSTN 呼叫当前不可用Currently PSTN calling is not available

若要对用户和 PSTN 号码发出一对多呼叫,必须指定被叫方的电话号码。To place a 1:n call to a user and a PSTN number you have to specify the phone number of callee. 必须将通信服务资源配置为允许 PSTN 呼叫:Your Communication Services resource must be configured to allow PSTN calling:

CommunicationUserIdentifier acsUser1 = new CommunicationUserIdentifier(<USER_ID>);
PhoneNumberIdentifier acsUser2 = new PhoneNumberIdentifier("<PHONE_NUMBER>");
CommunicationIdentifier participants[] = new CommunicationIdentifier[]{ acsUser1, acsUser2 };
StartCallOptions startCallOptions = new StartCallOptions();
Context appContext = this.getApplicationContext();
Call groupCall = callAgent.startCall(participants, startCallOptions);

发起启用相机的一对一通话Place a 1:1 call with video camera

警告

目前仅支持一个传出本地视频流。若要发出带有视频的呼叫,必须使用 deviceManager getCameras API 枚举本地相机。Currently only one outgoing local video stream is supported To place a call with video you have to enumerate local cameras using the deviceManager getCameras API. 选择所需相机后,用它来构造一个 LocalVideoStream 实例,并将其作为 call 方法的 localVideoStream 数组中的项目传递给 videoOptionsOnce you select a desired camera, use it to construct a LocalVideoStream instance and pass it into videoOptions as an item in the localVideoStream array to a call method. 呼叫接通后,会自动开始将视频流从所选相机发送给其他的参与者。Once the call connects it'll automatically start sending a video stream from the selected camera to other participant(s).

备注

出于隐私考虑,如果未在本地预览视频,则不在呼叫中共享该视频。Due to privacy concerns, video will not be shared to the call if it is not being previewed locally. 有关更多详细信息,请查看本地相机预览See Local camera preview for more details.

Context appContext = this.getApplicationContext();
VideoDeviceInfo desiredCamera = callClient.getDeviceManager().get().getCameras().get(0);
LocalVideoStream currentVideoStream = new LocalVideoStream(desiredCamera, appContext);
VideoOptions videoOptions = new VideoOptions(currentVideoStream);

// Render a local preview of video so the user knows that their video is being shared
Renderer previewRenderer = new Renderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new RenderingOptions(ScalingMode.Fit));
// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

CommunicationUserIdentifier[] participants = new CommunicationUserIdentifier[]{ new CommunicationUserIdentifier("<acs user id>") };
StartCallOptions startCallOptions = new StartCallOptions();
startCallOptions.setVideoOptions(videoOptions);
Call call = callAgent.startCall(context, participants, startCallOptions);

加入群组通话Join a group call

若要发起新的群组通话或加入正在进行的群组通话,必须调用“join”方法并传递带有 groupId 属性的对象。To start a new group call or join an ongoing group call you have to call the 'join' method and pass an object with a groupId property. 该值必须为 GUID。The value has to be a GUID.

Context appContext = this.getApplicationContext();
GroupCallLocator groupCallLocator = new GroupCallLocator("<GUID>");
JoinCallOptions joinCallOptions = new JoinCallOptions();

call = callAgent.join(context, groupCallLocator, joinCallOptions);

接受呼叫Accept a call

若要接听来电,请对呼叫对象调用“accept”方法。To accept a call, call the 'accept' method on a call object.

Context appContext = this.getApplicationContext();
IncomingCall incomingCall = retrieveIncomingCall();
Call call = incomingCall.accept(context).get();

若要接受启用了摄像机的呼叫:To accept a call with video camera on:

Context appContext = this.getApplicationContext();
IncomingCall incomingCall = retrieveIncomingCall();
AcceptCallOptions acceptCallOptions = new AcceptCallOptions();
VideoDeviceInfo desiredCamera = callClient.getDeviceManager().get().getCameraList().get(0);
acceptCallOptions.setVideoOptions(new VideoOptions(new LocalVideoStream(desiredCamera, appContext)));
Call call = incomingCall.accept(context, acceptCallOptions).get();

可订阅 callAgent 对象上的 onIncomingCall 事件来获取来电:The incoming call can be obtained by subscribing to the onIncomingCall event on the callAgent object:

// Assuming "callAgent" is an instance property obtained by calling the 'createCallAgent' method on CallClient instance 
public Call retrieveIncomingCall() {
    IncomingCall incomingCall;
    callAgent.addOnIncomingCallListener(new IncomingCallListener() {
        void onIncomingCall(IncomingCall inboundCall) {
            // Look for incoming call
            incomingCall = inboundCall;
        }
    });
    return incomingCall;
}

推送通知Push notifications

概述Overview

移动推送通知是移动设备上显示的弹出通知。Mobile push notifications are the pop-up notifications you see on mobile devices. 对于呼叫,我们将重点介绍 VoIP(Internet 语音协议)推送通知。For calling, we'll be focusing on VoIP (Voice over Internet Protocol) push notifications. 我们将注册推送通知、处理推送通知,然后取消注册推送通知。We'll register for push notifications, handle push notifications, and then un-register push notifications.

先决条件Prerequisites

一个 Firebase 帐户,它设置为启用 Cloud Messaging (FCM) 并将 Firebase Cloud Messaging 服务连接到 Azure 通知中心实例。A Firebase account set up with Cloud Messaging (FCM) enabled and with your Firebase Cloud Messaging service connected to an Azure Notification Hub instance. 有关更多详细信息,请参阅通信服务通知See Communication Services notifications for more information. 此外,本教程假定你使用 Android Studio 3.6 或更高版本来构建应用程序。Additionally, the tutorial assumes you're using Android Studio version 3.6 or higher to build your application.

Android 应用程序需要一组权限才能接收来自 Firebase Cloud Messaging 的通知消息。A set of permissions is required for the Android application in order to be able to receive notifications messages from Firebase Cloud Messaging. AndroidManifest.xml 文件中,在 <manifest ...> 或 标记下添加以下权限集 In your AndroidManifest.xml file, add the following set of permissions right after the <manifest ...> or below the tag

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

注册推送通知Register for push notifications

若要注册推送通知,应用程序需要使用设备注册令牌在 CallAgent 实例上调用 registerPushNotification()To register for push notifications, the application needs to call registerPushNotification() on a CallAgent instance with a device registration token.

若要获取设备注册令牌,请在 dependencies 部分添加以下行(如果尚不存在),将 Firebase SDK 添加到应用程序模块的 build.gradle 文件中:To obtain the device registration token, add the Firebase SDK to your application module's build.gradle file by adding the following lines in the dependencies section if it's not already there:

    // Add the SDK for Firebase Cloud Messaging
    implementation 'com.google.firebase:firebase-core:16.0.8'
    implementation 'com.google.firebase:firebase-messaging:20.2.4'

在项目级别的 build.gradle 文件中,将以下内容添加到 dependencies 部分(如果尚不存在):In your project level's build.gradle file, add the following in the dependencies section if it's not already there:

    classpath 'com.google.gms:google-services:4.3.3'

在文件的开头添加以下插件(如果尚不存在):Add the following plugin to the beginning of the file if it's not already there:

apply plugin: 'com.google.gms.google-services'

在工具栏中选择“立即同步”。Select Sync Now in the toolbar. 添加以下代码片段,获取 Firebase Cloud Messaging SDK 为客户端应用程序实例生成的设备注册令牌。请务必将以下导入内容添加到实例主“活动”的标头中。Add the following code snippet to get the device registration token generated by the Firebase Cloud Messaging SDK for the client application instance Be sure to add the below imports to the header of the main Activity for the instance. 代码片段检索令牌时需要这些内容:They're required for the snippet to retrieve the token:

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;

添加此代码片段以检索令牌:Add this snippet to retrieve the token:

        FirebaseInstanceId.getInstance().getInstanceId()
                .addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
                    @Override
                    public void onComplete(@NonNull Task<InstanceIdResult> task) {
                        if (!task.isSuccessful()) {
                            Log.w("PushNotification", "getInstanceId failed", task.getException());
                            return;
                        }

                        // Get new Instance ID token
                        String deviceToken = task.getResult().getToken();
                        // Log
                        Log.d("PushNotification", "Device Registration token retrieved successfully");
                    }
                });

在通话服务 SDK 中注册设备注册令牌,以接收来电推送通知:Register the device registration token with the Calling Services SDK for incoming call push notifications:

String deviceRegistrationToken = "<Device Token from previous section>";
try {
    callAgent.registerPushNotification(deviceRegistrationToken).get();
}
catch(Exception e) {
    System.out.println("Something went wrong while registering for Incoming Calls Push Notifications.")
}

处理推送通知Push notification handling

若要接收来电推送通知,请使用有效负载在 CallAgent 实例上调用 handlePushNotification() 。To receive incoming call push notifications, call handlePushNotification() on a CallAgent instance with a payload.

若要从 Firebase Cloud Messaging 获取有效负载,请先创建一个新服务(“文件”>“新建”>“服务”>“服务”),该服务可扩展 FirebaseMessagingService Firebase SDK 类并替代 onMessageReceived 方法。To obtain the payload from Firebase Cloud Messaging, begin by creating a new Service (File > New > Service > Service) that extends the FirebaseMessagingService Firebase SDK class and override the onMessageReceived method. 当 Firebase Cloud Messaging 将推送通知传递到应用程序时,此方法被称为事件处理程序。This method is the event handler called when Firebase Cloud Messaging delivers the push notification to the application.

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    private java.util.Map<String, String> pushNotificationMessageDataFromFCM;

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // Check if message contains a notification payload.
        if (remoteMessage.getNotification() != null) {
            Log.d("PushNotification", "Message Notification Body: " + remoteMessage.getNotification().getBody());
        }
        else {
            pushNotificationMessageDataFromFCM = remoteMessage.getData();
        }
    }
}

将以下服务定义添加到 AndroidManifest.xml 文件的 标记内:Add the following service definition to the AndroidManifest.xml file, inside the tag:

        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
  • 检索到有效负载后,可将其传递给通信服务 SDK 以解析为内部 IncomingCallInformation 对象,该对象将通过在 CallAgent 实例上调用 handlePushNotification 方法进行处理 。Once the payload is retrieved, it can be passed to the Communication Services SDK to be parsed out into an internal IncomingCallInformation object that will be handled by calling the handlePushNotification method on a CallAgent instance. CallAgent 实例是通过在 CallClient 类上调用 createCallAgent(...) 方法来创建的。A CallAgent instance is created by calling the createCallAgent(...) method on the CallClient class.
try {
    IncomingCallInformation notification = IncomingCallInformation.fromMap(pushNotificationMessageDataFromFCM);
    Future handlePushNotificationFuture = callAgent.handlePushNotification(notification).get();
}
catch(Exception e) {
    System.out.println("Something went wrong while handling the Incoming Calls Push Notifications.");
}

成功处理推送通知消息,且正确注册所有事件处理程序时,应用程序将响铃。When the handling of the Push notification message is successful, and the all events handlers are registered properly, the application will ring.

取消注册推送通知Unregister push notifications

应用程序可以随时取消注册推送通知。Applications can unregister push notification at any time. 若要取消注册,请在 callAgent 上调用 unregisterPushNotification() 方法。Call the unregisterPushNotification() method on callAgent to unregister.

try {
    callAgent.unregisterPushNotifications().get();
}
catch(Exception e) {
    System.out.println("Something went wrong while un-registering for all Incoming Calls Push Notifications.")
}

通话管理Call Management

可在通话过程中访问通话属性并执行各种操作来管理与视频和音频相关的设置。You can access call properties and perform various operations during a call to manage settings related to video and audio.

通话属性Call properties

获取此呼叫的唯一 ID:Get the unique ID for this Call:

String callId = call.getId();

若要了解呼叫中的其他参与者,请检查 call 实例上的 remoteParticipant 集合:To learn about other participants in the call inspect remoteParticipant collection on the call instance:

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();

主叫方的标识(如果是来电):The identity of caller if the call is incoming:

CommunicationIdentifier callerId = call.getCallerId();

获取呼叫的状态:Get the state of the Call:

CallState callState = call.getState();

它会返回一个表示当前呼叫状态的字符串:It returns a string representing the current state of a call:

  • “None”- 初始通话状态'None' - initial call state
  • “Connecting”- 拨打或接听电话后的初始过渡状态'Connecting' - initial transition state once call is placed or accepted
  • “Ringing”- 对于去电,表示远程参与者的电话正在响铃'Ringing' - for an outgoing call - indicates call is ringing for remote participants
  • “EarlyMedia”- 表示在接通电话前播放通知的状态'EarlyMedia' - indicates a state in which an announcement is played before call is connected
  • “Connected”- 已接通电话'Connected' - call is connected
  • “LocalHold”- 本地参与者暂停通话,本地终结点与远程参与者之间没有媒体传输'LocalHold' - call is put on hold by local participant, no media is flowing between local endpoint and remote participant(s)
  • “RemoteHold”- 远程参与者暂停通话,本地终结点与远程参与者之间没有媒体传输'RemoteHold' - call is put on hold by a remote participant, no media is flowing between local endpoint and remote participant(s)
  • “Disconnecting”- 通话进入“Disconnected”状态前的过渡状态'Disconnecting' - transition state before call goes to 'Disconnected' state
  • “Disconnected”- 最终通话状态'Disconnected' - final call state

若要了解呼叫结束的原因,请检查 callEndReason 属性。To learn why a call ended, inspect callEndReason property. 它包含代码/子代码:It contains code/subcode:

CallEndReason callEndReason = call.getCallEndReason();
int code = callEndReason.getCode();
int subCode = callEndReason.getSubCode();

若要查看当前呼叫是来电还是去电,请检查 callDirection 属性:To see if the current call is an incoming or an outgoing call, inspect callDirection property:

CallDirection callDirection = call.getCallDirection(); 
// callDirection == CallDirection.Incoming for incoming call
// callDirection == CallDirection.Outgoing for outgoing call

若要查看当前麦克风是否静音,请检查 muted 属性:To see if the current microphone is muted, inspect the muted property:

boolean muted = call.getIsMicrophoneMuted();

若要查看是否正在录制当前呼叫,请检查 isRecordingActive 属性:To see if the current call is being recorded, inspect the isRecordingActive property:

boolean recordinggActive = call.getIsRecordingActive();

若要检查活动视频流,请查看 localVideoStreams 集合:To inspect active video streams, check the localVideoStreams collection:

List<LocalVideoStream> localVideoStreams = call.getLocalVideoStreams();

静音和取消静音Mute and unmute

若要使本地终结点静音或取消静音,可使用 muteunmute 异步 API:To mute or unmute the local endpoint you can use the mute and unmute asynchronous APIs:

call.mute().get();
call.unmute().get();

开始和停止发送本地视频Start and stop sending local video

若要开始发送视频,必须在 deviceManager 对象上使用 getCameraList API 来枚举相机。To start a video, you have to enumerate cameras using the getCameraList API on deviceManager object. 然后,创建 LocalVideoStream 的新实例传递所需的相机,并将其作为参数传递给 startVideo API:Then create a new instance of LocalVideoStream passing the desired camera, and pass it in the startVideo API as an argument:

VideoDeviceInfo desiredCamera = <get-video-device>;
Context appContext = this.getApplicationContext();
LocalVideoStream currentLocalVideoStream = new LocalVideoStream(desiredCamera, appContext);
VideoOptions videoOptions = new VideoOptions(currentLocalVideoStream);
Future startVideoFuture = call.startVideo(currentLocalVideoStream);
startVideoFuture.get();

成功开始发送视频后,LocalVideoStream 实例将被添加到呼叫实例上的 localVideoStreams 集合中。Once you successfully start sending video, a LocalVideoStream instance will be added to the localVideoStreams collection on the call instance.

currentLocalVideoStream == call.getLocalVideoStreams().get(0);

若要停止本地视频,请传递 localVideoStreams 集合中可用的 LocalVideoStream 实例:To stop local video, pass the LocalVideoStream instance available in localVideoStreams collection:

call.stopVideo(currentLocalVideoStream).get();

LocalVideoStream 实例调用 switchSource 来发送视频时,可切换到不同的相机设备:You can switch to a different camera device while video is being sent by invoking switchSource on a LocalVideoStream instance:

currentLocalVideoStream.switchSource(source).get();

远程参与者管理Remote participants management

所有远程参与者均以 RemoteParticipant 类型表示,可通过呼叫实例上的 remoteParticipants 集合获得。All remote participants are represented by RemoteParticipant type and are available through the remoteParticipants collection on a call instance.

列出通话参与者List participants in a call

remoteParticipants 集合会返回给定通话中远程参与者的列表:The remoteParticipants collection returns a list of remote participants in given call:

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants(); // [remoteParticipant, remoteParticipant....]

远程参与者属性Remote participant properties

任何给定的远程参与者都具有与之关联的一组属性和集合:Any given remote participant has a set of properties and collections associated with it:

  • 获取此远程参与者的标识符。Get the identifier for this remote participant. 标识其中一种“标识符”类型Identity is one of the 'Identifier' types
CommunicationIdentifier participantIdentifier = remoteParticipant.getIdentifier();
  • 获取此远程参与者的状态。Get state of this remote participant.
ParticipantState state = remoteParticipant.getState();

状态可以是下列其中一项State can be one of

  • “Idle”- 初始状态'Idle' - initial state

  • “EarlyMedia”- 在参与者接通电话前播放通知'EarlyMedia' - announcement is played before participant is connected to the call

  • “Ringing”- 参与者电话正在响铃'Ringing' - participant call is ringing

  • “Connecting”- 参与者正在连接到通话时的过渡状态'Connecting' - transition state while participant is connecting to the call

  • “Connected”- 参与者已接通电话'Connected' - participant is connected to the call

  • “Connected”- 参与者已暂停通话'Hold' - participant is on hold

  • “InLobby”- 参与者正在大厅中等待许可。'InLobby' - participant is waiting in the lobby to be admitted. 当前仅在 Teams 互操作方案中使用Currently only used in Teams interop scenario

  • “Disconnected”- 最终状态:参与者已与通话断开连接'Disconnected' - final state - participant is disconnected from the call

  • 若要了解参与者退出通话的原因,请检查 callEndReason 属性:To learn why a participant left the call, inspect callEndReason property:

CallEndReason callEndReason = remoteParticipant.getCallEndReason();
  • 若要检查此远程参与者是否已静音,请检查 isMuted 属性:To check whether this remote participant is muted or not, inspect the isMuted property:
boolean isParticipantMuted = remoteParticipant.getIsMuted();
  • 若要检查此远程参与者是否正在讲话,请检查 isSpeaking 属性:To check whether this remote participant is speaking or not, inspect the isSpeaking property:
boolean isParticipantSpeaking = remoteParticipant.getIsSpeaking();
  • 若要检查给定参与者在此呼叫中发送的所有视频流,请检查 videoStreams 集合:To inspect all video streams that a given participant is sending in this call, check the videoStreams collection:
List<RemoteVideoStream> videoStreams = remoteParticipant.getVideoStreams(); // [RemoteVideoStream, RemoteVideoStream, ...]

向通话添加参与者Add a participant to a call

若要向通话添加参与者(用户或电话号码),可调用 addParticipantTo add a participant to a call (either a user or a phone number) you can invoke addParticipant. 这会以同步方式返回远程参与者实例。This will synchronously return the remote participant instance.

const acsUser = new CommunicationUserIdentifier("<acs user id>");
const acsPhone = new PhoneNumberIdentifier("<phone number>");
RemoteParticipant remoteParticipant1 = call.addParticipant(acsUser);
AddPhoneNumberOptions addPhoneNumberOptions = new AddPhoneNumberOptions(new PhoneNumberIdentifier("<alternate phone number>"));
RemoteParticipant remoteParticipant2 = call.addParticipant(acsPhone, addPhoneNumberOptions);

删除通话参与者Remove participant from a call

若要删除通话参与者(用户或电话号码),可调用 removeParticipantTo remove a participant from a call (either a user or a phone number) you can invoke removeParticipant. 一旦从呼叫中删除参与者,就会异步解决此问题。This will resolve asynchronously once the participant is removed from the call. 这还将从 remoteParticipants 集合中删除该参与者。The participant will also be removed from remoteParticipants collection.

RemoteParticipant acsUserRemoteParticipant = call.getParticipants().get(0);
RemoteParticipant acsPhoneRemoteParticipant = call.getParticipants().get(1);
call.removeParticipant(acsUserRemoteParticipant).get();
call.removeParticipant(acsPhoneRemoteParticipant).get();

呈现远程参与者视频流Render remote participant video streams

若要列出远程参与者的视频流和屏幕共享流,请检查 videoStreams 集合:To list the video streams and screen sharing streams of remote participants, inspect the videoStreams collections:

RemoteParticipant remoteParticipant = call.getRemoteParticipants().get(0);
RemoteVideoStream remoteParticipantStream = remoteParticipant.getVideoStreams().get(0);
MediaStreamType streamType = remoteParticipantStream.getType(); // of type MediaStreamType.Video or MediaStreamType.ScreenSharing

若要呈现来自远程参与者的 RemoteVideoStream,必须订阅 OnVideoStreamsUpdated 事件。To render a RemoteVideoStream from a remote participant, you have to subscribe to a OnVideoStreamsUpdated event.

在此事件中,将 isAvailable 属性更改为 true 表示远程参与者当前正在发送流。Within the event, the change of isAvailable property to true indicates that remote participant is currently sending a stream. 发生此情况后,请创建 Renderer 的新实例,然后使用异步 createView API 创建新的 RendererView,并在应用程序 UI 中的任意位置附加 view.targetOnce that happens, create new instance of a Renderer, then create a new RendererView using asynchronous createView API and attach view.target anywhere in the UI of your application.

当远程流的可用性发生变化时,可选择销毁整个呈现器、销毁特定的 RendererView,也可保留它们,但这将导致显示空白的视频帧。Whenever availability of a remote stream changes you can choose to destroy the whole Renderer, a specific RendererView or keep them, but this will result in displaying blank video frame.

Renderer remoteVideoRenderer = new Renderer(remoteParticipantStream, appContext);
View uiView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.Fit));
layout.addView(uiView);

remoteParticipant.addOnVideoStreamsUpdatedListener(e -> onRemoteParticipantVideoStreamsUpdated(p, e));

void onRemoteParticipantVideoStreamsUpdated(RemoteParticipant participant, RemoteVideoStreamsEvent args) {
    for(RemoteVideoStream stream : args.getAddedRemoteVideoStreams()) {
        if(stream.getIsAvailable()) {
            startRenderingVideo();
        } else {
            renderer.dispose();
        }
    }
}

远程视频流属性Remote video stream properties

远程视频流具有几个属性Remote video stream has couple of properties

  • Id - 远程视频流的 IDId - ID of a remote video stream
int id = remoteVideoStream.getId();
  • MediaStreamType - 可以是“Video”或“ScreenSharing”MediaStreamType - Can be 'Video' or 'ScreenSharing'
MediaStreamType type = remoteVideoStream.getType();
  • isAvailable - 指示远程参与者终结点是否正在主动发送流isAvailable - Indicates if remote participant endpoint is actively sending stream
boolean availability = remoteVideoStream.getIsAvailable();

呈现器方法和属性Renderer methods and properties

采用 API 的呈现器对象Renderer object following APIs

  • 创建一个 RendererView 实例,随后可将其附加到应用程序 UI 中来呈现远程视频流。Create a RendererView instance that can be later attached in the application UI to render remote video stream.
// Create a view for a video stream
renderer.createView()
  • 处置呈现器及其所有相关 RendererViewDispose renderer and all RendererView associated with this renderer. 从 UI 中删除所有关联视图后,系统会调用它。To be called when you have removed all associated views from the UI.
renderer.dispose()
  • StreamSize - 远程视频流的大小(宽度/高度)StreamSize - size (width/height) of a remote video stream
StreamSize renderStreamSize = remoteVideoStream.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

RendererView 方法和属性RendererView methods and properties

创建 RendererView 时,可指定将应用于此视图的 scalingModemirrored 属性:缩放模式可以是“拉伸”、“裁剪”或“拟合”。如果将 mirrored 设置为 true,则呈现的流将垂直翻转。When creating a RendererView you can specify the scalingMode and mirrored properties that will apply to this view: Scaling mode can be either of 'Stretch' | 'Crop' | 'Fit' If mirrored is set to true, the rendered stream will be flipped vertically.

Renderer remoteVideoRenderer = new Renderer(remoteVideoStream, appContext);
RendererView rendererView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.Fit));

然后,可使用以下代码片段将创建的 RendererView 附加到应用程序 UI:The created RendererView can then be attached to the application UI using the following snippet:

layout.addView(rendererView);

稍后可在 RendererView 对象上调用 updateScalingMode API,并将 ScalingMode.Stretch、ScalingMode.Crop 或 ScalingMode.Fit 作为参数来更新缩放模式。You can later update the scaling mode by invoking updateScalingMode API on the RendererView object with one of ScalingMode.Stretch | ScalingMode.Crop | ScalingMode.Fit as an argument.

// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.Crop)

设备管理Device management

借助 DeviceManager,可枚举能够在通话中用于传输音频/视频流的本地设备。DeviceManager lets you enumerate local devices that can be used in a call to transmit your audio/video streams. 你还可用它来通过本机浏览器 API 向用户请求访问其麦克风和相机的权限。It also allows you to request permission from a user to access their microphone and camera using the native browser API.

可调用 callClient.getDeviceManager() 方法来访问 deviceManagerYou can access deviceManager by calling callClient.getDeviceManager() method.

警告

当前必须先实例化 callAgent 对象才能获得 DeviceManager 的访问权限Currently a callAgent object must be instantiated first in order to gain access to DeviceManager

DeviceManager deviceManager = callClient.getDeviceManager().get();

枚举本地设备Enumerate local devices

若要访问本地设备,可在设备管理器上使用枚举方法。To access local devices, you can use enumeration methods on the Device Manager. 枚举是同步操作。Enumeration is a synchronous action.

//  Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
List<AudioDeviceInfo> localMicrophones = deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
List<AudioDeviceInfo> localSpeakers = deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

设置默认麦克风/扬声器Set default microphone/speaker

借助设备管理器,可设置发起通话时要使用的默认设备。Device manager allows you to set a default device that will be used when starting a call. 如果未设置客户端默认值,通信服务将回退到 OS 默认值。If client defaults are not set, Communication Services will fall back to OS defaults.


// Get the microphone device that is being used.
AudioDeviceInfo defaultMicrophone = deviceManager.getMicrophones().get(0);

// Set the microphone device to use.
deviceManager.setMicrophone(defaultMicrophone);

// Get the speaker device that is being used.
AudioDeviceInfo defaultSpeaker = deviceManager.getSpeakers().get(0);

// Set the speaker device to use.
deviceManager.setSpeaker(defaultSpeaker);

本地相机预览Local camera preview

可使用 DeviceManagerRenderer 开始呈现来自本地相机的流。You can use DeviceManager and Renderer to begin rendering streams from your local camera. 此流不会发送给其他参与者;这是一项本地预览源。This stream won't be sent to other participants; it's a local preview feed. 这是异步操作。This is an asynchronous action.

VideoDeviceInfo videoDevice = <get-video-device>;
Context appContext = this.getApplicationContext();
currentVideoStream = new LocalVideoStream(videoDevice, appContext);
videoOptions = new VideoOptions(currentVideoStream);

Renderer previewRenderer = new Renderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new RenderingOptions(ScalingMode.Fit));

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

事件模型Eventing model

可订阅大多数属性和集合,以便在值发生更改时收到通知。You can subscribe to most of the properties and collections to be notified when values change.

属性Properties

若要订阅 property changed 事件,请采取以下操作:To subscribe to property changed events:

// 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);

集合Collections

若要订阅 collection updated 事件,请采取以下操作:To subscribe to collection updated events:

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

重要

Azure 通信服务聊天 SDK(Android 和 iOS 版)目前提供公共预览版。The Azure Communication Services Android and iOS Calling SDKs are currently in public preview. 此预览版在提供时没有附带服务级别协议,不建议将其用于生产工作负荷。This preview version is provided without a service-level agreement, and it's not recommended for production workloads. 某些功能可能不受支持或者受限。Certain features might not be supported or might have constrained capabilities. 有关详细信息,请参阅 Microsoft Azure 预览版补充使用条款For more information, see Supplemental Terms of Use for Microsoft Azure Previews.

必备条件Prerequisites

设置系统Set up your system

创建 Xcode 项目Create the Xcode project

在 Xcode 中,创建新的 iOS 项目,并选择“单视图应用”模板。In Xcode, create a new iOS project and select the Single View App template. 本快速入门使用 SwiftUI 框架,因此应将“语言”设置为“Swift”,并将“用户界面”设置为“SwiftUI” 。This quickstart uses the SwiftUI framework, so you should set the Language to Swift and User Interface to SwiftUI.

在本快速入门过程中,不会创建单元测试或 UI 测试。You're not going to create unit tests or UI tests during this quickstart. 可随意清除“包括单元测试”和“包括 UI 测试”文本框 。Feel free to clear the Include Unit Tests and Include UI Tests text boxes.

显示用于在 Xcode 中创建项目的窗口的屏幕截图。

使用 CocoaPods 安装包和依赖项Install the package and dependencies with CocoaPods

  1. 为应用程序创建 Podfile,如下所示:Create a Podfile for your application, like this:

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 1.0.0-beta.8'
      pod 'AzureCommunication', '~> 1.0.0-beta.8'
      pod 'AzureCore', '~> 1.0.0-beta.8'
    end
    
  2. 运行 pod installRun pod install.

  3. 使用 Xcode 打开 .xcworkspaceOpen .xcworkspace with Xcode.

请求访问麦克风Request access to the microphone

若要访问设备的麦克风,需要使用 NSMicrophoneUsageDescription 更新应用的信息属性列表。To access the device's microphone, you need to update your app's information property list with NSMicrophoneUsageDescription. 将关联的值设置为将要包含在系统用于向用户请求访问权限的对话框中的 stringYou set the associated value to a string that will be included in the dialog that the system uses to request access from the user.

右键单击项目树的 Info.plist 条目,然后选择“打开为” > “源代码” 。Right-click the Info.plist entry of the project tree and select Open As > Source Code. 将以下代码行添加到顶层 <dict> 节,然后保存文件。Add the following lines in the top-level <dict> section, and then save the file.

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

设置应用框架Set up the app framework

打开项目的 ContentView.swift 文件,然后将 import 声明添加到文件顶部以导入 AzureCommunicationCalling 库。Open your project's ContentView.swift file and add an import declaration to the top of the file to import the AzureCommunicationCalling library. 此外,导入 AVFoundationIn addition, import AVFoundation. 你将需要用它来处理代码中的音频权限请求。You'll need it for audio permission requests in the code.

import AzureCommunicationCalling
import AVFoundation

了解对象模型Learn the object model

以下类和接口处理适用于 iOS 的 Azure 通信服务呼叫 SDK 的某些主要功能。The following classes and interfaces handle some of the major features of the Azure Communication Services Calling SDK for iOS.

备注

本快速入门使用呼叫 SDK 版本 1.0.0-beta.8。This quickstart uses version 1.0.0-beta.8 of the Calling SDK.

名称Name 说明Description
CallClient CallClient 是呼叫 SDK 的主入口点。CallClient is the main entry point to the Calling SDK.
CallAgent CallAgent 用于启动和管理呼叫。CallAgent is used to start and manage calls.
CommunicationTokenCredential CommunicationTokenCredential 用作实例化 CallAgent 的令牌凭据。CommunicationTokenCredential is used as the token credential to instantiate CallAgent.
CommunicationIdentifier CommunicationIdentifier 用于表示用户的标识。CommunicationIdentifier is used to represent the identity of the user. 标识可以是 CommunicationUserIdentifierPhoneNumberIdentifierCallingApplicationThe identity can be CommunicationUserIdentifier, PhoneNumberIdentifier, or CallingApplication.

备注

应用程序在实现事件委托时,必须持有对需要事件订阅的对象的强引用。When the application implements event delegates, it has to hold a strong reference to the objects that require event subscriptions. 例如,如果在调用方法 call.addParticipant 时返回 RemoteParticipant 对象,并且应用程序将委托设置为侦听 RemoteParticipantDelegate,应用程序必须持有对 RemoteParticipant 对象的强引用。For example, when a RemoteParticipant object is returned on invoking the call.addParticipant method and the application sets the delegate to listen on RemoteParticipantDelegate, the application must hold a strong reference to the RemoteParticipant object. 否则,如果收集了此对象,则当呼叫 SDK 尝试调用对象时,委托将引发严重异常。Otherwise, if this object gets collected, the delegate will throw a fatal exception when the Calling SDK tries to invoke the object.

初始化 CallAgentInitialize CallAgent

若要从 CallClient 创建 CallAgent 实例,必须使用 callClient.createCallAgent 方法,该方法在初始化后异步返回 CallAgent 对象。To create a CallAgent instance from CallClient, you have to use a callClient.createCallAgent method that asynchronously returns a CallAgent object after it's initialized.

若要创建通话客户端,必须传递 CommunicationTokenCredential 对象。To create a call client, you have to pass a CommunicationTokenCredential object.


import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
var userCredential: CommunicationTokenCredential?
   do {
       userCredential = try CommunicationTokenCredential(with: CommunicationTokenRefreshOptions(initialToken: token, 
                                                                     refreshProactively: true,
                                                                     tokenRefresher: self.fetchTokenSync))
   } catch {
       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 并设置显示名称。Pass the CommunicationTokenCredential object that you created to CallClient, and set the display name.


callClient = CallClient()
let callAgentOptions:CallAgentOptions = CallAgentOptions()!
options.displayName = " iOS User"

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

拨出电话Place an outgoing call

若要创建并发起通话,需要在 CallAgent 上调用其中一个 API,并提供已使用通信服务管理 SDK 预配的用户的通信服务标识。To create and start a call, you need to call one of the APIs on CallAgent and provide the Communication Services identity of a user that you've provisioned by using the Communication Services Management SDK.

创建和发起呼叫的操作是同步的。Call creation and start are synchronous. 你将收到通话实例,可通过该实例订阅通话中的所有事件。You'll receive a call instance that allows you to subscribe to all events on the call.

向用户发起一对一通话,或向用户和 PSTN 发起一对多通话Place a 1:1 call to a user or a 1:n call with users and PSTN


let callees = [CommunicationUser(identifier: 'UserId')]
let oneToOneCall = self.callAgent.call(participants: callees, options: StartCallOptions())

向用户和 PSTN 发起一对多通话Place a 1:n call with users and PSTN

若要向 PSTN 发起通话,必须指定通过通信服务获取的电话号码。To place the call to PSTN, you have to specify a phone number acquired with Communication Services.


let pstnCallee = PhoneNumberIdentifier(phoneNumber: '+1999999999')
let callee = CommunicationUserIdentifier(identifier: 'UserId')
let groupCall = self.callAgent.call(participants: [pstnCallee, callee], options: StartCallOptions())

发起一对一视频通话Place a 1:1 call with video

若要获取设备管理器实例,请查看有关管理设备的部分。To get a device manager instance, see the section about managing devices.


let camera = self.deviceManager!.cameras!.first
let localVideoStream = LocalVideoStream(camera: camera)
let videoOptions = VideoOptions(localVideoStream: localVideoStream)

let startCallOptions = StartCallOptions()
startCallOptions?.videoOptions = videoOptions

let callee = CommunicationUserIdentifier(identifier: 'UserId')
let call = self.callAgent?.call(participants: [callee], options: startCallOptions)

加入群组通话Join a group call

若要加入通话,需要在 CallAgent 上调用其中一个 API。To join a call, you need to call one of the APIs on CallAgent.


let groupCallLocator = GroupCallLocator(groupId: UUID(uuidString: "uuid_string"))!
let call = self.callAgent?.join(with: groupCallLocator, joinCallOptions: JoinCallOptions())

订阅来电Subscribe to an incoming call

订阅来电事件。Subscribe to an incoming call event.

final class IncomingCallHandler: NSObject, CallAgentDelegate, IncomingCallDelegate
{
    // Event raised when there is an incoming call
    public func onIncomingCall(_ callAgent: CallAgent!, incomingcall: IncomingCall!) {
        self.incomingCall = incomingcall
        // Subscribe to get OnCallEnded event
        self.incomingCall?.delegate = self
    }

    // Event raised when incoming call was not answered
    public func onCallEnded(_ incomingCall: IncomingCall!, args: PropertyChangedEventArgs!) {
        self.incomingCall = nil
    }
}

接听来电Accept an incoming call

若要接听来电,请对呼叫对象调用 accept 方法。To accept a call, call the accept method on a call object. 将委托设置为 CallAgentSet a delegate to CallAgent.

final class CallHandler: NSObject, CallAgentDelegate
{
    public var incomingCall: Call?
 
    public func onCallsUpdated(_ callAgent: CallAgent!, args: CallsUpdatedEventArgs!) {
        if let incomingCall = args.addedCalls?.first(where: { $0.isIncoming }) {
            self.incomingCall = incomingCall
        }
    }
}

let firstCamera: VideoDeviceInfo? = self.deviceManager!.cameras!.first
let localVideoStream = LocalVideoStream(camera: firstCamera)
let acceptCallOptions = AcceptCallOptions()
acceptCallOptions!.videoOptions = VideoOptions(localVideoStream:localVideoStream!)
if let incomingCall = CallHandler().incomingCall {
   incomingCall.accept(options: acceptCallOptions) { (call, error) in
               if error == nil {
                   print("Incoming call accepted")
               } else {
                   print("Failed to accept incoming call")
               }
           }
} else {
   print("No incoming call found to accept")
}

设置推送通知Set up push notifications

移动推送通知是在移动设备中收到的弹出通知。A mobile push notification is the pop-up notification that you get in the mobile device. 对于呼叫,我们将重点介绍 VoIP(Internet 语音协议)推送通知。For calling, we'll focus on VoIP (voice over Internet Protocol) push notifications.

以下部分介绍如何注册、处理和取消注册推送通知。The following sections describe how to register for, handle, and unregister push notifications. 在开始这些任务之前,请先满足以下先决条件:Before you start those tasks, complete these prerequisites:

  1. 在 Xcode 中,转到“签名和功能”。In Xcode, go to Signing & Capabilities. 选择“+功能”来添加一项功能,然后选择“推送通知” 。Add a capability by selecting + Capability, and then select Push Notifications.
  2. 选择“+功能”来再添加一项功能,然后选择“背景模式” 。Add another capability by selecting + Capability, and then select Background Modes.
  3. 在“背景模式”下,选中“IP 语音”和“远程通知”复选框 。Under Background Modes, select the Voice over IP and Remote notifications checkboxes.

显示如何在 Xcode 中添加功能的屏幕截图。

注册推送通知Register for push notifications

若要注册推送通知,请使用设备注册令牌在 CallAgent 实例上调用 registerPushNotification()To register for push notifications, call registerPushNotification() on a CallAgent instance with a device registration token.

成功初始化后需要注册推送通知。Registration for push notifications needs to happen after successful initialization. 销毁 callAgent 对象时,将调用 logout,这会自动取消注册推送通知。When the callAgent object is destroyed, logout will be called, which will automatically unregister push notifications.


let deviceToken: Data = pushRegistry?.pushToken(for: PKPushType.voIP)
callAgent.registerPushNotifications(deviceToken: deviceToken) { (error) in
    if(error == nil) {
        print("Successfully registered to push notification.")
    } else {
        print("Failed to register push notification.")
    }
}

处理推送通知Handle push notifications

若要接收来电推送通知,请使用字典有效负载在 CallAgent 实例上调用 handlePushNotification()To receive push notifications for incoming calls, call handlePushNotification() on a CallAgent instance with a dictionary payload.


let callNotification = IncomingCallInformation.from(payload: pushPayload?.dictionaryPayload)

callAgent.handlePush(notification: callNotification) { (error) in
    if (error != nil) {
        print("Handling of push notification failed")
    } else {
        print("Handling of push notification was successful")
    }
}

取消注册推送通知Unregister push notifications

应用程序可以随时取消注册推送通知。Applications can unregister push notification at any time. 在 CallAgent 上调用 方法即可。Simply call the unregisterPushNotification method on CallAgent.

备注

注销时,应用程序不会自动取消注册推送通知。Applications are not automatically unregistered from push notifications on logout.


callAgent.unregisterPushNotifications { (error) in
    if (error != nil) {
        print("Unregister of push notification failed, please try again")
    } else {
        print("Unregister of push notification was successful")
    }
}

执行通话中操作Perform mid-call operations

可在通话过程中执行各种操作来管理与视频和音频相关的设置。You can perform various operations during a call to manage settings related to video and audio.

静音和取消静音Mute and unmute

若要将本地终结点静音或取消静音,可使用 muteunmute 异步 API。To mute or unmute the local endpoint, you can use the mute and unmute asynchronous APIs.

call!.mute { (error) in
    if error == nil {
        print("Successfully muted")
    } else {
        print("Failed to mute")
    }
}

使用以下代码以异步方式将本地终结点取消静音。Use the following code to unmute the local endpoint asynchronously.

call!.unmute { (error) in
    if error == nil {
        print("Successfully un-muted")
    } else {
        print("Failed to unmute")
    }
}

开始和停止发送本地视频Start and stop sending local video

若要开始向通话中的其他参与者发送本地视频,请使用 startVideo API 并使用 camera 传递 localVideoStreamTo start sending local video to other participants in a call, use the startVideo API and pass localVideoStream with camera.


let firstCamera: VideoDeviceInfo? = self.deviceManager!.cameras!.first
let localVideoStream = LocalVideoStream(camera: firstCamera)

call!.startVideo(stream: localVideoStream) { (error) in
    if (error == nil) {
        print("Local video started successfully")
    } else {
        print("Local video failed to start")
    }
}

开始发送视频后,LocalVideoStream 实例会添加到通话实例上的 localVideoStreams 集合中。After you start sending video, the LocalVideoStream instance is added the localVideoStreams collection on a call instance.


call.localVideoStreams[0]

若要停止本地视频,请传递从 call.startVideo 调用返回的 localVideoStream 实例。To stop local video, pass the localVideoStream instance returned from the invocation of call.startVideo. 这是异步操作。This is an asynchronous action.


call!.stopVideo(stream: localVideoStream) { (error) in
    if (error == nil) {
        print("Local video stopped successfully")
    } else {
        print("Local video failed to stop")
    }
}

管理远程参与者Manage remote participants

所有远程参与者均以 RemoteParticipant 类型表示,可通过通话实例上的 remoteParticipants 集合获得。All remote participants are represented by the RemoteParticipant type and are available through the remoteParticipants collection on a call instance.

列出通话参与者List participants in a call


call.remoteParticipants

获取远程参与者属性Get remote participant properties


// [RemoteParticipantDelegate] delegate - an object you provide to receive events from this RemoteParticipant instance
var remoteParticipantDelegate = remoteParticipant.delegate

// [CommunicationIdentifier] identity - same as the one used to provision a token for another user
var identity = remoteParticipant.identity

// ParticipantStateIdle = 0, ParticipantStateEarlyMedia = 1, ParticipantStateConnecting = 2, ParticipantStateConnected = 3, ParticipantStateOnHold = 4, ParticipantStateInLobby = 5, ParticipantStateDisconnected = 6
var state = remoteParticipant.state

// [Error] callEndReason - reason why participant left the call, contains code/subcode/message
var callEndReason = remoteParticipant.callEndReason

// [Bool] isMuted - indicating if participant is muted
var isMuted = remoteParticipant.isMuted

// [Bool] isSpeaking - indicating if participant is currently speaking
var isSpeaking = remoteParticipant.isSpeaking

// RemoteVideoStream[] - collection of video streams this participants has
var videoStreams = remoteParticipant.videoStreams // [RemoteVideoStream, RemoteVideoStream, ...]

向通话添加参与者Add a participant to a call

若要向通话添加参与者(用户或电话号码),可调用 addParticipantTo add a participant to a call (either a user or a phone number), you can invoke addParticipant. 此命令会同步返回一个远程参与者实例。This command will synchronously return a remote participant instance.


let remoteParticipantAdded: RemoteParticipant = call.add(participant: CommunicationUserIdentifier(identifier: "userId"))

删除通话参与者Remove a participant from a call

若要删除通话参与者(用户或电话号码),可调用 removeParticipant API。To remove a participant from a call (either a user or a phone number), you can invoke the removeParticipant API. 这会异步解析。This will resolve asynchronously.


call!.remove(participant: remoteParticipantAdded) { (error) in
    if (error == nil) {
        print("Successfully removed participant")
    } else {
        print("Failed to remove participant")
    }
}

呈现远程参与者视频流Render remote participant video streams

远程参与者可在通话期间发起视频或屏幕共享。Remote participants can initiate video or screen sharing during a call.

处理远程参与者的视频共享或屏幕共享流Handle video-sharing or screen-sharing streams of remote participants

若要列出远程参与者的流,请检查 videoStreams 集合。To list the streams of remote participants, inspect the videoStreams collections.


var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]

获取远程视频流属性Get remote video stream properties


var type: MediaStreamType = remoteParticipantVideoStream.type // 'MediaStreamTypeVideo'

var isAvailable: Bool = remoteParticipantVideoStream.isAvailable // indicates if remote stream is available

var id: Int = remoteParticipantVideoStream.id // id of remoteParticipantStream

呈现远程参与者流Render remote participant streams

若要开始呈现远程参与者流,请使用以下代码。To start rendering remote participant streams, use the following code.


let renderer: Renderer? = Renderer(remoteVideoStream: remoteParticipantVideoStream)
let targetRemoteParticipantView: RendererView? = renderer?.createView(with: RenderingOptions(scalingMode: ScalingMode.crop))
// To update the scaling mode later
targetRemoteParticipantView.update(scalingMode: ScalingMode.fit)

获取远程视频呈现器方法和属性Get remote video renderer methods and properties

// [Synchronous] dispose() - dispose renderer and all `RendererView` associated with this renderer. To be called when you have removed all associated views from the UI.
remoteVideoRenderer.dispose()

管理设备Manage devices

借助 DeviceManager,可枚举通话中可用于传输音频或视频流的本地设备。DeviceManager lets you enumerate local devices that can be used in a call to transmit audio or video streams. 你还可用它来向用户请求访问麦克风或相机的权限。It also allows you to request permission from a user to access a microphone or camera. 可访问 callClient 对象上的 deviceManagerYou can access deviceManager on the callClient object.


self.callClient!.getDeviceManager { (deviceManager, error) in
        if (error == nil) {
            print("Got device manager instance")
            self.deviceManager = deviceManager
        } else {
            print("Failed to get device manager instance")
        }
    }

枚举本地设备Enumerate local devices

若要访问本地设备,可在设备管理器上使用枚举方法。To access local devices, you can use enumeration methods on the device manager. 枚举是同步操作。Enumeration is a synchronous action.

// enumerate local cameras
var localCameras = deviceManager.cameras! // [VideoDeviceInfo, VideoDeviceInfo...]
// enumerate local cameras
var localMicrophones = deviceManager.microphones! // [AudioDeviceInfo, AudioDeviceInfo...]
// enumerate local cameras
var localSpeakers = deviceManager.speakers! // [AudioDeviceInfo, AudioDeviceInfo...]

设置默认麦克风或扬声器Set the default microphone or speaker

可使用设备管理器来设置在发起通话时要使用的默认设备。You can use the device manager to set a default device that will be used when a call is started. 如果未设置堆栈默认值,通信服务将回退到 OS 默认值。If stack defaults aren't set, Communication Services will fall back to OS defaults.

// get first microphone
var firstMicrophone = self.deviceManager!.cameras!.first
// [Synchronous] set microphone
deviceManager.setMicrophone(microphoneDevice: firstMicrophone)
// get first speaker
var firstSpeaker = self.deviceManager!.speakers!
// [Synchronous] set speaker
deviceManager.setSpeaker(speakerDevice: firstSpeaker)

获取本地相机预览Get a local camera preview

可使用 Renderer 开始呈现来自本地相机的流。You can use Renderer to begin rendering a stream from your local camera. 此流不会发送给其他参与者;这是一项本地预览源。This stream won't be sent to other participants; it's a local preview feed. 这是异步操作。This is an asynchronous action.


let camera: VideoDeviceInfo = self.deviceManager!.getCameraList()![0]
let localVideoStream: LocalVideoStream = LocalVideoStream(camera: camera)
let renderer: Renderer = Renderer(localVideoStream: localVideoStream)
self.view = try renderer!.createView()

获取本地相机预览属性Get local camera preview properties

呈现器包含一组属性和方法,可使用它们来控制呈现。The renderer has set of properties and methods that allow you to control the rendering.


// Constructor can take in LocalVideoStream or RemoteVideoStream
let localRenderer = Renderer(localVideoStream:localVideoStream)
let remoteRenderer = Renderer(remoteVideoStream:remoteVideoStream)

// [StreamSize] size of the rendering view
localRenderer.size

// [RendererDelegate] an object you provide to receive events from this Renderer instance
localRenderer.delegate

// [Synchronous] create view
try! localRenderer.createView()

// [Synchronous] create view with rendering options
try! localRenderer.createView(with: RenderingOptions(scalingMode: ScalingMode.fit))

// [Synchronous] dispose rendering view
localRenderer.dispose()

订阅通知Subscribe to notifications

可订阅大多数属性和集合,以便在值发生更改时收到通知。You can subscribe to most of the properties and collections to be notified when values change.

属性Properties

若要订阅 property changed 事件,请使用以下代码。To subscribe to property changed events, use the following code.

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

 // to unsubscribe
 self.call.delegate = nil

集合Collections

若要订阅 collection updated 事件,请使用以下代码。To subscribe to collection updated events, use the following code.

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

清理资源Clean up resources

如果想要清理并删除通信服务订阅,可以删除资源或资源组。If you want to clean up and remove a Communication Services subscription, you can delete the resource or resource group. 删除资源组同时也会删除与之相关联的任何其他资源。Deleting the resource group also deletes any other resources associated with it. 了解有关清理资源的详细信息。Learn more about cleaning up resources.

后续步骤Next steps

有关详细信息,请参阅以下文章:For more information, see the following articles: