Краткое руководство. Подключение приложения чата к собранию в Teams
Приступите к работе со Службами коммуникации Azure, подключив решение для чата к Microsoft Teams.
В этом кратком руководстве вы узнаете, как общаться в чате на собрании Teams с помощью пакета SDK для чата Служб коммуникации Azure для JavaScript.
Пример кода
Итоговый код для этого краткого руководства можно найти на сайте GitHub.
Предварительные требования
- Развертывание Teams.
- Рабочее приложение чата.
Присоединение к чату собрания
Пользователь Служб коммуникации может анонимно присоединиться к собранию Teams с помощью пакета SDK для вызовов. Присоединение к собранию также добавляет их в качестве участника в чат собрания, где они могут отправлять и получать сообщения с другими пользователями на собрании. У пользователя не будет доступа к сообщениям чата, отправленным до присоединения к собранию, и он не сможет отправлять или получать сообщения после завершения собрания. Чтобы присоединиться к собранию и начать беседу, можно выполнить следующие действия.
создание приложения Node.js;
Откройте терминал или командное окно, создайте каталог для своего приложения и перейдите к нему.
mkdir chat-interop-quickstart && cd chat-interop-quickstart
Воспользуйтесь командой npm init -y
, чтобы создать файл package.json с параметрами по умолчанию.
npm init -y
Установка пакетов чата
Используйте команду npm install
, чтобы установить необходимые пакеты SDK Служб коммуникации для JavaScript.
npm install @azure/communication-common --save
npm install @azure/communication-identity --save
npm install @azure/communication-signaling --save
npm install @azure/communication-chat --save
npm install @azure/communication-calling --save
Параметр --save
указывает библиотеку как зависимость в файле пакета package.json.
Настройка платформы приложения
В этом кратком руководстве для упаковки ресурсов приложения используется Webpack. Выполните следующую команду, чтобы установить пакеты npm Webpack, webpack-cli и webpack-dev-server и перечислить их как зависимости разработки в файле package.json:
npm install webpack@4.42.0 webpack-cli@3.3.11 webpack-dev-server@3.10.3 --save-dev
Создайте файл index.html в корневом каталоге проекта. Мы используем этот файл для настройки базового макета, который позволит пользователю присоединиться к собранию и начать чат.
Добавление элементов управления пользовательского интерфейса Teams
Замените код в файле index.html приведенным ниже фрагментом кода. В текстовые поля в верхней части страницы будут вводиться контекст собрания группы и идентификатор потока собрания. Кнопка "Присоединиться к собранию Teams" используется для присоединения к указанному собранию. В нижней части страницы появится всплывающее окно чата. Его можно использовать для отправки сообщений в потоке собраний, и в режиме реального времени отображаются все сообщения, отправленные в потоке, пока пользователь Служб коммуникации является участником.
<!DOCTYPE html>
<html>
<head>
<title>Communication Client - Calling and Chat Sample</title>
<style>
body {box-sizing: border-box;}
/* The popup chat - hidden by default */
.chat-popup {
display: none;
position: fixed;
bottom: 0;
left: 15px;
border: 3px solid #f1f1f1;
z-index: 9;
}
.message-box {
display: none;
position: fixed;
bottom: 0;
left: 15px;
border: 3px solid #FFFACD;
z-index: 9;
}
.form-container {
max-width: 300px;
padding: 10px;
background-color: white;
}
.form-container textarea {
width: 90%;
padding: 15px;
margin: 5px 0 22px 0;
border: none;
background: #e1e1e1;
resize: none;
min-height: 50px;
}
.form-container .btn {
background-color: #4CAF40;
color: white;
padding: 14px 18px;
margin-bottom:10px;
opacity: 0.6;
border: none;
cursor: pointer;
width: 100%;
}
.container {
border: 1px solid #dedede;
background-color: #F1F1F1;
border-radius: 3px;
padding: 8px;
margin: 8px 0;
}
.darker {
border-color: #ccc;
background-color: #ffdab9;
margin-left: 25px;
margin-right: 3px;
}
.lighter {
margin-right: 20px;
margin-left: 3px;
}
.container::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<h4>Azure Communication Services</h4>
<h1>Calling and Chat Quickstart</h1>
<input id="teams-link-input" type="text" placeholder="Teams meeting link"
style="margin-bottom:1em; width: 300px;" />
<input id="thread-id-input" type="text" placeholder="Chat thread id"
style="margin-bottom:1em; width: 300px;" />
<p>Call state <span style="font-weight: bold" id="call-state">-</span></p>
<div>
<button id="join-meeting-button" type="button">
Join Teams Meeting
</button>
<button id="hang-up-button" type="button" disabled="true">
Hang Up
</button>
</div>
<div class="chat-popup" id="chat-box">
<div id="messages-container"></div>
<form class="form-container">
<textarea placeholder="Type message.." name="msg" id="message-box" required></textarea>
<button type="button" class="btn" id="send-message">Send</button>
</form>
</div>
<script src="./bundle.js"></script>
</body>
</html>
Включение элементов управления пользовательского интерфейса Teams
Замените содержимое файла client.js приведенным ниже фрагментом кода.
Во фрагменте кода замените
SECRET_CONNECTION_STRING
строкой подключения Служб коммуникации,ENDPOINT_URL
URL-адресом конечной точки Служб коммуникации.
import { CallClient, CallAgent } from "@azure/communication-calling";
import { AzureCommunicationTokenCredential } from "@azure/communication-common";
import { CommunicationIdentityClient } from "@azure/communication-identity";
import { ChatClient } from "@azure/communication-chat";
let call;
let callAgent;
let chatClient;
let chatThreadClient;
const meetingLinkInput = document.getElementById('teams-link-input');
const threadIdInput = document.getElementById('thread-id-input');
const callButton = document.getElementById("join-meeting-button");
const hangUpButton = document.getElementById("hang-up-button");
const callStateElement = document.getElementById('call-state');
const messagesContainer = document.getElementById("messages-container");
const chatBox = document.getElementById("chat-box");
const sendMessageButton = document.getElementById("send-message");
const messagebox = document.getElementById("message-box");
var userId = '';
var lastMessage = '';
var previousMessage = '';
async function init() {
const connectionString = "<SECRET_CONNECTION_STRING>";
const endpointUrl = "<ENDPOINT_URL>";
const identityClient = new CommunicationIdentityClient(connectionString);
let identityResponse = await identityClient.createUser();
userId = identityResponse.communicationUserId;
console.log(`\nCreated an identity with ID: ${identityResponse.communicationUserId}`);
let tokenResponse = await identityClient.getToken(identityResponse, [
"voip",
"chat",
]);
const { token, expiresOn } = tokenResponse;
console.log(`\nIssued an access token that expires at: ${expiresOn}`);
console.log(token);
const callClient = new CallClient();
const tokenCredential = new AzureCommunicationTokenCredential(token);
callAgent = await callClient.createCallAgent(tokenCredential);
callButton.disabled = false;
chatClient = new ChatClient(
endpointUrl,
new AzureCommunicationTokenCredential(token)
);
console.log('Azure Communication Chat client created!');
}
init();
callButton.addEventListener("click", async () => {
// join with meeting link
call = callAgent.join({meetingLink: meetingLinkInput.value}, {});
call.on('stateChanged', () => {
callStateElement.innerText = call.state;
})
// toggle button and chat box states
chatBox.style.display = "block";
hangUpButton.disabled = false;
callButton.disabled = true;
messagesContainer.innerHTML = "";
console.log(call);
// open notifications channel
await chatClient.startRealtimeNotifications();
// subscribe to new message notifications
chatClient.on("chatMessageReceived", (e) => {
console.log("Notification chatMessageReceived!");
// check whether the notification is intended for the current thread
if (threadIdInput.value != e.threadId) {
return;
}
if (e.sender.communicationUserId != userId) {
renderReceivedMessage(e.message);
}
else {
renderSentMessage(e.message);
}
});
chatThreadClient = await chatClient.getChatThreadClient(threadIdInput.value);
});
async function renderReceivedMessage(message) {
previousMessage = lastMessage;
lastMessage = '<div class="container lighter">' + message + '</div>';
messagesContainer.innerHTML = previousMessage + lastMessage;
}
async function renderSentMessage(message) {
previousMessage = lastMessage;
lastMessage = '<div class="container darker">' + message + '</div>';
messagesContainer.innerHTML = previousMessage + lastMessage;
}
hangUpButton.addEventListener("click", async () =>
{
// end the current call
await call.hangUp();
// toggle button states
hangUpButton.disabled = true;
callButton.disabled = false;
callStateElement.innerText = '-';
// toggle chat states
chatBox.style.display = "none";
messages = "";
});
sendMessageButton.addEventListener("click", async () =>
{
let message = messagebox.value;
let sendMessageRequest = { content: message };
let sendMessageOptions = { senderDisplayName : 'Jack' };
let sendChatMessageResult = await chatThreadClient.sendMessage(sendMessageRequest, sendMessageOptions);
let messageId = sendChatMessageResult.id;
messagebox.value = '';
console.log(`Message sent!, message id:${messageId}`);
});
Отображаемые имена участников потока чата не задаются клиентом Teams. Имена возвращаются как null в API для перечисления участников, в событии participantsAdded
и в событии participantsRemoved
. Отображаемые имена участников чата можно получить из поля remoteParticipants
объекта call
. При получении уведомления об изменении состава можно использовать этот код для получения имени пользователя, который был добавлен или удален:
var displayName = call.remoteParticipants.find(p => p.identifier.communicationUserId == '<REMOTE_USER_ID>').displayName;
Получение данных о беседе в чате для пользователя Служб коммуникации
Сведения о собрании Teams можно получить с помощью API-интерфейсов Graph. Подробности см. в документации по Graph. Пакет SDK вызовов Служб коммуникации принимает полную ссылку на собрание Teams или идентификатор собрания. Они возвращаются как часть onlineMeeting
ресурса, доступного в свойствеjoinWebUrl
С помощью API Graph можно также получить threadID
. Ответ содержит chatInfo
объект с threadID
.
Вы также можете получить необходимую информацию о собрании и идентификатор потока по URL-адресу Присоединиться к собранию в самом приглашении на собрание в Teams.
Ссылка на собрание в Teams выглядит следующим образом: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here
. — threadId
это , где meeting_chat_thread_id
находится в ссылке. Перед использованием убедитесь, что объект meeting_chat_thread_id
не экранирован. Он должен иметь следующий формат: 19:meeting_ZWRhZDY4ZGUtYmRlNS00OWZaLTlkZTgtZWRiYjIxOWI2NTQ4@thread.v2
.
Выполнение кода
Пользователи Webpack могут использовать webpack-dev-server
для сборки и запуска приложения. Выполните следующую команду, чтобы создать пакет узла приложения на локальном веб-сервере:
npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map
Откройте веб-браузер и перейдите по адресу http://localhost:8080/
. Приложение должно быть запущено, как показано на следующем снимке экрана:
В текстовые поля вставьте ссылку на собрание в Teams и идентификатор потока. Чтобы присоединиться к собранию в Teams, нажмите кнопку Join Teams Meeting (Присоединиться к собранию в Teams). После того, как пользователь Службы коммуникации был допущен к встрече, вы можете общаться в чате из приложения служб связи. Чтобы начать беседу, перейдите к полю в нижней части страницы. Для простоты приложение отображает в чате только два последних сообщения.
Примечание
Некоторые функции в настоящее время не поддерживаются для сценариев взаимодействия с Teams. Дополнительные сведения о поддерживаемых функциях см. в статье Возможности собраний Teams для внешних пользователей Teams.
В этом кратком руководстве описывается, как общаться в чате конференции Teams с помощью пакета SDK для чата Служб коммуникации Azure для iOS.
Предварительные требования
- Развертывание Teams.
- Рабочее вызывающее приложение.
Присоединение к чату собрания
Пользователь Служб коммуникации может анонимно присоединиться к собранию Teams с помощью пакета SDK для вызовов. Присоединение к собранию также добавляет их в качестве участника в чат собрания, где они могут отправлять и получать сообщения с другими пользователями на собрании. У пользователя не будет доступа к сообщениям чата, которые были отправлены до того, как он присоединился к собранию, и он не сможет отправлять или получать сообщения после завершения собрания. Чтобы присоединиться к собранию и начать беседу, можно выполнить следующие действия.
Добавление чата в вызывающее приложение Teams
Удалите Podfile.lock
и замените содержимое файла Podfile следующим кодом.
platform :ios, '13.0'
use_frameworks!
target 'AzureCommunicationCallingSample' do
pod 'AzureCommunicationCalling', '~> 1.0.0-beta.12'
pod 'AzureCommunicationChat', '~> 1.0.0-beta.11'
end
Установка пакетов чата
Запустите pod install
для установки пакета AzureCommunicationChat.
После установки откройте .xcworkspace
файл .
Настройка платформы приложения
Импортируйте пакет AzureCommunicationChat в ContentView.swift
, добавив следующий фрагмент кода.
import AzureCommunicationChat
Добавление элементов управления пользовательского интерфейса Teams
Добавьте следующий фрагмент кода под существующие переменные состояния в ContentView.swift
.
// Chat
@State var chatClient: ChatClient?
@State var chatThreadClient: ChatThreadClient?
@State var chatMessage: String = ""
@State var meetingMessages: [MeetingMessage] = []
let displayName: String = "<YOUR_DISPLAY_NAME_HERE>"
Замените <YOUR_DISPLAY_NAME_HERE>
на отображаемое имя, которое хотите использовать в чате.
Далее мы изменим форму Section
для отображения сообщений чата и добавим элементы управления пользовательского интерфейса для чата.
Добавьте следующий фрагмент кода в существующую форму. Сразу после текстового представления Text(recordingStatus)
для состояния записи.
VStack(alignment: .leading) {
ForEach(meetingMessages, id: \.id) { message in
let currentUser: Bool = (message.displayName == self.displayName)
let foregroundColor = currentUser ? Color.white : Color.black
let background = currentUser ? Color.blue : Color(.systemGray6)
let alignment = currentUser ? HorizontalAlignment.trailing : HorizontalAlignment.leading
VStack {
Text(message.displayName).font(Font.system(size: 10))
Text(message.content)
}
.alignmentGuide(.leading) { d in d[alignment] }
.padding(10)
.foregroundColor(foregroundColor)
.background(background)
.cornerRadius(10)
}
}.frame(minWidth: 0, maxWidth: .infinity)
TextField("Enter your message...", text: $chatMessage)
Button(action: sendMessage) {
Text("Send Message")
}.disabled(chatThreadClient == nil)
Далее измените заголовок на Chat Teams Quickstart
. Измените следующую строку с помощью этого заголовка.
.navigationBarTitle("Chat Teams Quickstart")
Включение элементов управления пользовательского интерфейса Teams
Инициализация ChatClient
Создайте экземпляр ChatClient
и включите уведомления в реальном времени. Мы используем уведомления в режиме реального времени для получения сообщений чата.
В добавьте NavigationView
.onAppear
фрагмент кода ниже после существующего кода, который инициализирует CallAgent
.
// Initialize the ChatClient
do {
let endpoint = "COMMUNICATION_SERVICES_RESOURCE_ENDPOINT_HERE>"
let credential = try CommunicationTokenCredential(token: "<USER_ACCESS_TOKEN_HERE>")
self.chatClient = try ChatClient(
endpoint: endpoint,
credential: credential,
withOptions: AzureCommunicationChatClientOptions()
)
self.message = "ChatClient successfully created"
// Start real-time notifications
self.chatClient?.startRealTimeNotifications() { result in
switch result {
case .success:
print("Real-time notifications started")
// Receive chat messages
self.chatClient?.register(event: ChatEventId.chatMessageReceived, handler: receiveMessage)
case .failure:
print("Failed to start real-time notifications")
self.message = "Failed to enable chat notifications"
}
}
} catch {
print("Unable to create ChatClient")
self.message = "Please enter a valid endpoint and Chat token in source code"
return
}
Замените <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT_HERE>
значением конечной точки для ресурса Служб коммуникации.
Замените <USER_ACCESS_TOKEN_HERE>
маркером доступа с областью чата.
Дополнительные сведения о маркерах доступа пользователей: маркер доступа пользователя
Инициализация ChatThreadClient
После того как пользователь присоединится к конференции, внутри существующей функции joinTeamsMeeting()
будет выполнена инициализация ChatThreadClient
.
Внутри обработчика завершения для вызова self.callAgent?.join()
добавьте код под комментарием // Initialize the ChatThreadClient
. Полный код показан ниже.
self.callAgent?.join(with: teamsMeetingLinkLocator, joinCallOptions: joinCallOptions) { (call, error) in
if (error == nil) {
self.call = call
self.callObserver = CallObserver(self)
self.call!.delegate = self.callObserver
self.message = "Teams meeting joined successfully"
// Initialize the ChatThreadClient
do {
guard let threadId = getThreadId(from: self.meetingLink) else {
self.message = "Failed to join meeting chat"
return
}
self.chatThreadClient = try chatClient?.createClient(forThread: threadId)
self.message = "Joined meeting chat successfully"
} catch {
print("Failed to create ChatThreadClient")
self.message = "Failed to join meeting chat"
return
}
} else {
print("Failed to join Teams meeting")
}
}
Добавьте в ContentView
следующую вспомогательную функцию, которая используется для извлечения идентификатора беседы чата из ссылки на конференцию Teams.
func getThreadId(from meetingLink: String) -> String? {
if let range = self.meetingLink.range(of: "meetup-join/") {
let thread = self.meetingLink[range.upperBound...]
if let endRange = thread.range(of: "/")?.lowerBound {
return String(thread.prefix(upTo: endRange))
}
}
return nil
}
Включение отправки сообщений
Добавьте функцию sendMessage()
в ContentView
. Эта функция использует для отправки ChatThreadClient
сообщений от пользователя.
func sendMessage() {
let message = SendChatMessageRequest(
content: self.chatMessage,
senderDisplayName: self.displayName
)
self.chatThreadClient?.send(message: message) { result, _ in
switch result {
case .success:
print("Chat message sent")
case .failure:
print("Failed to send chat message")
}
self.chatMessage = ""
}
}
Включение получения сообщений
Для получения сообщений мы реализуем обработчик событий ChatMessageReceived
. При отправке новых сообщений в поток этот обработчик добавляет сообщения в meetingMessages
переменную, чтобы их можно было отобразить в пользовательском интерфейсе.
Сначала добавьте следующую структуру в ContentView.swift
. Пользовательский интерфейс использует данные в структуре для отображения сообщений чата.
struct MeetingMessage {
let id: String
let content: String
let displayName: String
}
Затем добавьте функцию receiveMessage()
в ContentView
. Это обработчик, который вызывается при каждом возникновении события ChatMessageReceived
.
func receiveMessage(response: Any, eventId: ChatEventId) {
let chatEvent: ChatMessageReceivedEvent = response as! ChatMessageReceivedEvent
let displayName: String = chatEvent.senderDisplayName ?? "Unknown User"
let content: String = chatEvent.message.replacingOccurrences(of: "<[^>]+>", with: "", options: String.CompareOptions.regularExpression)
self.meetingMessages.append(
MeetingMessage(
id: chatEvent.id,
content: content,
displayName: displayName
)
)
}
Выход из чата
Когда пользователь покидает собрание команды, мы очищаем чат из пользовательского интерфейса. Полный код показан ниже.
func leaveMeeting() {
if let call = call {
call.hangUp(options: nil, completionHandler: { (error) in
if error == nil {
self.message = "Leaving Teams meeting was successful"
// Clear the chat
self.meetingMessages.removeAll()
} else {
self.message = "Leaving Teams meeting failed"
}
})
} else {
self.message = "No active call to hanup"
}
}
Получение данных о беседе в чате для пользователя Служб коммуникации
Сведения о собрании Teams можно получить с помощью API-интерфейсов Graph. Подробности см. в документации по Graph. Пакет SDK вызовов Служб коммуникации принимает полную ссылку на собрание Teams или идентификатор собрания. Они предоставляются как часть ресурса onlineMeeting
, доступного под свойством joinWebUrl
.
С помощью API Graph можно также получить threadID
. Ответ содержит chatInfo
объект , содержащий threadID
.
Вы также можете получить необходимую информацию о собрании и идентификатор потока по URL-адресу Присоединиться к собранию в самом приглашении на собрание в Teams.
Ссылка на собрание в Teams выглядит следующим образом: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here
. — threadID
это расположение meeting_chat_thread_id
в ссылке. Перед использованием убедитесь, что объект meeting_chat_thread_id
не экранирован. Он должен иметь следующий формат: 19:meeting_ZWRhZDY4ZGUtYmRlNS00OWZaLTlkZTgtZWRiYjIxOWI2NTQ4@thread.v2
.
Выполнение кода
Запустите приложение.
Чтобы присоединиться к собранию Teams, введите ссылку на собрание команды в пользовательском интерфейсе.
После присоединения к собранию команды необходимо допустить пользователя к собранию в клиенте вашей команды. После того как пользователь будет допущен и присоединился к чату, вы сможете отправлять и получать сообщения.
Примечание
Некоторые функции в настоящее время не поддерживаются для сценариев взаимодействия с Teams. Дополнительные сведения о поддерживаемых функциях см. в статье Возможности собраний Teams для внешних пользователей Teams.
В этом кратком руководстве описывается, как общаться в чате собрании Teams с помощью пакета SDK для чата Служб коммуникации Azure для Android.
Предварительные требования
- Развертывание Teams.
- Рабочее вызывающее приложение.
Обеспечение взаимодействия с Teams
Пользователь Служб коммуникации, который присоединяется к собранию в Teams в качестве гостевого пользователя, может получить доступ к чату собрания, только когда он присоединится к вызову собрания в Teams. Дополнительные сведения о добавлении пользователя Служб коммуникации в вызов собрания в Teams см. в документации по взаимодействию с Teams.
Для использования этой функции пользователь должен быть членом организации-владельца обеих сущностей.
Присоединение к чату собрания
Включив взаимодействие с Teams, пользователь Служб коммуникации может присоединиться в качестве внешнего пользователя к вызову в Teams с помощью пакета SDK для вызовов. Присоединение к звонку добавляет их в качестве участника в чат собрания, где они могут отправлять и получать сообщения с другими пользователями во время звонка. Пользователь не будет видеть сообщения в чате, отправленные до его присоединения к вызову. Чтобы присоединиться к собранию и начать беседу, можно выполнить следующие действия.
Добавление чата в вызывающее приложение Teams
На уровне build.gradle
модуля добавьте зависимость от пакета SDK для чата.
Важно!
Известная проблема: при использовании в Android пакетов SDK для вызовов и чатов вместе в одном приложении функция уведомлений в режиме реального времени пакета SDK для чата не работает. Возникает проблема с разрешением зависимости. Пока мы работаем над решением, вы можете отключить функцию уведомлений в режиме реального времени, добавив следующие исключения в зависимость от SDK для чата в файле приложения build.gradle
:
implementation ("com.azure.android:azure-communication-chat:1.0.0") {
exclude group: 'com.microsoft', module: 'trouter-client-android'
}
Добавление макета пользовательского интерфейса Teams
Замените код в activity_main.xml приведенным ниже фрагментом кода. Он добавляет входные данные для идентификатора потока и для отправки сообщений, кнопку для отправки типизированного сообщения и базовый макет чата.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/teams_meeting_thread_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="128dp"
android:ems="10"
android:hint="Meeting Thread Id"
android:inputType="textUri"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/teams_meeting_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="64dp"
android:ems="10"
android:hint="Teams meeting link"
android:inputType="textUri"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/button_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/teams_meeting_thread_id">
<Button
android:id="@+id/join_meeting_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Join Meeting" />
<Button
android:id="@+id/hangup_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hangup" />
</LinearLayout>
<TextView
android:id="@+id/call_status_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/recording_status_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ScrollView
android:id="@+id/chat_box"
android:layout_width="374dp"
android:layout_height="294dp"
android:layout_marginTop="40dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toTopOf="@+id/send_message_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_layout"
android:orientation="vertical"
android:gravity="bottom"
android:layout_gravity="bottom"
android:fillViewport="true">
<LinearLayout
android:id="@+id/chat_box_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="bottom"
android:layout_gravity="top"
android:layout_alignParentBottom="true"/>
</ScrollView>
<EditText
android:id="@+id/message_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="588dp"
android:ems="10"
android:inputType="textUri"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Type your message here..."
tools:visibility="invisible" />
<Button
android:id="@+id/send_message_button"
android:layout_width="138dp"
android:layout_height="45dp"
android:layout_marginStart="133dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="133dp"
android:text="Send Message"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/recording_status_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.428"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chat_box" />
</androidx.constraintlayout.widget.ConstraintLayout>
Включение элементов управления пользовательского интерфейса Teams
Импорт пакетов и определение переменных состояния
К содержимому MainActivity.java
добавьте следующие операции импорта:
import android.graphics.Typeface;
import android.graphics.Color;
import android.text.Html;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.azure.android.communication.chat.ChatThreadAsyncClient;
import com.azure.android.communication.chat.ChatThreadClientBuilder;
import com.azure.android.communication.chat.models.ChatMessage;
import com.azure.android.communication.chat.models.ChatMessageType;
import com.azure.android.communication.chat.models.ChatParticipant;
import com.azure.android.communication.chat.models.ListChatMessagesOptions;
import com.azure.android.communication.chat.models.SendChatMessageOptions;
import com.azure.android.communication.common.CommunicationIdentifier;
import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.core.rest.util.paging.PagedAsyncStream;
import com.azure.android.core.util.AsyncStreamHandler;
Добавьте в класс MainActivity
следующие переменные:
// InitiatorId is used to differentiate incoming messages from outgoing messages
private static final String InitiatorId = "<USER_ID>";
private static final String ResourceUrl = "<COMMUNICATION_SERVICES_RESOURCE_ENDPOINT>";
private String threadId;
private ChatThreadAsyncClient chatThreadAsyncClient;
// The list of ids corresponsding to messages which have already been processed
ArrayList<String> chatMessages = new ArrayList<>();
Замените <USER_ID>
идентификатором пользователя, запускающего чат.
Замените <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT>
значением конечной точки для ресурса Служб коммуникации.
Инициализация ChatThreadClient
После присоединения к собранию создайте экземпляр ChatThreadClient
и сделайте компоненты чата видимыми.
Измените конец метода MainActivity.joinTeamsMeeting()
, используя приведенный ниже код:
private void joinTeamsMeeting() {
...
EditText threadIdView = findViewById(R.id.teams_meeting_thread_id);
threadId = threadIdView.getText().toString();
// Initialize Chat Thread Client
chatThreadAsyncClient = new ChatThreadClientBuilder()
.endpoint(ResourceUrl)
.credential(new CommunicationTokenCredential(UserToken))
.chatThreadId(threadId)
.buildAsyncClient();
Button sendMessageButton = findViewById(R.id.send_message_button);
EditText messageBody = findViewById(R.id.message_body);
// Register the method for sending messages and toggle the visibility of chat components
sendMessageButton.setOnClickListener(l -> sendMessage());
sendMessageButton.setVisibility(View.VISIBLE);
messageBody.setVisibility(View.VISIBLE);
// Start the polling for chat messages immediately
handler.post(runnable);
}
Включение отправки сообщений
Добавьте метод sendMessage()
в MainActivity
. Он использует для отправки ChatThreadClient
сообщений от имени пользователя.
private void sendMessage() {
// Retrieve the typed message content
EditText messageBody = findViewById(R.id.message_body);
// Set request options and send message
SendChatMessageOptions options = new SendChatMessageOptions();
options.setContent(messageBody.getText().toString());
options.setSenderDisplayName("Test User");
chatThreadAsyncClient.sendMessage(options);
// Clear the text box
messageBody.setText("");
}
Включение опроса сообщений и их отрисовка в приложении
Важно!
Известная ошибка: так как функция уведомлений в режиме реального времени пакета SDK для чата не работает вместе с пакетом SDK для вызовов, нам придется опрашивать API GetMessages
через определенные интервалы. В нашем примере мы будем использовать 3-секундные интервалы.
Из списка сообщений, возвращенного API GetMessages
, можно получить следующие данные:
- сообщения
text
иhtml
в потоке после присоединения; - изменения в списке потоков;
- обновления темы потока.
Добавьте в MainActivity
класс обработчик и выполняемую задачу, которая будет выполняться с интервалом в 3 секунды:
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
try {
retrieveMessages();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Repeat every 3 seconds
handler.postDelayed(runnable, 3000);
}
};
Обратите внимание на то, что эта задача уже запускается в конце метода MainActivity.joinTeamsMeeting()
, обновленного на этапе инициализации.
Наконец, мы добавим метод для запроса всех доступных сообщений в потоке, анализируя их по типу html
сообщения и отображая те из них и text
:
private void retrieveMessages() throws InterruptedException {
// Initialize the list of messages not yet processed
ArrayList<ChatMessage> newChatMessages = new ArrayList<>();
// Retrieve all messages accessible to the user
PagedAsyncStream<ChatMessage> messagePagedAsyncStream
= this.chatThreadAsyncClient.listMessages(new ListChatMessagesOptions(), null);
// Set up a lock to wait until all returned messages have been inspected
CountDownLatch latch = new CountDownLatch(1);
// Traverse the returned messages
messagePagedAsyncStream.forEach(new AsyncStreamHandler<ChatMessage>() {
@Override
public void onNext(ChatMessage message) {
// Messages that should be displayed in the chat
if ((message.getType().equals(ChatMessageType.TEXT)
|| message.getType().equals(ChatMessageType.HTML))
&& !chatMessages.contains(message.getId())) {
newChatMessages.add(message);
chatMessages.add(message.getId());
}
if (message.getType().equals(ChatMessageType.PARTICIPANT_ADDED)) {
// Handle participants added to chat operation
List<ChatParticipant> participantsAdded = message.getContent().getParticipants();
CommunicationIdentifier participantsAddedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
if (message.getType().equals(ChatMessageType.PARTICIPANT_REMOVED)) {
// Handle participants removed from chat operation
List<ChatParticipant> participantsRemoved = message.getContent().getParticipants();
CommunicationIdentifier participantsRemovedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
if (message.getType().equals(ChatMessageType.TOPIC_UPDATED)) {
// Handle topic updated
String newTopic = message.getContent().getTopic();
CommunicationIdentifier topicUpdatedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
}
@Override
public void onError(Throwable throwable) {
latch.countDown();
}
@Override
public void onComplete() {
latch.countDown();
}
});
// Wait until the operation completes
latch.await(1, TimeUnit.MINUTES);
// Returned messages should be ordered by the createdOn field to be guaranteed a proper chronological order
// For the purpose of this demo we will just reverse the list of returned messages
Collections.reverse(newChatMessages);
for (ChatMessage chatMessage : newChatMessages)
{
LinearLayout chatBoxLayout = findViewById(R.id.chat_box_layout);
// For the purpose of this demo UI, we don't need to use HTML formatting for displaying messages
// The Teams client always sends html messages in meeting chats
String message = Html.fromHtml(chatMessage.getContent().getMessage(), Html.FROM_HTML_MODE_LEGACY).toString().trim();
TextView messageView = new TextView(this);
messageView.setText(message);
// Compare with sender identifier and align LEFT/RIGHT accordingly
// Azure Communication Services users are of type CommunicationUserIdentifier
CommunicationIdentifier senderId = chatMessage.getSenderCommunicationIdentifier();
if (senderId instanceof CommunicationUserIdentifier
&& InitiatorId.equals(((CommunicationUserIdentifier) senderId).getId())) {
messageView.setTextColor(Color.GREEN);
messageView.setGravity(Gravity.RIGHT);
} else {
messageView.setTextColor(Color.BLUE);
messageView.setGravity(Gravity.LEFT);
}
// Note: messages with the deletedOn property set to a timestamp, should be marked as deleted
// Note: messages with the editedOn property set to a timestamp, should be marked as edited
messageView.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
chatBoxLayout.addView(messageView);
}
}
Клиент Teams не задает отображаемые имена участников потока чата. Имена возвращаются как null в API для перечисления участников, в событии participantsAdded
и в событии participantsRemoved
. Отображаемые имена участников чата можно получить из поля remoteParticipants
объекта call
.
Получение данных о беседе в чате для пользователя Служб коммуникации
Сведения о собрании Teams можно получить с помощью API-интерфейсов Graph. Подробности см. в документации по Graph. Пакет SDK вызовов Служб коммуникации принимает полную ссылку на собрание Teams или идентификатор собрания. Они предоставляются как часть ресурса onlineMeeting
, доступного под свойством joinWebUrl
.
С помощью API Graph можно также получить threadID
. Ответ содержит chatInfo
объект , содержащий threadID
.
Вы также можете получить необходимую информацию о собрании и идентификатор потока по URL-адресу Присоединиться к собранию в самом приглашении на собрание в Teams.
Ссылка на собрание в Teams выглядит следующим образом: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here
. — threadId
это расположение meeting_chat_thread_id
в ссылке. Перед использованием убедитесь, что объект meeting_chat_thread_id
не экранирован. Он должен иметь следующий формат: 19:meeting_ZWRhZDY4ZGUtYmRlNS00OWZaLTlkZTgtZWRiYjIxOWI2NTQ4@thread.v2
.
Выполнение кода
Теперь вы можете запустить приложение с помощью кнопки Run App (Запустить приложение) на панели инструментов или нажав клавиши SHIFT+F10.
Чтобы присоединиться к собранию и чату Teams, введите ссылку на собрание Teams и идентификатор потока в пользовательском интерфейсе.
После присоединения к собранию команды необходимо допустить пользователя к собранию в клиенте вашей команды. После того как пользователь будет допущен и присоединился к чату, вы сможете отправлять и получать сообщения.
Примечание
Некоторые функции в настоящее время не поддерживаются для сценариев взаимодействия с Teams. Дополнительные сведения о поддерживаемых функциях см. в статье Возможности собраний Teams для внешних пользователей Teams.
В этом кратком руководстве описывается, как общаться в чате на собрании Teams с помощью пакета SDK для чата Служб коммуникации Azure для C#.
Образец кода
Код для этого краткого руководства можно найти на сайте GitHub.
Предварительные требования
- Развертывание Teams.
- Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно.
- Установите Visual Studio 2019 с рабочей нагрузкой разработки для универсальной платформы Windows.
- Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации.
- Ссылка на собрание Teams.
Присоединение к чату собрания
Пользователь Служб коммуникации может анонимно присоединиться к собранию Teams с помощью пакета SDK для вызовов. При этом пользователь будет также добавлен в чат собрания как его участник, где он сможет обмениваться сообщениями с другими участниками собрания. У пользователя не будет доступа к сообщениям чата, которые были отправлены до того, как он присоединился к собранию, и он не сможет отправлять или получать сообщения после завершения собрания. Чтобы присоединиться к собранию и начать беседу, можно выполнить следующие действия.
Выполнение кода
Вы можете выполнить сборку и запустить код в Visual Studio. Учтите, что поддерживаются такие платформы решения: x64
, x86
и ARM64
.
- Откройте экземпляр PowerShell, Терминал Windows, командную строку или аналогичную среду и перейдите к каталогу, в который вы хотите клонировать пример.
git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
- Откройте проект ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj в Visual Studio.
- Установите следующие или более поздние версии пакетов NuGet:
Install-Package Azure.Communication.Calling -Version 1.0.0-beta.29
Install-Package Azure.Communication.Chat -Version 1.1.0
Install-Package Azure.Communication.Common -Version 1.0.1
Install-Package Azure.Communication.Identity -Version 1.0.1
- Используя ресурс Служб коммуникации, полученный на этапе выполнения предварительных требований, добавьте строку подключения в файл ChatTeamsInteropQuickStart/MainPage.xaml.cs.
//Azure Communication Services resource connection string i.e = "endpoint=https://your-resource.communication.azure.net/;accesskey=your-access-key";
private const string connectionString_ = "";
Важно!
- Прежде чем выполнить этот код, выберите нужную платформу в раскрывающемся списке "Платформы решения" в Visual Studio. Например
x64
- Убедитесь, что в Windows 10 включен режим разработчика (Параметры разработчика).
Если эти настройки заданы неправильно, следующие шаги не удастся выполнить.
- Нажмите клавишу F5, чтобы запустить проект в режиме отладки.
- Вставьте допустимую ссылку на собрание Teams в соответствующем поле (см. следующий раздел).
- Нажмите кнопку "Присоединиться к собранию в Teams", чтобы начать беседу.
Важно!
Когда пакет SDK для звонков установит подключение к собранию Teams (см. раздел о приложении Windows для вызовов с помощью Служб коммуникации), ключевыми функциями для управления операциями чата будут StartPollingForChatMessages и SendMessageButton_Click. Оба фрагмента кода находятся в ChatTeamsInteropQuickStart\MainPage.xaml.cs.
/// <summary>
/// Background task that keeps polling for chat messages while the call connection is stablished
/// </summary>
private async Task StartPollingForChatMessages()
{
CommunicationTokenCredential communicationTokenCredential = new(user_token_);
chatClient_ = new ChatClient(EndPointFromConnectionString(), communicationTokenCredential);
await Task.Run(async () =>
{
keepPolling_ = true;
ChatThreadClient chatThreadClient = chatClient_.GetChatThreadClient(thread_Id_);
int previousTextMessages = 0;
while (keepPolling_)
{
try
{
CommunicationUserIdentifier currentUser = new(user_Id_);
AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
SortedDictionary<long, string> messageList = new();
int textMessages = 0;
string userPrefix;
await foreach (ChatMessage message in allMessages)
{
if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
{
textMessages++;
userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{StripHtml(message.Content.Message)}");
}
}
//Update UI just when there are new messages
if (textMessages > previousTextMessages)
{
previousTextMessages = textMessages;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TxtChat.Text = string.Join(Environment.NewLine, messageList.Values.ToList());
});
}
if (!keepPolling_)
{
return;
}
await SetInCallState(true);
await Task.Delay(3000);
}
catch (Exception e)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
_ = new MessageDialog($"An error ocurred while fetching messages in PollingChatMessagesAsync(). The application will shutdown. Details : {e.Message}").ShowAsync();
throw e;
});
await SetInCallState(false);
}
}
});
}
private async void SendMessageButton_Click(object sender, RoutedEventArgs e)
{
SendMessageButton.IsEnabled = false;
ChatThreadClient chatThreadClient = chatClient_.GetChatThreadClient(thread_Id_);
_ = await chatThreadClient.SendMessageAsync(TxtMessage.Text);
TxtMessage.Text = "";
SendMessageButton.IsEnabled = true;
}
Получение ссылки на собрание Teams
Ссылку на собрание Teams можно получить с помощью интерфейсов API Graph. Подробности см. в документации по Graph. Эта ссылка возвращается как часть ресурса onlineMeeting
, доступного под свойством joinWebUrl
.
Вы также можете получить необходимую ссылку на собрание из URL-адреса Присоединиться к собранию в самом приглашении на собрание Teams.
Ссылка на собрание в Teams выглядит следующим образом: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here
.
Примечание
Некоторые функции в настоящее время не поддерживаются для сценариев взаимодействия с Teams. Дополнительные сведения о поддерживаемых функциях см. в статье Возможности собраний Teams для внешних пользователей Teams.
Очистка ресурсов
Если вы хотите очистить и удалить подписку на Службы коммуникации, вы можете удалить ресурс или группу ресурсов. При этом удаляются все ресурсы, связанные с ней. См. сведения об очистке ресурсов.
Дальнейшие действия
Дополнительные сведения см. в следующих статьях:
- Ознакомьтесь с нашим главным примером функции чата.
- См. дополнительные сведения о принципах работы чата.