Partilhar via


Guia de início rápido: ingressar seu aplicativo de bate-papo 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 conversar em uma reunião do Teams usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.

Código de Exemplo

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

Pré-requisitos

Participar no chat da reunião

Um usuário dos Serviços de Comunicação pode participar de uma reunião do Teams como um usuário anônimo usando o SDK de Chamada. Participar da reunião também os adiciona como participantes ao bate-papo da reunião, onde eles podem enviar e receber mensagens com outros usuários na reunião. O usuário não terá acesso às mensagens de bate-papo que foram enviadas antes de entrar na reunião e não poderá enviar ou receber mensagens após o término da reunião. Para participar da reunião e começar a conversar, você pode seguir as próximas etapas.

Criar uma nova aplicação Node.js

Abra o terminal ou a janela de comando, crie um novo 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 npm install comando para instalar os SDKs de 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 --save opção lista a biblioteca como uma dependência em seu arquivo package.json .

Configurar a estrutura do aplicativo

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 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 seu projeto. Usamos esse arquivo para configurar um layout básico que permitirá que o usuário participe de uma reunião e comece a conversar.

Adicionar os controles da interface do usuário do Teams

Substitua o código no index.html pelo trecho a seguir. A caixa de texto na parte superior da página será usada para inserir o contexto da reunião do Teams. O botão 'Participar na reunião de equipas' é utilizado para participar na reunião especificada. Um pop-up de bate-papo aparece na parte inferior da página. Ele pode ser usado para enviar mensagens no thread da reunião e exibe em tempo real todas as mensagens enviadas no thread 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 da interface do usuário do Teams

Substitua o conteúdo do arquivo client.js pelo trecho a seguir.

