Início Rápido: Ingressar seu aplicativo de chat em uma reunião do Teams

Comece a usar os Serviços de Comunicação do Azure conectando sua solução de chat ao Microsoft Teams.

Neste guia de início rápido, você aprenderá a participar de um chat de reunião no Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.

Exemplo de código

Encontre o código finalizado para este guia de início rápido no GitHub.

Pré-requisitos

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. O ingresso na reunião o adiciona como um participante do chat da reunião também, em que ele poderá enviar e receber mensagens com os outros usuários na reunião. O usuário não tem acesso às mensagens de chat que foram enviadas antes de ele ingressar na reunião e não pode enviar nem 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 guia de início rápido usa o webpack para agrupar os ativos do aplicativo. Execute o seguinte comando para instalar os pacotes npm Webpack, webpack-cli e webpack-dev-server e listá-los como dependências de desenvolvimento no 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 permitirá que o usuário ingresse em um 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. A caixa de texto na parte superior da página será usada para entrar no contexto da reunião do Teams. O botão 'Ingressar na reunião do Teams' é usado para ingressar na reunião especificada. Um pop-up de chat é exibido na parte inferior da página. Ele pode ser usado para enviar mensagens na conversa da reunião e exibe em tempo real todas as mensagens enviadas na conversa enquanto o usuário dos Serviços de Comunicação for um participante.

<!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_STRING pela 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];

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

Os nomes de exibição dos participantes da thread de chat não são definidos pelo cliente do Teams. 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

Os usuários do webpack podem usar o webpack-dev-server para compilar e executar 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:

Captura de tela do Aplicativo JavaScript concluído.

Insira o link da reunião do Teams na caixa de texto. Pressione Ingressar na reunião do Teams para participar da reunião. Depois que o usuário dos Serviços de Comunicação for admitido na reunião, ele poderá usar o chat no seu aplicativo de 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. Saiba mais sobre os recursos com suporte em Recursos de reunião do Teams para usuários externos do Teams

Neste guia de início rápido, você aprenderá a participar de um chat de reunião no Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para iOS.

Pré-requisitos

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. O ingresso na reunião o adiciona como um participante do chat da reunião também, em que ele poderá enviar e receber mensagens com os outros usuários na reunião. O usuário não terá acesso às mensagens de chat que foram enviadas antes de ele ingressar na reunião e não poderá enviar nem 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.

Adicionar o Chat ao aplicativo de chamadas do Teams

Exclua o Podfile.lock e substitua o conteúdo do Podfile pelo seguinte:

platform :ios, '13.0'
use_frameworks!

target 'AzureCommunicationCallingSample' do
  pod 'AzureCommunicationCalling', '~> 1.0.0-beta.12'
  pod 'AzureCommunicationChat', '~> 1.0.0-beta.11'
end

Instalar os pacotes de chat

Execute pod install para instalar o pacote AzureCommunicationChat. Após a instalação, abra o arquivo .xcworkspace.

Configurar o framework de aplicativos

Importe o pacote AzureCommunicationChat no ContentView.swift adicionando o seguinte snippet:

import AzureCommunicationChat

Adicionar os controles de interface do usuário do Teams

Em ContentView.swift, adicione o seguinte snippet abaixo das variáveis de estado existentes.

    // Chat
    @State var chatClient: ChatClient?
    @State var chatThreadClient: ChatThreadClient?
    @State var chatMessage: String = ""
    @State var meetingMessages: [MeetingMessage] = []

    let displayName: String = "<YOUR_DISPLAY_NAME_HERE>"

Substitua <YOUR_DISPLAY_NAME_HERE> pelo nome de exibição que você gostaria de usar no Chat.

Em seguida, modificamos o formulário Section para exibir nossas mensagens de chat e adicionar os controles de interface do usuário para conversar.

Adicione o seguinte snippet ao formulário existente. Logo após a exibição de texto Text(recordingStatus) para o status de gravação.

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)

Em seguida, altere o título para Chat Teams Quickstart. Modifique a linha a seguir com este título.

.navigationBarTitle("Chat Teams Quickstart")

Habilitar os controles de interface do usuário do Teams

Inicializar o ChatClient

Instancie o ChatClient e habilita as notificações em tempo real. Usamos as notificações em tempo real para receber mensagens de chat.

Em NavigationView.onAppear, adicione o snippet abaixo após o código existente que inicializa o 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
}

Substitua <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT_HERE> pelo ponto de extremidade do recurso Serviços de Comunicação. Substitua <USER_ACCESS_TOKEN_HERE> por um token de acesso que tenha escopo de Chat.

Leia mais sobre os tokens de acesso do usuário: Token de Acesso do Usuário

Inicializar o ChatThreadClient

Na função existente joinTeamsMeeting(), inicializaremos o ChatThreadClient depois que o usuário ingressar na reunião.

No manipulador de conclusão da chamada para self.callAgent?.join(), adicione o código abaixo do comentário // Initialize the ChatThreadClient. O código completo é mostrado abaixo.

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")
    }
}

Adicione a seguinte função auxiliar ao ContentView, que é usado para recuperar a ID do thread de chat do link de reunião do 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
}

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: 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 = ""
    }
}

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 {
    let id: String
    let content: String
    let displayName: String
}

Em seguida, adicione a função receiveMessage() a ContentView. Esse é o manipulador chamado toda vez que um evento ChatMessageReceived ocorre.

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

Sair do chat

Quando o usuário sair da reunião do Teams, desmarcamos o chat da interface do usuário. O código completo é mostrado abaixo.

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"
    }
}

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.

