開始使用 Azure 通訊服務 UI 連結庫呼叫 Teams 語音應用程式
此專案旨在引導開發人員使用 Azure 通訊 UI 連結庫,從 Azure 通訊服務通話 Web SDK 到 Teams 通話佇列和自動語音應答起始通話。
視您的需求而定,您可能需要為客戶提供簡單的方法來連絡您,而不需要任何複雜的設定。
呼叫 Teams 通話佇列和自動語音應答是一個是一個簡單且有效的概念,可促進與客戶支援、財務顧問和其他客戶面向團隊的即時互動。 本教學課程的目標是協助您在客戶按一下 Web 上的按鈕時,起始與客戶的互動。
如果您想要試用,您可以從 GitHub 下載程式碼。
請遵循本教學課程:
- 可讓您根據客戶案例來控制客戶的音訊和視訊體驗
- 教您如何建置小工具,以使用 UI 連結庫在Webapp上啟動呼叫。
必要條件
若要遵循本教學課程,需要這些步驟。 請連絡 Teams 管理員,以取得最後兩個專案,以確定您已正確設定。
- 其中一個支援平台上的 Visual Studio Code。
- Node.js,建議使用中 LTS (長期支援) 和節點 20 版本。 請使用
node --version
命令來檢查您的版本。 - Azure 通訊服務資源。 建立通訊資源
- 完成 Teams 租使用者設定,以與您的 Azure 通訊服務 資源互動
- 使用 Teams 通話佇列和 Azure 通訊服務。
- 使用 Teams 自動語音應答和 Azure 通訊服務。
檢查節點和Visual StudioCode
您可以使用此命令檢查 是否已正確安裝 Node 。
node -v
輸出會告訴您您擁有的版本,如果 Node 未安裝並新增至您的 PATH
,就會失敗。 就像使用 Node,您可以檢查 VS Code 是否已使用此命令安裝。
code --version
就像使用 Node 一樣,如果電腦上安裝 VS Code 時發生問題,此命令就會失敗。
開始使用
本教學課程有 7 個步驟,最後應用程式將能夠呼叫 Teams 語音應用程式。 步驟是:
1.設定專案
只有在您要建立新的應用程式時,才使用此步驟。
若要設定 react 應用程式,我們會使用 create-react-app
命令行工具。 此工具會建立一個容易執行由 React 提供的 TypeScript 應用程式。
若要確定您已在電腦上安裝 Node,請在 PowerShell 或終端機中執行此命令,以查看您的節點版本:
node -v
如果您尚未 create-react-app
在電腦上安裝 ,請執行下列命令,將它安裝為全域命令:
npm install -g create-react-app
安裝該命令之後,請執行下一個命令來建立新的 react 應用程式,以在 中建置範例:
# Create an Azure Communication Services App powered by React.
npx create-react-app ui-library-calling-widget-app --template typescript
# Change to the directory of the newly created App.
cd ui-library-calling-widget-app
這些命令完成後,您想要在 VS Code 中開啟已建立的專案。 您可以使用下列命令開啟專案。
code .
2.取得相依性
然後,您需要更新 中的package.json
相依性數位,以包含來自 Azure 通訊服務 的一些套件,以取得我們要建置以運作的小工具體驗:
"@azure/communication-calling": "^1.23.1",
"@azure/communication-chat": "^1.4.0",
"@azure/communication-react": "^1.15.0",
"@azure/communication-calling-effects": "1.0.1",
"@azure/communication-common": "2.3.0",
"@fluentui/react-icons": "~2.0.203",
"@fluentui/react": "~8.98.3",
若要安裝所需的套件,請執行下列 Node 封裝管理員 命令。
npm install
安裝這些套件之後,您全都會設定為開始撰寫建置應用程式的程序代碼。 在本教學課程中,我們會修改 src
目錄中的檔案。
3.初始應用程式設定
若要開始使用,我們會以主頁面取代所提供的 App.tsx
內容,以便:
- 儲存我們需要建立 CallAdapter 的所有 Azure 通訊資訊,以提供通話體驗
- 顯示向使用者公開的小工具。
您的 App.tsx
檔案看起來應該像這樣:
src/App.tsx
import "./App.css";
import {
CommunicationIdentifier,
MicrosoftTeamsAppIdentifier,
} from "@azure/communication-common";
import {
Spinner,
Stack,
initializeIcons,
registerIcons,
Text,
} from "@fluentui/react";
import { CallAdd20Regular, Dismiss20Regular } from "@fluentui/react-icons";
import logo from "./logo.svg";
import { CallingWidgetComponent } from "./components/CallingWidgetComponent";
registerIcons({
icons: { dismiss: <Dismiss20Regular />, callAdd: <CallAdd20Regular /> },
});
initializeIcons();
function App() {
/**
* Token for local user.
*/
const token = "<Enter your ACS Token here>";
/**
* User identifier for local user.
*/
const userId: CommunicationIdentifier = {
communicationUserId: "Enter your ACS Id here",
};
/**
* Enter your Teams voice app identifier from the Teams admin center here
*/
const teamsAppIdentifier: MicrosoftTeamsAppIdentifier = {
teamsAppId: "<Enter your Teams Voice app id here>",
cloud: "public",
};
const widgetParams = {
userId,
token,
teamsAppIdentifier,
};
if (!token || !userId || !teamsAppIdentifier) {
return (
<Stack verticalAlign="center" style={{ height: "100%", width: "100%" }}>
<Spinner
label={"Getting user credentials from server"}
ariaLive="assertive"
labelPosition="top"
/>
</Stack>
);
}
return (
<Stack
style={{ height: "100%", width: "100%", padding: "3rem" }}
tokens={{ childrenGap: "1.5rem" }}
>
<Stack tokens={{ childrenGap: "1rem" }} style={{ margin: "auto" }}>
<Stack
style={{ padding: "3rem" }}
horizontal
tokens={{ childrenGap: "2rem" }}
>
<Text style={{ marginTop: "auto" }} variant="xLarge">
Welcome to a Calling Widget sample
</Text>
<img
style={{ width: "7rem", height: "auto" }}
src={logo}
alt="logo"
/>
</Stack>
<Text>
Welcome to a Calling Widget sample for the Azure Communication
Services UI Library. Sample has the ability to connect you through
Teams voice apps to a agent to help you.
</Text>
<Text>
As a user all you need to do is click the widget below, enter your
display name for the call - this will act as your caller id, and
action the <b>start call</b> button.
</Text>
</Stack>
<Stack
horizontal
tokens={{ childrenGap: "1.5rem" }}
style={{ overflow: "hidden", margin: "auto" }}
>
<CallingWidgetComponent
widgetAdapterArgs={widgetParams}
onRenderLogo={() => {
return (
<img
style={{ height: "4rem", width: "4rem", margin: "auto" }}
src={logo}
alt="logo"
/>
);
}}
/>
</Stack>
</Stack>
);
}
export default App;
在這裡程式碼片段中,我們會註冊兩個新的圖示 <Dismiss20Regular/>
和 <CallAdd20Regular>
。 我們將在下一節中建立的小工具元件內使用這些新圖示。
4.建立小工具
現在,我們需要製作一個小工具,以三種不同的模式顯示:
- 等候: 此小工具狀態是元件在呼叫之前和之後的狀態
- 安裝程式: 此狀態是在小工具向使用者要求資訊時,例如其名稱。
- 在呼叫中: 此小工具會取代為 UI 連結庫呼叫複合。 此小工具模式是在使用者呼叫 Voice 應用程式或與代理程式交談時。
讓我們建立名為 src/components
的資料夾。 在此資料夾中,建立名為 CallingWidgetComponent.tsx
的新檔案。 此檔案看起來應該像下列程式碼片段:
CallingWidgetComponent.tsx
import {
IconButton,
PrimaryButton,
Stack,
TextField,
useTheme,
Checkbox,
Icon,
Spinner,
} from "@fluentui/react";
import React, { useEffect, useRef, useState } from "react";
import {
callingWidgetSetupContainerStyles,
checkboxStyles,
startCallButtonStyles,
callingWidgetContainerStyles,
callIconStyles,
logoContainerStyles,
collapseButtonStyles,
} from "../styles/CallingWidgetComponent.styles";
import {
AzureCommunicationTokenCredential,
CommunicationUserIdentifier,
MicrosoftTeamsAppIdentifier,
} from "@azure/communication-common";
import {
CallAdapter,
CallAdapterState,
CallComposite,
CommonCallAdapterOptions,
StartCallIdentifier,
createAzureCommunicationCallAdapter,
} from "@azure/communication-react";
// lets add to our react imports as well
import { useMemo } from "react";
import { callingWidgetInCallContainerStyles } from "../styles/CallingWidgetComponent.styles";
/**
* Properties needed for our widget to start a call.
*/
export type WidgetAdapterArgs = {
token: string;
userId: CommunicationUserIdentifier;
teamsAppIdentifier: MicrosoftTeamsAppIdentifier;
};
export interface CallingWidgetComponentProps {
/**
* arguments for creating an AzureCommunicationCallAdapter for your Calling experience
*/
widgetAdapterArgs: WidgetAdapterArgs;
/**
* Custom render function for displaying logo.
* @returns
*/
onRenderLogo?: () => JSX.Element;
}
/**
* Widget for Calling Widget
* @param props
*/
export const CallingWidgetComponent = (
props: CallingWidgetComponentProps
): JSX.Element => {
const { onRenderLogo, widgetAdapterArgs } = props;
const [widgetState, setWidgetState] = useState<"new" | "setup" | "inCall">(
"new"
);
const [displayName, setDisplayName] = useState<string>();
const [consentToData, setConsentToData] = useState<boolean>(false);
const [useLocalVideo, setUseLocalVideo] = useState<boolean>(false);
const [adapter, setAdapter] = useState<CallAdapter>();
const callIdRef = useRef<string>();
const theme = useTheme();
// add this before the React template
const credential = useMemo(() => {
try {
return new AzureCommunicationTokenCredential(widgetAdapterArgs.token);
} catch {
console.error("Failed to construct token credential");
return undefined;
}
}, [widgetAdapterArgs.token]);
const adapterOptions: CommonCallAdapterOptions = useMemo(
() => ({
callingSounds: {
callEnded: { url: "/sounds/callEnded.mp3" },
callRinging: { url: "/sounds/callRinging.mp3" },
callBusy: { url: "/sounds/callBusy.mp3" },
},
}),
[]
);
const callAdapterArgs = useMemo(() => {
return {
userId: widgetAdapterArgs.userId,
credential: credential,
targetCallees: [
widgetAdapterArgs.teamsAppIdentifier,
] as StartCallIdentifier[],
displayName: displayName,
options: adapterOptions,
};
}, [
widgetAdapterArgs.userId,
widgetAdapterArgs.teamsAppIdentifier.teamsAppId,
credential,
displayName,
]);
useEffect(() => {
if (adapter) {
adapter.on("callEnded", () => {
/**
* We only want to reset the widget state if the call that ended is the same as the current call.
*/
if (
adapter.getState().acceptedTransferCallState &&
adapter.getState().acceptedTransferCallState?.id !== callIdRef.current
) {
return;
}
setDisplayName(undefined);
setWidgetState("new");
setConsentToData(false);
setAdapter(undefined);
adapter.dispose();
});
adapter.on("transferAccepted", (e) => {
console.log("transferAccepted", e);
});
adapter.onStateChange((state: CallAdapterState) => {
if (state?.call?.id && callIdRef.current !== state?.call?.id) {
callIdRef.current = state?.call?.id;
console.log(`Call Id: ${callIdRef.current}`);
}
});
}
}, [adapter]);
/** widget template for when widget is open, put any fields here for user information desired */
if (widgetState === "setup") {
return (
<Stack
styles={callingWidgetSetupContainerStyles(theme)}
tokens={{ childrenGap: "1rem" }}
>
<IconButton
styles={collapseButtonStyles}
iconProps={{ iconName: "Dismiss" }}
onClick={() => {
setDisplayName(undefined);
setConsentToData(false);
setUseLocalVideo(false);
setWidgetState("new");
}}
/>
<Stack tokens={{ childrenGap: "1rem" }} styles={logoContainerStyles}>
<Stack style={{ transform: "scale(1.8)" }}>
{onRenderLogo && onRenderLogo()}
</Stack>
</Stack>
<TextField
label={"Name"}
required={true}
placeholder={"Enter your name"}
onChange={(_, newValue) => {
setDisplayName(newValue);
}}
/>
<Checkbox
styles={checkboxStyles(theme)}
label={
"Use video - Checking this box will enable camera controls and screen sharing"
}
onChange={(_, checked?: boolean | undefined) => {
setUseLocalVideo(!!checked);
setUseLocalVideo(true);
}}
></Checkbox>
<Checkbox
required={true}
styles={checkboxStyles(theme)}
disabled={displayName === undefined}
label={
"By checking this box, you are consenting that we will collect data from the call for customer support reasons"
}
onChange={async (_, checked?: boolean | undefined) => {
setConsentToData(!!checked);
if (callAdapterArgs && callAdapterArgs.credential) {
setAdapter(
await createAzureCommunicationCallAdapter({
displayName: displayName ?? "",
userId: callAdapterArgs.userId,
credential: callAdapterArgs.credential,
targetCallees: callAdapterArgs.targetCallees,
options: callAdapterArgs.options,
})
);
}
}}
></Checkbox>
<PrimaryButton
styles={startCallButtonStyles(theme)}
onClick={() => {
if (displayName && consentToData && adapter) {
setWidgetState("inCall");
adapter?.startCall(callAdapterArgs.targetCallees, {
audioOptions: { muted: false },
});
}
}}
>
{!consentToData && `Enter your name`}
{consentToData && !adapter && (
<Spinner ariaLive="assertive" labelPosition="top" />
)}
{consentToData && adapter && `StartCall`}
</PrimaryButton>
</Stack>
);
}
if (widgetState === "inCall" && adapter) {
return (
<Stack styles={callingWidgetInCallContainerStyles(theme)}>
<CallComposite
adapter={adapter}
options={{
callControls: {
cameraButton: useLocalVideo,
screenShareButton: useLocalVideo,
moreButton: false,
peopleButton: false,
displayType: "compact",
},
localVideoTile: !useLocalVideo ? false : { position: "floating" },
}}
/>
</Stack>
);
}
return (
<Stack
horizontalAlign="center"
verticalAlign="center"
styles={callingWidgetContainerStyles(theme)}
onClick={() => {
setWidgetState("setup");
}}
>
<Stack
horizontalAlign="center"
verticalAlign="center"
style={{
height: "4rem",
width: "4rem",
borderRadius: "50%",
background: theme.palette.themePrimary,
}}
>
<Icon iconName="callAdd" styles={callIconStyles(theme)} />
</Stack>
</Stack>
);
};
在 中 CallAdapterOptions
,我們看到一些參考的聲音檔案,這些檔案是使用 中的 CallComposite
通話音效功能。 如果您有興趣使用音效,請參閱完成的程式代碼來 下載聲音檔案。
5.設定小工具的樣式
我們需要撰寫一些樣式,以確保小工具看起來合適,而且可以保存我們的呼叫複合。 如果複製我們新增至檔案 CallingWidgetComponent.tsx
的程式碼片段,則應該已在小工具中使用這些樣式。
讓我們在此資料夾中建立名為 src/styles
的新資料夾,建立名為 CallingWidgetComponent.styles.ts
的檔案。 輸入如下列程式碼片段所示:
import {
IButtonStyles,
ICheckboxStyles,
IIconStyles,
IStackStyles,
Theme,
} from "@fluentui/react";
export const checkboxStyles = (theme: Theme): ICheckboxStyles => {
return {
label: {
color: theme.palette.neutralPrimary,
},
};
};
export const callingWidgetContainerStyles = (theme: Theme): IStackStyles => {
return {
root: {
width: "5rem",
height: "5rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: "50%",
bottom: "1rem",
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
":hover": {
boxShadow: theme.effects.elevation64,
},
},
};
};
export const callingWidgetSetupContainerStyles = (
theme: Theme
): IStackStyles => {
return {
root: {
width: "18rem",
minHeight: "20rem",
maxHeight: "25rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: theme.effects.roundedCorner6,
bottom: 0,
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
background: theme.palette.white,
},
};
};
export const callIconStyles = (theme: Theme): IIconStyles => {
return {
root: {
paddingTop: "0.2rem",
color: theme.palette.white,
transform: "scale(1.6)",
},
};
};
export const startCallButtonStyles = (theme: Theme): IButtonStyles => {
return {
root: {
background: theme.palette.themePrimary,
borderRadius: theme.effects.roundedCorner6,
borderColor: theme.palette.themePrimary,
},
textContainer: {
color: theme.palette.white,
},
};
};
export const logoContainerStyles: IStackStyles = {
root: {
margin: "auto",
padding: "0.2rem",
height: "5rem",
width: "10rem",
zIndex: 0,
},
};
export const collapseButtonStyles: IButtonStyles = {
root: {
position: "absolute",
top: "0.2rem",
right: "0.2rem",
zIndex: 1,
},
};
export const callingWidgetInCallContainerStyles = (
theme: Theme
): IStackStyles => {
return {
root: {
width: "35rem",
height: "25rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: theme.effects.roundedCorner6,
bottom: 0,
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
background: theme.semanticColors.bodyBackground,
},
};
};
6.設定識別值
執行應用程式之前,請移至 App.tsx
,並將該處的佔位元元值取代為您的Teams語音應用程式 Azure 通訊服務 身分識別和資源帳戶標識碼。 以下是、 userId
和teamsAppIdentifier
的token
輸入值。
./src/App.tsx
/**
* Token for local user.
*/
const token = "<Enter your ACS Token here>";
/**
* User identifier for local user.
*/
const userId: CommunicationIdentifier = {
communicationUserId: "Enter your ACS Id here",
};
/**
* Enter your Teams voice app identifier from the Teams admin center here
*/
const teamsAppIdentifier: MicrosoftTeamsAppIdentifier = {
teamsAppId: "<Enter your Teams Voice app id here>",
cloud: "public",
};
7. 執行應用程式
最後,我們可以執行應用程式來進行呼叫! 執行下列命令來安裝相依性並執行應用程式。
# Install the newe dependencies
npm install
# run the React app
npm run start
應用程式執行之後,您可以在瀏覽器中 http://localhost:3000
看到它。 您應該會看到下列啟動顯示畫面:
然後,當您執行小工具按鈕時,您應該會看到一個小功能表:
填寫名稱之後,按兩下 [開始呼叫],呼叫應該開始。 小工具在開始呼叫之後看起來應該像這樣:
下一步
如需 Teams 語音應用程式的詳細資訊,請參閱 Teams 自動語音應答和 Teams 通話佇列的相關文件。 或者,另請參閱我們的教學課程,瞭解如何使用JavaScript套件組合建置類似的體驗。
快速入門:開始使用呼叫 Teams 通話佇列和自動語音應答的 Azure 通訊服務 UI 連結庫 JavaScript 套件組合