Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Comece a usar os Serviços de Comunicação do Azure conectando sua solução de chat ao Microsoft Teams.
Este artigo descreve como conversar em uma reunião do Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.
Exemplo de código
Baixe este código no GitHub Azure Samples Conecte seu aplicativo de chat a uma reunião do Teams.
Pré-requisitos
- Uma implantação do Teams.
- Um aplicativo de chat funcional.
Como participar do chat da reunião
Um usuário dos Serviços de Comunicação pode ingressar em uma reunião do Teams como um usuário anônimo por meio do SDK de Chamada. Ingressar na reunião também os adiciona como participantes ao chat da reunião, onde eles podem enviar e receber mensagens com outros usuários na reunião. O usuário não tem acesso às mensagens de chat enviadas antes de ingressar na reunião e não pode enviar ou receber mensagens após o término da reunião. Para ingressar na reunião e iniciar o bate-papo, você pode seguir as próximas etapas.
Criar um novo aplicativo do Node.js
Abra a janela de comando ou do terminal para criar um diretório para seu aplicativo e navegue até ele.
mkdir chat-interop-quickstart && cd chat-interop-quickstart
Execute npm init -y para criar um arquivo package.json com as configurações padrão.
npm init -y
Instalar os pacotes de chat
Use o comando npm install para instalar os SDKs dos Serviços de Comunicação necessários para JavaScript.
npm install @azure/communication-common --save
npm install @azure/communication-identity --save
npm install @azure/communication-chat --save
npm install @azure/communication-calling --save
A opção --save lista a biblioteca como uma dependência no arquivo package.json.
Configurar o framework de aplicativos
Este exemplo usa o webpack para agrupar os ativos do aplicativo. Execute o seguinte comando para instalar os pacotes webpack, webpack-cli e webpack-dev-server npm e listá-los como dependências de desenvolvimento em seu package.json:
npm install webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 --save-dev
Crie um arquivo index.html no diretório raiz do projeto. Usamos esse arquivo para configurar um layout básico que permite que o usuário participe de uma reunião e comece a conversar.
Adicionar os controles de interface do usuário do Teams
Substitua o código em index.html pelo snippet a seguir.
Utilize a caixa de texto na parte superior da página para inserir o contexto da reunião no Teams. Os usuários finais podem ingressar na reunião especificada clicando no botão Ingressar na Reunião do Teams .
Um pop-up de chat é exibido na parte inferior da página. Os usuários finais podem usá-lo para enviar mensagens no thread da reunião. Exibe em tempo real todas as mensagens enviadas no tópico enquanto o usuário dos Serviços de Comunicação é membro.
<!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: 400px;" />
<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>
Habilitar os controles de interface do usuário do Teams
Substitua o conteúdo do arquivo client.js pelo snippet a seguir.
Dentro do snippet, substitua
-
SECRET_CONNECTION_STRINGpela cadeia de conexão do Serviço de Comunicação
import { CallClient } 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 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 messages = "";
var chatThreadId = "";
async function init() {
const connectionString = "<SECRET_CONNECTION_STRING>";
const endpointUrl = connectionString.split(";")[0].replace("endpoint=", "");
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();
const joinCall = (urlString, callAgent) => {
const url = new URL(urlString);
console.log(url);
if (url.pathname.startsWith("/meet")) {
// Short teams URL, so for now call meetingID and pass code API
return callAgent.join({
meetingId: url.pathname.split("/").pop(),
passcode: url.searchParams.get("p"),
});
} else {
return callAgent.join({ meetingLink: urlString }, {});
}
};
callButton.addEventListener("click", async () => {
// join with meeting link
try {
call = joinCall(meetingLinkInput.value, callAgent);
} catch {
throw new Error("Could not join meeting - have you set your connection string?");
}
// Chat thread ID is provided from the call info, after connection.
call.on("stateChanged", async () => {
callStateElement.innerText = call.state;
if (call.state === "Connected" && !chatThreadClient) {
chatThreadId = call.info?.threadId;
chatThreadClient = chatClient.getChatThreadClient(chatThreadId);
chatBox.style.display = "block";
messagesContainer.innerHTML = messages;
// 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 (chatThreadId != e.threadId) {
return;
}
if (e.sender.communicationUserId != userId) {
renderReceivedMessage(e.message);
} else {
renderSentMessage(e.message);
}
});
}
});
// toggle button and chat box states
hangUpButton.disabled = false;
callButton.disabled = true;
console.log(call);
});
async function renderReceivedMessage(message) {
messages += '<div class="container lighter">' + message + "</div>";
messagesContainer.innerHTML = messages;
}
async function renderSentMessage(message) {
messages += '<div class="container darker">' + message + "</div>";
messagesContainer.innerHTML = messages;
}
hangUpButton.addEventListener("click", async () => {
// end the current call
await call.hangUp();
// Stop notifications
chatClient.stopRealtimeNotifications();
// toggle button states
hangUpButton.disabled = true;
callButton.disabled = false;
callStateElement.innerText = "-";
// toggle chat states
chatBox.style.display = "none";
messages = "";
// Remove local ref
chatThreadClient = undefined;
});
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}`);
});
O cliente do Teams não define os nomes de exibição dos participantes do thread de chat. Nos eventos participantsAdded e participantsRemoved, os nomes são retornados como nulos na API com relação à listagem dos participantes. Os nomes de exibição dos participantes do chat podem ser recuperados no campo remoteParticipants do objeto call. Ao receber uma notificação sobre uma alteração na lista de participantes, você pode usar esse código para recuperar o nome do usuário adicionado ou removido:
var displayName = call.remoteParticipants.find(p => p.identifier.communicationUserId == '<REMOTE_USER_ID>').displayName;
Executar o código
Use webpack-dev-server para criar e executar o seu aplicativo. Execute o seguinte comando para empacotar o host de aplicativos em um servidor Web local:
npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map
Abra o navegador e navegue até http://localhost:8080/. Você deverá ver o aplicativo iniciado conforme mostrado na captura de tela a seguir:
Insira o link da reunião do Teams na caixa de texto. Os usuários podem clicar em Ingressar na Reunião do Teams para ingressar na reunião do Teams. Depois que o usuário dos Serviços de Comunicação for admitido na reunião, você poderá conversar de dentro de seu aplicativo dos Serviços de Comunicação. Navegue até a caixa na parte inferior da página para começar a conversar. Para simplificar, o aplicativo mostrará apenas as duas últimas mensagens no chat.
Observação
Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Para obter mais informações sobre recursos com suporte, consulte os recursos de reunião do Teams para usuários externos do Teams.
Este artigo descreve como conversar em uma reunião do Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para iOS.
Exemplo de código
Baixe este código no GitHub Azure Samples Conecte seu aplicativo de chat a uma reunião do Teams.
Pré-requisitos
- Uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente
- Um Mac executando Xcode, juntamente com um certificado de desenvolvedor válido instalado em seu conjunto de chaves.
- Uma implantação do Teams.
- Um Token de Acesso do Usuário para o seu Serviço de Comunicação do Azure. Você também pode usar a CLI do Azure e executar o comando abaixo com a cadeia de conexão para criar um usuário e um token de acesso.
az communication user-identity token issue --scope voip chat --connection-string "yourConnectionString"
Para obter detalhes, confira Usar a CLI do Azure para criar e gerenciar tokens de acesso.
Configurando
Como criar o projeto do Xcode
No Xcode, crie um novo projeto do iOS e selecione o modelo Aplicativo de exibição única. Este tutorial usa a estrutura SwiftUI. Portanto, você deve definir a Linguagem como Swift e a Interface do Usuário como SwiftUI. Você não criará testes durante este guia de início rápido. Fique à vontade para desmarcar Incluir Testes.
Como instalar o CocoaPods
Use este guia para instalar o CocoaPods no Mac.
Instale o pacote e as dependências com o CocoaPods
Para criar um
Podfilepara seu aplicativo, abra o terminal, procure a pasta do projeto e execute pod init.Adicione o código a seguir ao
Podfileno destino e salve.
target 'Chat Teams Interop' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for Chat Teams Interop
pod 'AzureCommunicationCalling'
pod 'AzureCommunicationChat'
end
Execute
pod install.Abra o arquivo
.xcworkspacecom o Xcode.
Solicitar acesso ao microfone
Para acessar o microfone do dispositivo, você precisa atualizar a Lista de Propriedades de Informações do aplicativo com um NSMicrophoneUsageDescription. Você define o valor associado como string que foi incluído na caixa de diálogo que o sistema usa para solicitar o acesso do usuário.
No destino, selecione a guia Info e adicione uma cadeia de caracteres a Privacy - Microphone Usage Description.
Desabilitar a área restrita de script de usuário
Alguns dos scripts nas bibliotecas vinculadas gravam arquivos durante o processo de compilação. Para habilitar a gravação de arquivos, desabilite a área restrita de script do usuário no Xcode.
Nas configurações de compilação, pesquise por sandbox e defina User Script Sandboxing como No.
Como participar do chat da reunião
Um usuário dos Serviços de Comunicação pode ingressar em uma reunião do Teams como um usuário anônimo por meio do SDK de Chamada. Depois que um usuário ingressar na reunião do Teams, ele poderá enviar e receber mensagens com outros participantes da reunião. O usuário não tem acesso a mensagens de chat enviadas antes de ingressar, nem pode enviar ou receber mensagens quando não estiver na reunião.
Para ingressar na reunião e começar a conversar, siga as próximas etapas.
Configurar o framework de aplicativos
Importe os pacotes de Comunicação do Azure no ContentView.swift adicionando o seguinte snippet:
import AVFoundation
import SwiftUI
import AzureCommunicationCalling
import AzureCommunicationChat
Em ContentView.swift, adicione o seguinte snippet, logo acima da declaração struct ContentView: View:
let endpoint = "<ADD_YOUR_ENDPOINT_URL_HERE>"
let token = "<ADD_YOUR_USER_TOKEN_HERE>"
let displayName: String = "Quickstart User"
Substitua <ADD_YOUR_ENDPOINT_URL_HERE> pelo ponto de extremidade do recurso Serviços de Comunicação.
Substitua <ADD_YOUR_USER_TOKEN_HERE> pelo token gerado anteriormente por meio da linha de comando do cliente do Azure.
Para obter mais informações, consulte o Token de Acesso do Usuário
Substitua Quickstart User pelo nome de exibição que você gostaria de usar no Chat.
Para manter o estado, adicione as seguintes variáveis ao struct ContentView:
@State var message: String = ""
@State var meetingLink: String = ""
@State var chatThreadId: String = ""
// Calling state
@State var callClient: CallClient?
@State var callObserver: CallDelegate?
@State var callAgent: CallAgent?
@State var call: Call?
// Chat state
@State var chatClient: ChatClient?
@State var chatThreadClient: ChatThreadClient?
@State var chatMessage: String = ""
@State var meetingMessages: [MeetingMessage] = []
Agora adicione o VAR do corpo principal para manter os elementos da interface do usuário. Anexamos a lógica de negócios a esses controles. Adicione o seguinte código ao struct ContentView:
var body: some View {
NavigationView {
Form {
Section {
TextField("Teams Meeting URL", text: $meetingLink)
.onChange(of: self.meetingLink, perform: { value in
if let threadIdFromMeetingLink = getThreadId(from: value) {
self.chatThreadId = threadIdFromMeetingLink
}
})
TextField("Chat thread ID", text: $chatThreadId)
}
Section {
HStack {
Button(action: joinMeeting) {
Text("Join Meeting")
}.disabled(
chatThreadId.isEmpty || callAgent == nil || call != nil
)
Spacer()
Button(action: leaveMeeting) {
Text("Leave Meeting")
}.disabled(call == nil)
}
Text(message)
}
Section {
ForEach(meetingMessages, id: \.id) { message in
let currentUser: Bool = (message.displayName == displayName)
let foregroundColor = currentUser ? Color.white : Color.black
let background = currentUser ? Color.blue : Color(.systemGray6)
let alignment = currentUser ? HorizontalAlignment.trailing : .leading
HStack {
if currentUser {
Spacer()
}
VStack(alignment: alignment) {
Text(message.displayName).font(Font.system(size: 10))
Text(message.content)
.frame(maxWidth: 200)
}
.padding(8)
.foregroundColor(foregroundColor)
.background(background)
.cornerRadius(8)
if !currentUser {
Spacer()
}
}
}
.frame(maxWidth: .infinity)
}
TextField("Enter your message...", text: $chatMessage)
Button(action: sendMessage) {
Text("Send Message")
}.disabled(chatThreadClient == nil)
}
.navigationBarTitle("Teams Chat Interop")
}
.onAppear {
// Handle initialization of the call and chat clients
}
}
Inicializar o ChatClient
Crie uma instância de ChatClient e habilite as notificações de mensagens. Notificações em tempo real são usadas para receber mensagens de chat.
Com o corpo principal configurado, adicione as funções para lidar com a configuração dos clientes de chamada e chat.
Na função onAppear, adicione o seguinte código para inicializar CallClient e ChatClient:
if let threadIdFromMeetingLink = getThreadId(from: self.meetingLink) {
self.chatThreadId = threadIdFromMeetingLink
}
// Authenticate
do {
let credentials = try CommunicationTokenCredential(token: token)
self.callClient = CallClient()
self.callClient?.createCallAgent(
userCredential: credentials
) { agent, error in
if let e = error {
self.message = "ERROR: It was not possible to create a call agent."
print(e)
return
} else {
self.callAgent = agent
}
}
// Start the chat client
self.chatClient = try ChatClient(
endpoint: endpoint,
credential: credentials,
withOptions: AzureCommunicationChatClientOptions()
)
// Register for real-time notifications
self.chatClient?.startRealTimeNotifications { result in
switch result {
case .success:
self.chatClient?.register(
event: .chatMessageReceived,
handler: receiveMessage
)
case let .failure(error):
self.message = "Could not register for message notifications: " + error.localizedDescription
print(error)
}
}
} catch {
print(error)
self.message = error.localizedDescription
}
Adicionar a função de ingresso na reunião
Adicione a função a seguir ao struct ContentView para processar o ingresso na reunião.
func joinMeeting() {
// Ask permissions
AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
if granted {
let teamsMeetingLink = TeamsMeetingLinkLocator(
meetingLink: self.meetingLink
)
self.callAgent?.join(
with: teamsMeetingLink,
joinCallOptions: JoinCallOptions()
) {(call, error) in
if let e = error {
self.message = "Failed to join call: " + e.localizedDescription
print(e.localizedDescription)
return
}
self.call = call
self.callObserver = CallObserver(self)
self.call?.delegate = self.callObserver
self.message = "Teams meeting joined successfully"
}
} else {
self.message = "Not authorized to use mic"
}
}
}
Inicializar o ChatThreadClient
Nós inicializamos o ChatThreadClient depois que o usuário ingressa na reunião. Em seguida, precisamos verificar o status da reunião no representante e, em seguida, inicializar ChatThreadClient com a threadId ao ingressar na reunião.
Crie a função connectChat() com o seguinte código:
func connectChat() {
do {
self.chatThreadClient = try chatClient?.createClient(
forThread: self.chatThreadId
)
self.message = "Joined meeting chat successfully"
} catch {
self.message = "Failed to join the chat thread: " + error.localizedDescription
}
}
Se possível, adicione a função auxiliar a seguir a ContentView, que é usada para analisar o ID do thread do chat do link da reunião do Teams. Caso essa extração falhe, o usuário precisa inserir manualmente a ID do thread de chat usando APIs do Graph para recuperar a ID do thread.
func getThreadId(from teamsMeetingLink: String) -> String? {
if let range = teamsMeetingLink.range(of: "meetup-join/") {
let thread = teamsMeetingLink[range.upperBound...]
if let endRange = thread.range(of: "/")?.lowerBound {
return String(thread.prefix(upTo: endRange))
}
}
return nil
}
Habilitar o envio de mensagens
Adicione a função sendMessage() a ContentView. Essa função usa o ChatThreadClient para enviar mensagens do usuário.
func sendMessage() {
let message = SendChatMessageRequest(
content: self.chatMessage,
senderDisplayName: displayName,
type: .text
)
self.chatThreadClient?.send(message: message) { result, _ in
switch result {
case .success:
print("Chat message sent")
self.chatMessage = ""
case let .failure(error):
self.message = "Failed to send message: " + error.localizedDescription + "\n Has your token expired?"
}
}
}
Habilitar o recebimento de mensagens
Para receber mensagens, implementamos o manipulador de ChatMessageReceived eventos. Quando novas mensagens são enviadas para o thread, esse manipulador adiciona as mensagens à variável meetingMessages para que possam ser exibidas na interface do usuário.
Primeiro, adicione o struct ContentView.swift a seguir. A interface do usuário usa os dados no struct para exibir nossas mensagens de chat.
struct MeetingMessage: Identifiable {
let id: String
let date: Date
let content: String
let displayName: String
static func fromTrouter(event: ChatMessageReceivedEvent) -> MeetingMessage {
let displayName: String = event.senderDisplayName ?? "Unknown User"
let content: String = event.message.replacingOccurrences(
of: "<[^>]+>", with: "",
options: String.CompareOptions.regularExpression
)
return MeetingMessage(
id: event.id,
date: event.createdOn?.value ?? Date(),
content: content,
displayName: displayName
)
}
}
Em seguida, adicione a função receiveMessage() a ContentView. Quando ocorre um evento de mensagens, ele chama essa função. Você precisa registrar todos os eventos que deseja manipular na instrução switch por meio do método chatClient?.register().
func receiveMessage(event: TrouterEvent) -> Void {
switch event {
case let .chatMessageReceivedEvent(messageEvent):
let message = MeetingMessage.fromTrouter(event: messageEvent)
self.meetingMessages.append(message)
/// OTHER EVENTS
// case .realTimeNotificationConnected:
// case .realTimeNotificationDisconnected:
// case .typingIndicatorReceived(_):
// case .readReceiptReceived(_):
// case .chatMessageEdited(_):
// case .chatMessageDeleted(_):
// case .chatThreadCreated(_):
// case .chatThreadPropertiesUpdated(_):
// case .chatThreadDeleted(_):
// case .participantsAdded(_):
// case .participantsRemoved(_):
default:
break
}
}
Por fim, implemente o manipulador delegado para o cliente da chamada. Use esse manipulador para verificar o status da chamada e inicializar o cliente de chat quando o usuário ingressar na reunião.
class CallObserver : NSObject, CallDelegate {
private var owner: ContentView
init(_ view: ContentView) {
owner = view
}
func call(
_ call: Call,
didChangeState args: PropertyChangedEventArgs
) {
owner.message = CallObserver.callStateToString(state: call.state)
if call.state == .disconnected {
owner.call = nil
owner.message = "Left Meeting"
} else if call.state == .inLobby {
owner.message = "Waiting in lobby (go let them in!)"
} else if call.state == .connected {
owner.message = "Connected"
owner.connectChat()
}
}
private static func callStateToString(state: CallState) -> String {
switch state {
case .connected: return "Connected"
case .connecting: return "Connecting"
case .disconnected: return "Disconnected"
case .disconnecting: return "Disconnecting"
case .earlyMedia: return "EarlyMedia"
case .none: return "None"
case .ringing: return "Ringing"
case .inLobby: return "InLobby"
default: return "Unknown"
}
}
}
Sair do chat
Quando o usuário sai da reunião do Teams, limpamos as mensagens de Chat da interface do usuário e desligamos a chamada. Veja o exemplo de código completo a seguir.
func leaveMeeting() {
if let call = self.call {
self.chatClient?.unregister(event: .chatMessageReceived)
self.chatClient?.stopRealTimeNotifications()
call.hangUp(options: nil) { (error) in
if let e = error {
self.message = "Leaving Teams meeting failed: " + e.localizedDescription
} else {
self.message = "Leaving Teams meeting was successful"
}
}
self.meetingMessages.removeAll()
} else {
self.message = "No active call to hangup"
}
}
Obter um thread de chat da reunião do Teams para um usuário dos Serviços de Comunicação
Os detalhes da reunião do Teams podem ser recuperados usando as APIs do Graph, detalhadas na documentação do Graph. O SDK de Chamada de Serviços de Comunicação aceita um link completo de reunião do Teams ou um ID de reunião. Esse link é retornado como parte do recurso onlineMeeting, acessível sob a joinWebUrlpropriedade
Com as APIs do Graph, você também pode obter o threadID. A resposta tem um objeto chatInfo que contém o threadID.
Executar o código
Execute o aplicativo.
Para ingressar na reunião do Teams, insira o link de reunião do Teams na interface do usuário.
Depois de ingressar na reunião do Teams, você precisa aceitar o usuário para a reunião no cliente do Teams. Depois que o usuário é admitido e ingressa no chat, ele pode enviar e receber mensagens.
Observação
Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Para obter mais informações sobre recursos com suporte, consulte os recursos de reunião do Teams para usuários externos do Teams.
Este artigo descreve como adicionar o chat de reunião do Teams ao seu aplicativo usando o SDK de Chat dos Serviços de Comunicação do Azure para Android.
Exemplo de código
Baixe este código no GitHub Azure Samples Conecte seu aplicativo de chat a uma reunião do Teams.
Pré-requisitos
- Uma implantação do Teams.
- Um aplicativo de chamadas funcional.
Habilitar a interoperabilidade do Teams
Um usuário dos Serviços de Comunicação que ingressa em uma reunião do Teams como usuário convidado pode acessar o chat da reunião somente depois de ingressar na chamada de reunião do Teams. Para obter mais informações sobre como adicionar um usuário dos Serviços de Comunicação a uma chamada de reunião do Teams, consulte a interoperabilidade do Teams.
Você precisa ser membro da organização proprietária de ambas as entidades para usar esse recurso.
Como participar do chat da reunião
Depois de habilitar a interoperabilidade do Teams, um usuário dos Serviços de Comunicação poderá ingressar na chamada do Teams como um usuário externo usando o SDK de Chamada. Ingressar na chamada também os adiciona como participantes ao chat da reunião. No chat, eles podem enviar e receber mensagens com outros usuários na chamada. O usuário não tem acesso às mensagens de chat que foram enviadas antes de ele se conectar à chamada. Para permitir que os usuários finais participem das reuniões do Teams e comecem a conversar, conclua as etapas a seguir.
Adicionar o Chat ao aplicativo de chamadas do Teams
No nível de módulo do build.gradle, adicione a dependência no SDK de chat.
Importante
Problema conhecido: quando você estiver usando o SDK de Chat e Chamada do Android juntos no mesmo aplicativo, o recurso de notificações em tempo real do SDK de Chat não funciona. Você gera um problema de resolução de dependência. Enquanto trabalhamos em uma solução, você pode desativar o recurso de notificações em tempo real adicionando as exclusões a seguir à dependência do SDK de Chat no arquivo build.gradle do aplicativo:
implementation ("com.azure.android:azure-communication-chat:2.0.3") {
exclude group: 'com.microsoft', module: 'trouter-client-android'
}
Adicionar o layout da interface do usuário do Teams
Substitua o código em activity_main.xml pelo snippet a seguir. Ele adiciona entradas para a ID do thread e para enviar mensagens, um botão para enviar a mensagem digitada e um layout de chat básico.
<?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>
Habilitar os controles de interface do usuário do Teams
Importar pacotes e definir variáveis de estado
Para o conteúdo de MainActivity.java, adicione as seguintes importações:
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 java.util.List;
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;
Para a classe MainActivity, adicione as seguintes variáveis:
// 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 corresponding to messages which have already been processed
ArrayList<String> chatMessages = new ArrayList<>();
Substitua <USER_ID> pela ID do usuário que está iniciando o chat.
Substitua <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT> pelo ponto de extremidade do recurso Serviços de Comunicação.
Inicializar o ChatThreadClient
Depois de ingressar na reunião, crie uma instância do ChatThreadClient e torne visíveis os componentes do chat.
Atualize o final do MainActivity.joinTeamsMeeting() método com o seguinte código:
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);
}
Habilitar o envio de mensagens
Adicione o método sendMessage() a MainActivity. Ele usa ChatThreadClient para enviar mensagens em nome do usuário.
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("");
}
Habilitar sondagem de mensagens e renderizá-las no aplicativo
Importante
Problema conhecido: como o recurso de notificações em tempo real do SDK de Chat não funciona junto com os SDKs de Chamada, você precisa sondar a GetMessages API em intervalos predefinidos. Neste exemplo, usamos intervalos de 3 segundos.
É possível obter os seguintes dados da lista de mensagens retornada pela API GetMessages:
- As mensagens
textehtmlno thread desde a junção - Alterações na lista de participantes do thread
- Atualizações para o tópico do thread
Para a MainActivity classe, adicione um manipulador e uma tarefa executável que é executada em intervalos de 3 segundos:
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);
}
};
A tarefa já foi iniciada no final do método MainActivity.joinTeamsMeeting() atualizado na etapa de inicialização.
Por fim, adicionamos o método para consultar todas as mensagens acessíveis no thread, analisá-las por tipo de mensagem e exibir os html e 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 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);
}
}
O cliente do Teams não define os nomes de exibição dos participantes do thread de chat. Nos eventos participantsAdded e participantsRemoved, os nomes são retornados como nulos na API com relação à listagem dos participantes. Os nomes de exibição dos participantes do chat podem ser recuperados no campo remoteParticipants do objeto call.
Obter um thread de chat da reunião do Teams para um usuário dos Serviços de Comunicação
Os detalhes da reunião do Teams podem ser recuperados usando as APIs do Graph, detalhadas na documentação do Graph. O SDK de Chamada de Serviços de Comunicação aceita um link completo de reunião do Teams ou um ID de reunião. Esse link é retornado como parte do recurso onlineMeeting, acessível sob a joinWebUrlpropriedade
Com as APIs do Graph, você também pode obter o threadID. A resposta tem um objeto chatInfo que contém o threadID.
Executar o código
Agora você pode iniciar o aplicativo no botão Executar Aplicativo na barra de ferramentas (Shift+F10).
Para ingressar na reunião e chat do Teams, insira o link de reunião a ID do thread do Teams na interface do usuário.
Depois de ingressar na reunião do Teams, você precisará aceitar o usuário para a reunião no cliente do Teams. Depois que o usuário é admitido e ingressa no chat, ele pode enviar e receber mensagens.
Observação
Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Para obter mais informações sobre recursos com suporte, consulte os recursos de reunião do Teams para usuários externos do Teams
Este artigo descreve como conversar em uma reunião do Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para C#.
Código de exemplo
Baixe este código no GitHub Azure Samples Conecte seu aplicativo de chat a uma reunião do Teams.
Pré-requisitos
- Uma implantação do Teams.
- Uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Instale o Visual Studio 2019 com a carga de trabalho de desenvolvimento da Plataforma Universal do Windows.
- Um recurso dos Serviços de Comunicação implantado. Crie um recurso dos Serviços de Comunicação.
- Um link de reunião do Teams.
Como participar do chat da reunião
Um usuário dos Serviços de Comunicação pode ingressar em uma reunião do Teams anonimamente usando o SDK de Chamada. Ingressar na reunião também os adiciona como participantes ao chat da reunião. No chat, eles podem enviar e receber mensagens com outros usuários na reunião. O usuário não tem acesso às mensagens de chat que foram enviadas antes de ingressarem na reunião. Eles não podem enviar ou receber mensagens após o término da reunião. Para permitir que os usuários participem das reuniões do Teams e comecem a conversar, conclua as etapas a seguir.
Executar o código
Você pode criar e executar o código no Visual Studio. As plataformas de solução com suporte são: x64,x86e ARM64.
Abra uma instância do PowerShell, terminal do Windows, prompt de comando ou equivalente e navegue até o diretório para o qual você deseja clonar o exemplo.
git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.gitAbra o projeto
ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csprojno Visual Studio.Instale as seguintes versões de pacotes NuGet (ou superiores):
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.1Com o recurso dos Serviços de Comunicação adquirido nos pré-requisitos, adicione a cadeia de conexão ao arquivo 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_ = "";Importante
- Selecione a plataforma adequada na lista suspensa plataformas de solução no Visual Studio antes de executar o código, como
x64 - Habilite o Modo de Desenvolvedor no Windows (Configurações do Desenvolvedor)
As próximas etapas não funcionarão se a plataforma não estiver configurada corretamente.
- Selecione a plataforma adequada na lista suspensa plataformas de solução no Visual Studio antes de executar o código, como
Pressione F5 para iniciar o projeto no modo de depuração.
Cole um link de reunião válido do Teams na caixa Link de Reunião do Teams (consulte a próxima seção).
Os usuários finais clicam em Ingressar na reunião do Teams para começar a conversar.
Importante
Depois que o SDK de chamada estabelece uma conexão com a reunião do Teams – veja Chamada dos Serviços de Comunicação do aplicativo do Windows, as principais funções para lidar com as operações de chat são: StartPollingForChatMessages e SendMessageButton_Click. Ambos os snippets de código estão no ChatTeamsInteropQuickStart\MainPage.xaml.cs arquivo
/// <summary>
/// Background task that keeps polling for chat messages while the call connection is established
/// </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 occurred 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;
}
Obter o link de reunião do Teams
Recupere o link de reunião do Teams usando APIs do Graph, conforme descrito na documentação do Graph. Esse link é retornado como parte do recurso onlineMeeting, acessível sob a propriedade joinWebUrl.
Você também pode obter o link da reunião necessário na URL Ingressar na Reunião indicada no próprio convite da reunião do Teams.
Um link de reunião do Teams se parece com a seguinte URL:
https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here`.
Se o link do Teams tiver um formato diferente, você precisará recuperar a ID do thread usando a API do Graph.
Observação
Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Para obter mais informações sobre recursos com suporte, consulte os recursos de reunião do Teams para usuários externos do Teams.
Limpar os recursos
Se quiser limpar e remover uma assinatura dos Serviços de Comunicação, exclua o recurso ou o grupo de recursos. Excluir o grupo de recursos também exclui todos os recursos associados a ele. Saiba mais sobre como limpar recursos.
Artigos relacionados
- Confira nosso exemplo de chat em destaque
- Saiba mais sobre como o chat funciona