No trecho, substitua

  • SECRET_CONNECTION_STRING com a cadeia de conexão do seu 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}`);
});

Os nomes de exibição dos participantes do tópico de bate-papo não são definidos pelo cliente do Teams. Os nomes são retornados como nulos na API para listar participantes, no participantsAdded evento e no participantsRemoved evento. Os nomes de exibição dos participantes do bate-papo podem ser recuperados do remoteParticipants campo do call objeto. Ao receber uma notificação sobre uma alteração na lista, você pode usar esse código para recuperar o nome do usuário que foi 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 criar e executar seu aplicativo. Execute o seguinte comando para agrupar o host do aplicativo 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ê deve ver o aplicativo iniciado como mostrado na captura de tela a seguir:

Captura de ecrã da Aplicação JavaScript concluída.

Insira o link da reunião do Teams na caixa de texto. Prima Participar na Reunião de Equipas para participar na reunião de Equipas. Depois que o usuário dos Serviços de Comunicação for admitido na reunião, você poderá conversar por chat de dentro do 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 mostra apenas as duas últimas mensagens no chat.

Nota

No momento, determinados recursos não são suportados para cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos suportados, consulte Recursos de reunião do Teams para usuários externos do Teams

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

Código de Exemplo

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Pré-requisitos

  • Uma conta do Azure com uma subscrição ativa. Crie uma conta gratuitamente
  • Um Mac com Xcode, juntamente com um certificado de programador válido instalado no seu Porta-chaves.
  • Uma implantação do Teams.
  • Um Token de Acesso de Usuário para seu Serviço de Comunicação do Azure. Você também pode usar a CLI do Azure e executar o comando com sua 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, consulte Usar a CLI do Azure para criar e gerenciar tokens de acesso.

Configuração

Criando o projeto Xcode

No Xcode, crie um novo projeto iOS e selecione o modelo Single View App. Este tutorial usa a estrutura SwiftUI, portanto, você deve definir o idioma como Swift e a interface do usuário como SwiftUI. Você não vai criar testes durante esse início rápido. Sinta-se à vontade para desmarcar Incluir testes.

Captura de tela mostrando a janela Novo projeto no Xcode.

Instalação do CocoaPods

Use este guia para instalar o CocoaPods no seu Mac.

Instale o pacote e as dependências com o CocoaPods

  1. Para criar um Podfile para seu aplicativo, abra o terminal e navegue até a pasta do projeto e execute pod init.

  2. Adicione o seguinte código ao Podfile abaixo do 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
  1. Execute o pod install.

  2. Abra o arquivo com o .xcworkspace Xcode.

Solicitar acesso ao microfone

Para acessar o microfone do dispositivo, você precisa atualizar a Lista de Propriedades de Informações do seu aplicativo com um NSMicrophoneUsageDescriptionarquivo . Você define o valor associado como um string que foi incluído na caixa de diálogo que o sistema usa para solicitar acesso do usuário.

Abaixo do alvo, selecione a Info guia e adicione uma string para 'Privacidade - Descrição de uso do microfone'

Captura de tela mostrando a adição do uso do microfone no Xcode.

Desativar a área restrita de scripts de usuário

Alguns dos scripts dentro das bibliotecas vinculadas gravam arquivos durante o processo de compilação. Para permitir isso, desative o User Script Sandboxing no Xcode. Nas configurações de compilação, procure sandbox e defina User Script Sandboxing como No.

Captura de tela mostrando a desativação da área restrita de script do usuário no Xcode.

Participar no chat da reunião

Um usuário dos Serviços de Comunicação pode participar de uma reunião do Teams como um usuário anônimo usando o SDK de Chamada. Depois que um usuário ingressa na reunião do Teams, ele pode enviar e receber mensagens com outros participantes da reunião. O usuário não terá acesso às mensagens de bate-papo enviadas antes de entrar, nem poderá enviar ou receber mensagens quando não estiver na reunião. Para participar da reunião e começar a conversar, você pode seguir as próximas etapas.

Configurar a estrutura do aplicativo

Importe os pacotes de Comunicação do Azure adicionando ContentView.swift o seguinte trecho:

import AVFoundation
import SwiftUI

import AzureCommunicationCalling
import AzureCommunicationChat

Adicione ContentView.swift o seguinte trecho, logo acima da struct ContentView: View declaração:

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 dos Serviços de Comunicação. Substitua <ADD_YOUR_USER_TOKEN_HERE> pelo token gerado acima, por meio da linha de comando do cliente do Azure. Leia mais sobre tokens de acesso de usuário: Token de acesso de usuário

Substitua Quickstart User pelo nome de exibição que você gostaria de usar no bate-papo.

Para manter o estado, adicione as seguintes variáveis à ContentView estrutura:

  @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 vamos adicionar o var do corpo principal para manter os elementos da interface do usuário. Anexamos lógica de negócios a esses controles neste início rápido. Adicione o seguinte código à ContentView estrutura:

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

Instancie e habilite as ChatClient notificações de mensagens. Estamos usando notificações em tempo real para receber mensagens de bate-papo.

Com o corpo principal configurado, vamos adicionar as funções para lidar com a configuração dos clientes de chamada e chat.

onAppear Na função, adicione o seguinte código para inicializar o 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 função de ingresso na reunião

Adicione a seguinte função ao ContentView struct para lidar com a entrada 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

Inicializaremos o ChatThreadClient após o usuário ter entrado na reunião. Isso requer que verifiquemos o status da reunião do delegado e, em seguida, inicializemos o ChatThreadClient com o threadId quando ingressou na reunião.

Crie a connectChat() função 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
    }
  }

Adicione a seguinte função auxiliar ao ContentView, usado para analisar o ID do thread do bate-papo do link da reunião da equipe, se possível. Caso essa extração falhe, o usuário precisará inserir manualmente o ID do thread do Chat usando as APIs do Graph para recuperar o 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
}

Ativar o envio de mensagens

Adicione a sendMessage() função a ContentView. Esta 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?"
    }
  }
}

Ativar o recebimento de mensagens

Para receber mensagens, implementamos o manipulador para ChatMessageReceived eventos. Quando novas mensagens são enviadas para o thread, esse manipulador adiciona as mensagens à variável para meetingMessages que possam ser exibidas na interface do usuário.

Primeiro, adicione o seguinte struct ao ContentView.swift. A interface do usuário usa os dados na estrutura para exibir nossas mensagens de bate-papo.

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 receiveMessage() função a ContentView. Isso é chamado quando ocorre um evento de mensagens. Observe que você precisa se registrar para todos os eventos que deseja manipular na switch instrução por meio do chatClient?.register() método.

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

Finalmente, precisamos implementar o manipulador delegado para o cliente de chamada. Esse manipulador é usado para verificar o status da chamada e inicializar o cliente de chat quando o usuário entra 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"
    }
  }
}

Saia do chat

Quando o usuário sai da reunião da equipe, limpamos as mensagens de bate-papo da interface do usuário e desligamos a chamada. O código completo é mostrado abaixo.

  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 tópico de chat de 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 dos Serviços de Comunicação aceita um link de reunião completo do Teams ou uma ID de reunião. Eles são retornados como parte do onlineMeeting recurso, acessíveis sob a joinWebUrl propriedade

Com as APIs do Graph, você também pode obter o threadID. A resposta tem um chatInfo objeto que contém o threadID.

Executar o código

Execute a aplicação.

Para participar da reunião do Teams, insira o link da reunião da sua equipe na interface do usuário.

Depois de participar na reunião da Equipa, tem de admitir o utilizador na reunião no cliente da sua Equipa. Uma vez que o usuário é admitido e entrou no chat, você é capaz de enviar e receber mensagens.

Captura de ecrã da Aplicação iOS concluída.

Nota

No momento, determinados recursos não são suportados para cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos suportados, consulte Recursos de reunião do Teams para usuários externos do Teams

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

Código de Exemplo

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Pré-requisitos

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 bate-papo da reunião somente quando ingressar na chamada de reunião do Teams. Consulte a documentação de interoperabilidade do Teams para saber como adicionar um usuário do Communication Services a uma chamada de reunião do Teams.

Você deve ser membro da organização proprietária de ambas as entidades para usar esse recurso.

Participar no chat da reunião

Depois que a interoperabilidade do Teams estiver habilitada, 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. Participar da chamada também os adiciona como participantes ao bate-papo da reunião, onde eles podem enviar e receber mensagens com outros usuários na chamada. O usuário não tem acesso às mensagens de bate-papo que foram enviadas antes de ingressar na chamada. Para participar da reunião e começar a conversar, você pode seguir as próximas etapas.

Adicionar bate-papo ao aplicativo de chamadas do Teams

No nível build.gradledo módulo , adicione a dependência do SDK de chat.

Importante

Problema conhecido: ao usar o Android Chat e o Calling SDK juntos no mesmo aplicativo, o recurso de notificações em tempo real do Chat SDK não funcionará. Você terá um problema de resolução de dependência. Enquanto estamos trabalhando em uma solução, você pode desativar o recurso de notificações em tempo real adicionando as seguintes exclusões à dependência do SDK de bate-papo no arquivo do build.gradle 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 no activity_main.xml pelo trecho a seguir. Ele adiciona entradas para o ID do thread e para o envio de mensagens, um botão para enviar a mensagem digitada e um layout de bate-papo 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 da interface do usuário do Teams

Importar pacotes e definir variáveis de estado

Ao conteúdo do 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 MainActivity classe, 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> pelo ID do usuário que inicia o bate-papo. Substitua <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT> pelo ponto de extremidade do recurso dos Serviços de Comunicação.

Inicializar o ChatThreadClient

Depois de entrar na reunião, instancie e ChatThreadClient torne os componentes de chat visíveis.

Atualize o final do MainActivity.joinTeamsMeeting() método 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);
    }

Ativar o envio de mensagens

Adicione o sendMessage() método a MainActivity. Ele usa o 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 para mensagens e processá-las no aplicativo

Importante

Problema conhecido: Como o recurso de notificações em tempo real do SDK de Chat não funciona em conjunto com o SDK de Chamada, teremos que pesquisar a GetMessages API em intervalos predefinidos. Em nossa amostra, usaremos intervalos de 3 segundos.

Podemos obter os seguintes dados da lista de mensagens retornadas pela GetMessages API:

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

Para a classe, adicione um manipulador e uma tarefa executável que será executada MainActivity 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 MainActivity.joinTeamsMeeting() método atualizado na etapa de inicialização.

Finalmente, adicionamos o método para consultar todas as mensagens acessíveis no thread, analisando-as por tipo de mensagem e exibindo as html e text uns:

    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 do tópico de bate-papo não são definidos pelo cliente do Teams. Os nomes são retornados como nulos na API para listar participantes, no participantsAdded evento e no participantsRemoved evento. Os nomes de exibição dos participantes do bate-papo podem ser recuperados do remoteParticipants campo do call objeto.

Obter um tópico de chat de 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 dos Serviços de Comunicação aceita um link de reunião completo do Teams ou uma ID de reunião. Eles são retornados como parte do onlineMeeting recurso, acessíveis sob a joinWebUrl propriedade

Com as APIs do Graph, você também pode obter o threadID. A resposta tem um chatInfo objeto que contém o threadID.

Executar o código

O aplicativo agora pode ser iniciado usando o botão "Executar aplicativo" na barra de ferramentas (Shift + F10).

Para participar da reunião e do bate-papo do Teams, insira o link da reunião da sua equipe e o ID do thread na interface do usuário.

Depois de participar na reunião da Equipa, tem de admitir o utilizador na reunião no cliente da sua Equipa. Uma vez que o usuário é admitido e entrou no chat, você é capaz de enviar e receber mensagens.

Captura de ecrã da Aplicação Android concluída.

Nota

No momento, determinados recursos não são suportados para cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos suportados, consulte Recursos de reunião do Teams para usuários externos do Teams

Neste guia de início rápido, você aprenderá a 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

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

Pré-requisitos

Participar no chat da reunião

Um usuário dos Serviços de Comunicação pode participar de uma reunião do Teams como um usuário anônimo usando o SDK de Chamada. Participar da reunião também os adiciona como participantes ao bate-papo da reunião, onde eles podem enviar e receber mensagens com outros usuários na reunião. O usuário não terá acesso às mensagens de bate-papo enviadas antes de entrar na reunião e não poderá enviar ou receber mensagens após o término da reunião. Para participar da reunião e começar a conversar, você pode seguir as próximas etapas.

Executar o código

Você pode criar e executar o código no Visual Studio. Observe as plataformas de solução que suportamos: 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 em 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, ou seja, x64
  • Certifique-se de que tem o 'Modo de Programador' no Windows 10 ativado (Definições do Programador)

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 válido para reunião de equipes na caixa 'Link de reunião de equipes' (veja a próxima seção)
  3. Pressione 'Participar da reunião do Teams' para começar a conversar.

Importante

Depois que o SDK de chamada estabelece a conexão com a reunião das equipes Consulte Serviços de Comunicação chamando o aplicativo do Windows, as principais funções para lidar com operações de bate-papo são: StartPollingForChatMessages e SendMessageButton_Click. Ambos os trechos de código estão em ChatTeamsInteropQuickStart\MainPage.xaml.cs

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

O link da reunião do Teams pode ser recuperado usando as APIs do Graph, detalhadas na documentação do Graph. Este link é retornado como parte do onlineMeeting recurso, acessível sob a joinWebUrl propriedade.

Você também pode obter o link de reunião necessário a partir do URL de Ingresso na Reunião no próprio convite de reunião do Teams. Um link de reunião do Teams tem esta aparência: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here. Se o link das equipes tiver um formato diferente disso, você precisará recuperar o ID do thread usando a API do Graph.

Captura de tela do aplicativo csharp concluído.

Nota

No momento, determinados recursos não são suportados para cenários de interoperabilidade com o Teams. Saiba mais sobre os recursos suportados, consulte Recursos de reunião do Teams para usuários externos do Teams

Clean up resources (Limpar recursos)

Se quiser limpar e remover uma assinatura dos Serviços de Comunicação, você pode excluir o recurso ou grupo de recursos. A exclusão do grupo de recursos também exclui quaisquer outros recursos associados a ele. Saiba mais sobre a limpeza de recursos.

Próximos passos

Para obter mais informações, consulte os seguintes artigos que podem estar em inglês: