Inicio rápido: Incorporación de una aplicación de chat a una reunión de Teams

Comience a usar Azure Communication Services mediante la conexión de la solución de chat a Microsoft Teams.

En este inicio rápido aprenderá a chatear en una reunión de Teams mediante el SDK de chat de Azure Communication Services para JavaScript.

Código de ejemplo

Busque el código finalizado de este inicio rápido en GitHub.

Requisitos previos

Unión al chat de la reunión

Un usuario de Communication Services puede unirse a una reunión de Teams como un usuario anónimo mediante el SDK de llamada. Al unirse a la reunión, se le agrega también como participante en el chat de la reunión, donde podrá enviar mensajes a otros usuarios durante la reunión, y recibirlos de ellos. El usuario no tendrá acceso a los mensajes de chat que se enviaron antes de unirse a la reunión y no podrá enviar ni recibir mensajes una vez que finalice la esta. Para unirse a la reunión e iniciar el chat, puede seguir los pasos siguientes.

Creación de una aplicación Node.js

Abra la ventana de comandos o el terminal, cree un nuevo directorio para la aplicación y navegue hasta él.

mkdir chat-interop-quickstart && cd chat-interop-quickstart

Ejecute npm init -y para crear un archivo package.json con la configuración predeterminada.

npm init -y

Instalación de los paquetes de chat

Use el comando npm install para instalar los SDK de Communication Services necesarios 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

La opción --save muestra la biblioteca como una dependencia en el archivo package.json.

Instalación del marco de la aplicación

Esta guía de inicio rápido usa webpack para agrupar los recursos de la aplicación. Ejecute el siguiente comando para instalar los paquetes de npm Webpack, webpack-cli y webpack-dev-server, y mostrarlos como dependencias de desarrollo en el archivo package.json:

npm install webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 --save-dev

Cree un archivo index.html en el directorio raíz del proyecto. Este archivo lo usaremos para configurar un diseño básico que permitirá al usuario unirse a una reunión y comenzar a chatear.

Incorporación de los controles de la interfaz de usuario de Teams

Reemplace el código de index.html por el siguiente fragmento de código. El cuadro de texto de la parte superior de la página se usará para escribir el contexto de la reunión de Teams. El botón "Unirse a una reunión de Teams" se usa para unirse a la reunión especificada. Aparece una ventana emergente de chat en la parte inferior de la página. Se puede usar para enviar mensajes en la conversación de la reunión, y los mensajes enviados se muestran en tiempo real en dicha conversación mientras el usuario de Communication Services sea miembro de esta.

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

Habilitación de los controles de la interfaz de usuario de Teams

Reemplace el contenido del archivo client.js por el siguiente fragmento de código.

En el fragmento de código, reemplace lo siguiente:

  • SECRET_CONNECTION_STRING por la cadena de conexión de Communication Services.
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}`);
});

El cliente de Teams no establece los nombres para mostrar de los participantes de la conversación del chat. Los nombres se devuelven como NULL en la API para enumerar participantes, en el evento participantsAdded y en el evento participantsRemoved. Los nombres para mostrar de los participantes del chat se pueden recuperar del campo remoteParticipants del objeto call. Al recibir una notificación sobre un cambio en la lista, puede usar este código para recuperar el nombre del usuario que se agregó o quitó:

var displayName = call.remoteParticipants.find(p => p.identifier.communicationUserId == '<REMOTE_USER_ID>').displayName;

Ejecución del código

Los usuarios de WebPack pueden usar webpack-dev-server para compilar y ejecutar la aplicación. Ejecute el siguiente comando para agrupar el host de aplicación en un servidor web local:

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

Abra el explorador web y vaya a http://localhost:8080/. Debería ver que la aplicación se inició como se muestra en la captura de pantalla siguiente:

Captura de pantalla de la aplicación JavaScript completada.

Inserte el vínculo de reunión de Teams en el cuadro de texto. Presione Unirse a una reunión de Teams para unirse a dicha reunión. Una vez que el usuario de Communication Services se haya admitido en la reunión, puede chatear desde dentro de la aplicación de Communication Services. Navegue hasta el cuadro que hay en la parte inferior de la página para iniciar el chat. Por simplicidad, la aplicación solo muestra los dos últimos mensajes en el chat.

Nota

Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Más información sobre las características admitidas, consulte Funcionalidades de reuniones de Teams para usuarios externos de Teams

En este inicio rápido aprenderá a chatear en una reunión de Teams mediante el SDK de chat de Azure Communication Services para iOS.

Código de ejemplo

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Requisitos previos

az communication identity token issue --scope voip --connection-string "yourConnectionString"

Para más información, consulte Uso de la CLI de Azure para crear y administrar tokens de acceso.

Instalación

Creación del proyecto de Xcode

En Xcode, cree un nuevo proyecto de iOS y seleccione la plantilla Aplicación de una vista. En este tutorial se usa el marco SwiftUI, por lo que debe establecer el lenguaje en Swift y la interfaz de usuario en SwiftUI. Durante este inicio rápido, no va a crear pruebas. No dude en desactivar Incluir pruebas.

Captura de pantalla que muestra la ventana Nuevo proyecto en Xcode.

Instalación de CocoaPods

Utilice esta guía para instalar CocoaPods en su Mac.

Instalación del paquete y las dependencias con CocoaPods

  1. Para crear un Podfile para la aplicación, abra el terminal, vaya a la carpeta del proyecto y ejecute el archivo init del pod.

  2. Agregue el siguiente código al Podfile en el destino y guarde los cambios.

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. Ejecute pod install.

  2. Abra el archivo .xcworkspace con Xcode.

Solicitud de acceso al micrófono

Para acceder al micrófono del dispositivo, debe actualizar la lista de propiedades de información de la aplicación con el elemento NSMicrophoneUsageDescription. Puede establecer el valor asociado al elemento string que se incluirá en el cuadro de diálogo que el sistema usa para solicitar acceso al usuario.

En el destino, seleccione la pestaña Info y agregue una cadena para ‘Privacidad: Descripción del uso del micrófono’

Captura de pantalla en la que se muestra cómo agregar el uso del micrófono en Xcode.

Deshabilitación del espacio aislado de script de usuario

Algunos de los scripts de las bibliotecas vinculadas escriben archivos durante el proceso de compilación. Para permitir esto, deshabilite el espacio aislado de script de usuario en Xcode. En la configuración de compilación, busque sandbox y establezca User Script Sandboxing en No.

Captura de pantalla que muestra la deshabilitación del espacio aislado de script de usuario en Xcode.

Unión al chat de la reunión

Un usuario de Communication Services puede unirse a una reunión de Teams como un usuario anónimo mediante el SDK de llamada. Una vez que un usuario se ha unido a la reunión de Teams, puede enviar y recibir mensajes con otros asistentes a la reunión. El usuario no tendrá acceso a los mensajes de chat enviados antes de unirse, ni podrá enviar ni recibir mensajes cuando no estén en la reunión. Para unirse a la reunión e iniciar el chat, puede seguir los pasos siguientes.

Instalación del marco de la aplicación

Importe los paquetes de Comunicación de Azure en ContentView.swift agregando el siguiente fragmento de código:

import AVFoundation
import SwiftUI

import AzureCommunicationCalling
import AzureCommunicationChat

En ContentView.swift, agregue el siguiente fragmento de código, justo encima de la declaración struct ContentView: View:

let endpoint = "<ADD_YOUR_ENDPOINT_URL_HERE>"
let token = "<ADD_YOUR_USER_TOKEN_HERE>"
let displayName: String = "Quickstart User"

Reemplace <ADD_YOUR_ENDPOINT_URL_HERE> por el punto de conexión del recurso de Communication Services. Reemplace <ADD_YOUR_USER_TOKEN_HERE> por el token generado anteriormente, a través de la línea de comandos del cliente de Azure. Más información sobre los tokens de acceso de usuario: Token de acceso de usuario

Reemplace Quickstart User por el nombre para mostrar que le gustaría usar en el chat.

Para contener el estado, agregue las siguientes variables a la estructura ContentView:

  @State var message: String = ""
  @State var meetingLink: String = ""
  @State var chatThreadId: String = ""

  // Calling state
  @State var callClient: CallClient?
  @State var callObserver: CallDelegate?
  @State var callAgent: CallAgent?
  @State var call: Call?

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

Ahora vamos a agregar la variable del cuerpo principal para contener los elementos de la interfaz de usuario. En este inicio rápido, asociaremos la lógica de negocios a estos controles. Agregue el siguiente código a la estructura ContentView:

var body: some View {
    NavigationView {
      Form {
        Section {
          TextField("Teams Meeting URL", text: $meetingLink)
            .onChange(of: self.meetingLink, perform: { value in
              if let threadIdFromMeetingLink = getThreadId(from: value) {
                self.chatThreadId = threadIdFromMeetingLink
              }
            })
          TextField("Chat thread ID", text: $chatThreadId)
        }
        Section {
          HStack {
            Button(action: joinMeeting) {
              Text("Join Meeting")
            }.disabled(
              chatThreadId.isEmpty || callAgent == nil || call != nil
            )
            Spacer()
            Button(action: leaveMeeting) {
              Text("Leave Meeting")
            }.disabled(call == nil)
          }
          Text(message)
        }
        Section {
          ForEach(meetingMessages, id: \.id) { message in
            let currentUser: Bool = (message.displayName == displayName)
            let foregroundColor = currentUser ? Color.white : Color.black
            let background = currentUser ? Color.blue : Color(.systemGray6)
            let alignment = currentUser ? HorizontalAlignment.trailing : .leading
            
            HStack {
              if currentUser {
                Spacer()
              }
              VStack(alignment: alignment) {
                Text(message.displayName).font(Font.system(size: 10))
                Text(message.content)
                  .frame(maxWidth: 200)
              }

              .padding(8)
              .foregroundColor(foregroundColor)
              .background(background)
              .cornerRadius(8)

              if !currentUser {
                Spacer()
              }
            }
          }
          .frame(maxWidth: .infinity)
        }

        TextField("Enter your message...", text: $chatMessage)
        Button(action: sendMessage) {
          Text("Send Message")
        }.disabled(chatThreadClient == nil)
      }

      .navigationBarTitle("Teams Chat Interop")
    }

    .onAppear {
      // Handle initialization of the call and chat clients
    }
  }

Inicialización de ChatClient

Cree una instancia ChatClient y habilite las notificaciones de mensajes. Estamos usando notificaciones en tiempo real para recibir los mensajes del chat.

Con la configuración principal del cuerpo, vamos a agregar las funciones para controlar la configuración de los clientes de llamadas y chat.

En la función onAppear, agregue el siguiente código para inicializar el CallClient y el 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
  }

Adición de la función de unión a reuniones

Agregue la siguiente función a la estructura ContentView para controlar la unión a la reunión.

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

Inicialización de ChatThreadClient

Inicializaremos ChatThreadClient una vez que el usuario se haya unido a la reunión. Esto requiere que comprobemos el estado de la reunión del delegado y, a continuación, inicializar ChatThreadClient con threadId cuando se una a la reunión.

Cree la función connectChat() con el siguiente 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
    }
  }

Agregue la siguiente función auxiliar a ContentView, que se usa para analizar el identificador del subproceso de chat desde el vínculo de reunión del equipo, si es posible. En caso de que se produzca un error en esta extracción, el usuario deberá escribir manualmente el identificador de subproceso de chat mediante Graph API para recuperar el identificador de subproceso.

 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
}

Habilitación del envío de mensajes

Agregue la función sendMessage() a ContentView. Esta función usa ChatThreadClient para enviar mensajes del usuario.

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

Habilitación de la recepción de mensajes

Para recibir mensajes, implementaremos el controlador para eventos ChatMessageReceived. Cuando se envíen nuevos mensajes al subproceso, este controlador agregará los mensajes a la variable meetingMessages para que se puedan mostrar en la interfaz de usuario.

En primer lugar, agregue la siguiente estructura a ContentView.swift. La interfaz de usuario usa los datos de la estructura para mostrar los mensajes del chat.

struct MeetingMessage: Identifiable {
  let id: String
  let date: Date
  let content: String
  let displayName: String

  static func fromTrouter(event: ChatMessageReceivedEvent) -> MeetingMessage {
    let displayName: String = event.senderDisplayName ?? "Unknown User"
    let content: String = event.message.replacingOccurrences(
      of: "<[^>]+>", with: "",
      options: String.CompareOptions.regularExpression
    )
    return MeetingMessage(
      id: event.id,
      date: event.createdOn?.value ?? Date(),
      content: content,
      displayName: displayName
    )
  }
}

A continuación, agregue la función receiveMessage() a ContentView. Se llama cuando se produce un evento de mensajería. Tenga en cuenta que es necesario registrarse para todos los eventos que desea controlar en la instrucción switch a través del método chatClient?.register().

  func receiveMessage(event: TrouterEvent) -> Void {
    switch event {
    case let .chatMessageReceivedEvent(messageEvent):
      let message = MeetingMessage.fromTrouter(event: messageEvent)
      self.meetingMessages.append(message)

      /// OTHER EVENTS
      //    case .realTimeNotificationConnected:
      //    case .realTimeNotificationDisconnected:
      //    case .typingIndicatorReceived(_):
      //    case .readReceiptReceived(_):
      //    case .chatMessageEdited(_):
      //    case .chatMessageDeleted(_):
      //    case .chatThreadCreated(_):
      //    case .chatThreadPropertiesUpdated(_):
      //    case .chatThreadDeleted(_):
      //    case .participantsAdded(_):
      //    case .participantsRemoved(_):

    default:
      break
    }
  }

Por último, es necesario implementar el controlador de delegado para el cliente de llamada. Este controlador se usa para comprobar el estado de la llamada e inicializar el cliente de chat cuando el usuario se una a la reunión.

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

Abandono del chat

Cuando el usuario abandone la reunión de Teams, borraremos los mensajes de chat de la interfaz de usuario y colgaremos la llamada. El código completo se muestra a continuación.

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

Obtención de un subproceso del chat de la reunión para un usuario de Communication Services

Los detalles de la reunión de Teams se pueden recuperar mediante las instancias de Graph API, que se detallan en la documentación de Graph. El SDK de llamada de Communication Services acepta un vínculo completo a la reunión de Teams o un identificador de reunión. Ambos elementos se devuelven como parte del recurso onlineMeeting, al que se puede acceder bajo la propiedad joinWebUrl

Con Graph API, también se puede obtener threadID. La respuesta tiene un objeto chatInfo que contiene el threadID.

Ejecución del código

Ejecute la aplicación.

Para unirse a la reunión de Teams, escriba el vínculo de la reunión de Teams en la interfaz de usuario.

Después de unirse a la reunión de Teams, debe admitir al usuario a la reunión en el cliente de Teams. Una vez que el usuario se haya admitido y se haya unido al chat, puede enviar y recibir mensajes.

Captura de pantalla de la aplicación iOS completada.

Nota

Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Más información sobre las características admitidas, consulte Funcionalidades de reuniones de Teams para usuarios externos de Teams

En este artículo de inicio rápido aprenderá a chatear en una reunión de Teams mediante Chat SDK de Azure Communication Services para Android.

Código de ejemplo

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Requisitos previos

Habilitación de la interoperabilidad de Teams

Cualquier usuario de Communication Services que se una a una reunión de Teams como invitado solo puede acceder al chat cuando se haya unido a la llamada de la reunión de Teams. Consulte la documentación de la interoperabilidad de Teams para aprender a agregar un usuario de Communication Services a una llamada de reunión de Teams.

Para usar esta característica debe ser miembro de la organización propietaria de ambas entidades.

Unión al chat de la reunión

Una vez que la interoperabilidad de Teams se habilita, cualquier usuario de Communication Services puede unirse a la llamada de Teams como usuario externo mediante el SDK de llamadas. Al unirse a la llamada se le agrega también como participante en el chat de la reunión, donde podrán enviar mensajes a otros usuarios durante la llamada, y recibirlos de ellos. El usuario no tiene acceso a los mensajes del chat enviados antes de que se haya unido a la llamada. Para unirse a la reunión e iniciar el chat, puede seguir los pasos siguientes.

Incorporación de chat a la aplicación de llamada de Teams

En el nivel del módulo build.gradle, agregue la dependencia en el SDK del chat.

Importante

Problema conocido: Al usar Chat y Calling SDK de Android juntos en la misma aplicación, la característica de notificaciones en tiempo real de Chat SDK no funcionará. Recibirá un problema de resolución de dependencias. Mientras trabajamos en una solución, puede desactivar la característica de notificaciones en tiempo real agregando las siguientes exclusiones a la dependencia de Chat SDK en el archivo build.gradle de la aplicación:

implementation ("com.azure.android:azure-communication-chat:2.0.3") {
    exclude group: 'com.microsoft', module: 'trouter-client-android'
}

Incorporación del diseño de la interfaz de usuario de Teams

Reemplace el código de activity_main.xml por el siguiente fragmento de código. Agrega entradas para el identificador de subproceso y para enviar mensajes, un botón para enviar el mensaje con tipo y un diseño 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>

Habilitación de los controles de la interfaz de usuario de Teams

Importación de paquetes y definición de variables de estado

Agregue las siguientes importaciones al contenido de MainActivity.java:

import android.graphics.Typeface;
import android.graphics.Color;
import android.text.Html;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import 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;

Agregue las siguientes variables a la clase MainActivity:

    // InitiatorId is used to differentiate incoming messages from outgoing messages
    private static final String InitiatorId = "<USER_ID>";
    private static final String ResourceUrl = "<COMMUNICATION_SERVICES_RESOURCE_ENDPOINT>";
    private String threadId;
    private ChatThreadAsyncClient chatThreadAsyncClient;
    
    // The list of ids corresponsding to messages which have already been processed
    ArrayList<String> chatMessages = new ArrayList<>();

Reemplace <USER_ID> por el identificador del usuario que inicia el chat. Reemplace <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT> por el punto de conexión del recurso de Communication Services.

Inicialización de ChatThreadClient

Después de unirse a la reunión, cree una instancia de ChatThreadClient y haga visibles los componentes de chat.

Actualice el final del método MainActivity.joinTeamsMeeting() con el código siguiente:

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

Habilitación del envío de mensajes

Agregue el método sendMessage() a MainActivity. Usa ChatThreadClient para enviar mensajes en nombre del usuario.

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

Habilitación del sondeo para mensajes y su representación en la aplicación

Importante

Problema conocido: Dado que la característica de notificaciones en tiempo real de Chat SDK no funciona junto con Calling SDK, tendremos que sondear la API GetMessages a intervalos predefinidos. En este ejemplo, usaremos intervalos de 3 segundos.

Podemos obtener los siguientes datos de la lista de mensajes devueltos por la API GetMessages:

  • Los mensajes text y html en el subproceso desde la unión
  • Cambios en la lista de subprocesos
  • Actualizaciones en el tema de subprocesos

En la clase MainActivity, agregue un controlador y una tarea ejecutable que se ejecutará a 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);
        }
    };

Tenga en cuenta que la tarea ya se ha iniciado al final del método MainActivity.joinTeamsMeeting() actualizado en el paso de inicialización.

Por último, agregaremos el método para consultar todos los mensajes accesibles en el subproceso, analizarlos por tipo de mensaje y mostrar los html y 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);
        }
    }

El cliente de Teams no establece los nombres para mostrar de los participantes de la conversación del chat. Los nombres se devuelven como NULL en la API para enumerar participantes, en el evento participantsAdded y en el evento participantsRemoved. Los nombres para mostrar de los participantes del chat se pueden recuperar del campo remoteParticipants del objeto call.

Obtención de un subproceso del chat de la reunión para un usuario de Communication Services

Los detalles de la reunión de Teams se pueden recuperar mediante las instancias de Graph API, que se detallan en la documentación de Graph. El SDK de llamada de Communication Services acepta un vínculo completo a la reunión de Teams o un identificador de reunión. Ambos elementos se devuelven como parte del recurso onlineMeeting, al que se puede acceder bajo la propiedad joinWebUrl

Con Graph API, también se puede obtener threadID. La respuesta tiene un objeto chatInfo que contiene el threadID.

Ejecución del código

Ahora se puede iniciar la aplicación con el botón "Ejecutar aplicación" de la barra de herramientas (Mayús + F10).

Para unirse a la reunión y el chat de Teams, escriba el vínculo de la reunión de Teams y el identificador del subproceso en la interfaz de usuario.

Después de unirse a la reunión de Teams, deberá admitir al usuario a la reunión en el cliente de Teams. Una vez que el usuario se haya admitido y se haya unido al chat, puede enviar y recibir mensajes.

Captura de pantalla de la aplicación Android completada.

Nota

Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Más información sobre las características admitidas, consulte Funcionalidades de reuniones de Teams para usuarios externos de Teams

En este inicio rápido, obtendrá información sobre cómo chatear en una reunión de Teams mediante el SDK de Azure Communication Services Chat para C#.

Código de ejemplo

Busque el código de este inicio rápido en GitHub.

Requisitos previos

Unión al chat de la reunión

Un usuario de Communication Services puede unirse a una reunión de Teams como un usuario anónimo mediante el SDK de llamada. Al unirse a la reunión, se le agrega también como participante en el chat de la reunión, donde podrá enviar mensajes a otros usuarios durante la reunión, y recibirlos de ellos. El usuario no tendrá acceso a los mensajes de chat que se enviaron antes de unirse a la reunión y no podrá enviar ni recibir mensajes una vez que esta finalice. Para unirse a la reunión e iniciar el chat, puede seguir los pasos siguientes.

Ejecución del código

Puede compilar y ejecutar el código en Visual Studio. Tenga en cuenta las plataformas de la solución que se admiten: x64, x86 y ARM64.

  1. Abra una instancia de PowerShell, terminal de Windows, símbolo del sistema o equivalente y navegue hasta el directorio donde le gustaría clonar el ejemplo.
  2. git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
  3. Abra el proyecto ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj en Visual Studio.
  4. Instale las siguientes versiones de paquetes NuGet (o versiones posteriores):
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. Con el recurso de Communication Services proporcionado según los requisitos previos, agregue la cadena de conexión al archivo 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

  • Seleccione la plataforma adecuada en la lista desplegable "Plataformas de solución" de Visual Studio antes de ejecutar el código, es decir, x64.
  • Asegúrese de que tiene el "Modo de desarrollador" de Windows 10 habilitado (Configuración del desarrollador).

Los pasos siguientes no funcionarán si esto no está configurado correctamente.

  1. Pulse F5 para iniciar el proyecto en modo de depuración.
  2. Pegue un vínculo de reunión de Teams válido en el cuadro "Teams Meeting Link" (Vínculo a la reunión de Teams) (consulte la sección siguiente).
  3. Pulse en "Unirse a la reunión de Teams" para empezar a chatear.

Importante

Una vez que el SDK que realiza la llamada establece la conexión con la reunión de Teams, consulte en Aplicación de Windows que llama a Communication Services las funciones principales para controlar las operaciones de chat, que son: StartPollingForChatMessages y SendMessageButton_Click. Ambos fragmentos de código están en 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;
        }

El vínculo a la reunión de Teams se puede recuperar mediante Graph API, como se detalla en la documentación de Graph. Este vínculo se devuelve como parte del recurso onlineMeeting, al que se puede acceder bajo la propiedad joinWebUrl.

También puede obtener el vínculo de la reunión necesario en la dirección URL Unirse a la reunión de la propia invitación a la reunión de Teams. Así es el vínculo de una reunión en Teams: https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here. Si el vínculo de los equipos tiene un formato diferente a este, debe recuperar el identificador de subproceso mediante Graph API.

Captura de pantalla de la aplicación csharp completada.

Nota

Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Más información sobre las características admitidas, consulte Funcionalidades de reuniones de Teams para usuarios externos de Teams

Limpieza de recursos

Si quiere limpiar y quitar una suscripción a Communication Services, puede eliminar el recurso o grupo de recursos. Al eliminar el grupo de recursos, también se elimina cualquier otro recurso que esté asociado a él. Obtenga más información sobre la limpieza de recursos.

Pasos siguientes

Para más información, consulte los siguientes artículos.