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
- Una implementación de Teams.
- Una aplicación de chat activa.
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:
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
- Una cuenta de Azure con una suscripción activa. Cree su cuenta de forma gratuita.
- Mac con Xcode, junto con un certificado de desarrollador válido instalado en el Llavero.
- Una implementación de Teams.
- Un token de acceso de usuario para su instancia de Azure Communication Services. También puede usar la CLI de Azure y ejecutar el comando siguiente con la cadena de conexión para crear un usuario y un token de acceso.
az communication user-identity token issue --scope voip chat --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.
Instalación de CocoaPods
Utilice esta guía para instalar CocoaPods en su Mac.
Instalación del paquete y las dependencias con CocoaPods
Para crear un
Podfile
para la aplicación, abra el terminal, vaya a la carpeta del proyecto y ejecute el archivo init del pod.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
Ejecute
pod install
.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’
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
.
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.
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
- Una implementación de Teams.
- Una aplicación de llamada en funcionamiento.
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
yhtml
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.
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
- Una implementación de Teams.
- Una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Instale Visual Studio 2019 con la carga de trabajo de desarrollo de la Plataforma universal de Windows.
- Un recurso de Communication Services implementado. Cree un recurso de Communication Services.
- Un vínculo a la reunión de Teams.
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
.
- 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.
git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
- Abra el proyecto ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj en Visual Studio.
- 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
- 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.
- Pulse F5 para iniciar el proyecto en modo de depuración.
- 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).
- 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 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;
}
Obtención de un vínculo a la reunión de Teams
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.
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.
- Consulte nuestro ejemplo de elementos principales de un chat.
- Más información sobre el funcionamiento del chat.