Você também pode obter as informações da reunião necessárias e a ID do thread na URL Participar da Reunião indicada no convite da própria reunião do Teams. Um link de reunião do Teams é parecido com este: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here. A threadID está onde o meeting_chat_thread_id estiver no link. Verifique se o meeting_chat_thread_id está sem escape antes de usar. Ele deve estar no seguinte formato: 19:meeting_ZWRhZDY4ZGUtYmRlNS00OWZaLTlkZTgtZWRiYjIxOWI2NTQ4@thread.v2

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 for aceito e ingressar no chat, você pode enviar e receber mensagens.

Captura de tela do Aplicativo iOS concluído.

Observação

Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos com suporte em Recursos de reunião do Teams para usuários externos do Teams

Neste início rápido, você aprenderá a participar de um chat de reunião no Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para Android.

Pré-requisitos

Habilitar a interoperabilidade do Teams

Um usuário dos Serviços de Comunicação que participa de uma reunião do Teams como um usuário convidado pode acessar o chat da reunião somente quando ele se conectou à chamada de reunião do Teams. Confira a documentação sobre a interoperabilidade do Teams para saber como adicionar um usuário dos Serviços de Comunicação a uma chamada de reunião 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

Quando a interoperabilidade do Teams estiver habilitada, um usuário dos Serviços de Comunicação poderá participar da chamada do Teams como um usuário externo usando o SDK de Chamada. A participação na chamada o adiciona como um participante do chat da reunião também, em que ele poderá enviar mensagens para outros usuários na chamada e receber mensagens deles. O usuário não terá acesso às mensagens de chat que foram enviadas antes de ele se conectar à chamada. Para ingressar na reunião e iniciar o bate-papo, você pode seguir as próximas etapas.

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: durante o uso conjunto do SDK de Chat e de Chamada do Android no mesmo aplicativo, o recurso de notificações em tempo real do SDK de Chat não funciona. Você obterá 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:1.0.0") {
    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 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 corresponsding 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 método MainActivity.joinTeamsMeeting() com o código abaixo:

    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 SDK de Chamada, precisaremos sondar a API GetMessages em intervalos predefinidos. Em nosso exemplo, usaremos intervalos de 3 segundos.

É possível obter os seguintes dados da lista de mensagens retornada pela API GetMessages:

  • As mensagens text e html no thread desde a junção
  • Alterações na lista de participantes do thread
  • Atualizações para o tópico do thread

Para a classe MainActivity, adicione um manipulador e uma tarefa executável que será 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);
        }
    };

Observe que 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 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);
        }
    }

Os nomes de exibição dos participantes da thread de chat não são definidos pelo cliente do Teams. 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 do 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.

Você também pode obter as informações da reunião necessárias e a ID do thread na URL Participar da Reunião indicada no convite da própria reunião do Teams. Um link de reunião do Teams é parecido com este: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here. A threadId está onde o meeting_chat_thread_id estiver no link. Verifique se o meeting_chat_thread_id está sem escape antes de usar. Ele deve estar no seguinte formato: 19:meeting_ZWRhZDY4ZGUtYmRlNS00OWZaLTlkZTgtZWRiYjIxOWI2NTQ4@thread.v2

Executar o código

Agora, o aplicativo pode ser iniciado usando o 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 for aceito e ingressar no chat, você pode enviar e receber mensagens.

Captura de tela do aplicativo Android concluído.

Observação

Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos com suporte em Recursos de reunião do Teams para usuários externos do Teams

Neste guia de início rápido, você aprenderá a bater papo em uma reunião no Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para C#.

Código de exemplo

Encontre o código deste guia de início rápido no GitHub.

Pré-requisitos

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. O ingresso na reunião o adicionará como um participante do chat da reunião também, em que ele poderá enviar e receber mensagens com os outros usuários na reunião. O usuário não terá acesso às mensagens de chat que foram enviadas antes de ele ingressar na reunião e não poderá enviar nem 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.

Executar o código

Você pode criar e executar o código no Visual Studio. Oferecemos suporte às plataformas de solução x64, x86 e ARM64.

  1. 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.
  2. git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
  3. Abra o projeto ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj no Visual Studio.
  4. 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.1

  1. Com o recurso Serviços de Comunicação adquirido nos pré-requisitos, adicione a cadeia de conexão no 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 apropriada na lista suspensa 'Plataformas de Solução' no Visual Studio antes de executar o código. isto é, x64
  • Verifique se o 'Modo de Desenvolvedor' no Windows 10 está habilitado (Configurações do desenvolvedor)

As próximas etapas não funcionarão se isso não estiver configurado corretamente

  1. Pressione F5 para iniciar o projeto no modo de depuração.
  2. Cole um link de reunião do Teams válido na caixa 'Link de Reunião do Teams' (confira a próxima seção)
  3. Pressione 'Ingressar reunião do Teams' para começar a bater papo.

Importante

Depois que o SDK de chamada estabelecer a conexão com a reunião do Teams, confira os Serviços de Comunicação chamando o aplicativo do Windows. As principais funções para gerenciar as operações de chat são: StartPollingForChatMessages e SendMessageButton_Click. Os dois snippets de código estão em 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 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;
        }

O link da reunião do Teams pode ser recuperado usando as APIs do Graph, detalhadas 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 é parecido com este: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here.

Captura de tela do aplicativo csharp concluído.

Observação

Atualmente, não há suporte para determinados recursos em cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos com suporte em 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.

Próximas etapas

Para obter mais informações, consulte os seguintes artigos: