Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Comience a usar Azure Communication Services mediante la conexión de la solución de chat a Microsoft Teams.
En este artículo se describe cómo chatear en una reunión de Teams mediante el SDK de chat de Azure Communication Services para JavaScript.
Código de ejemplo
Descargue este código en GitHub Azure Samples Join your chat app to a Teams meeting (Unirse a la aplicación de chat en GitHub Azure Samples) a una reunión de Teams.
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 a través del SDK de llamadas. Unirse a la reunión también los agrega como participante al chat de la reunión, donde pueden enviar y recibir mensajes con otros usuarios de la reunión. El usuario no tiene acceso a los mensajes de chat enviados antes de unirse a la reunión y no puede enviar ni recibir mensajes después de que finalice la reunión. 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 dependencia en el archivo package.json.
Instalación del marco de la aplicación
En este ejemplo se usa webpack para agrupar los recursos de la aplicación. Ejecute el siguiente comando para instalar los paquetes webpack, webpack-cli y webpack-dev-server npm y enumerarlos como dependencias de desarrollo en el 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. Usamos este archivo para configurar un diseño básico que permite al usuario unirse a una reunión y empezar 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.
Utiliza el cuadro de texto en la parte superior de la página para escribir el contexto de la reunión de Teams. Los usuarios finales pueden unirse a la reunión especificada haciendo clic en el botón Unirse a la reunión de Teams.
Aparece una ventana emergente de chat en la parte inferior de la página. Los usuarios finales pueden usarlo para enviar mensajes en el subproceso de reunión. Muestra en tiempo real cualquier mensaje enviado en el hilo mientras el usuario de Communication Services sea miembro.
<!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_STRINGpor 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 de visualización de los participantes del hilo de 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
Utilice 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. Los usuarios pueden hacer clic en Unirse a la reunión de Teams para unirse a la reunión de Teams. Una vez que el usuario de Communication Services se admita en la reunión, puede chatear desde la aplicación 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. Para obtener más información sobre las características admitidas, consulte Funcionalidades de reunión de Teams para usuarios externos de Teams.
En este artículo se describe cómo chatear en una reunión de Teams mediante el SDK de chat de Azure Communication Services para iOS.
Código de ejemplo
Descargue este código en GitHub Azure Samples Join your chat app to a Teams meeting (Unirse a la aplicación de chat en GitHub Azure Samples) a una reunión de Teams.
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
Podfilepara 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
Podfileen 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
.xcworkspacecon Xcode.
Solicitud de acceso al micrófono
Para acceder al micrófono del dispositivo, debes actualizar la lista de propiedades de información de la aplicación con un 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 objetivo, seleccione la pestaña Info y agregue una cadena de texto para Privacy - Microphone Usage Description.
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 habilitar la escritura de archivos, 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 une a la reunión de Teams, puede enviar y recibir mensajes con otros asistentes a la reunión. El usuario no tiene acceso a los mensajes de chat enviados antes de unirse, ni puede enviar ni recibir mensajes cuando no están en la reunión.
Para unirse a la reunión e iniciar el chat, siga 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.
Para obtener más información, consulte 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 agregamos la variable del cuerpo principal para contener los elementos de la interfaz de usuario. Adjuntamos 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
Inicializamos ChatThreadClient después de que el usuario se una a la reunión. A continuación, es necesario comprobar el estado de la reunión del delegado y, a continuación, inicializar ChatThreadClient con el threadId cuando se une 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 debe 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. Cuando se produce un evento de mensajería, llama a esta función. 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. Use este controlador 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 abandona la reunión de Teams, borramos los mensajes de chat de la interfaz de usuario y colgamos la llamada. Consulte el siguiente ejemplo de código completo.
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 admita y se una al chat, puede enviar y recibir mensajes.
Nota:
Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Para obtener más información sobre las características admitidas, consulte Funcionalidades de reunión de Teams para usuarios externos de Teams.
En este artículo se describe cómo agregar el chat de reunión de Teams a la aplicación mediante el SDK de chat de Azure Communication Services para Android.
Código de ejemplo
Descargue este código en GitHub Azure Samples Join your chat app to a Teams meeting (Unirse a la aplicación de chat en GitHub Azure Samples) a una reunión de Teams.
Requisitos previos
- Una implementación de Teams.
- Una aplicación de llamada en funcionamiento.
Habilitación de la interoperabilidad de Teams
Un usuario de Communication Services que se une a una reunión de Teams como usuario invitado solo puede acceder al chat de reunión después de unirse a la llamada a la reunión de Teams. Para obtener más información sobre cómo agregar un usuario de Communication Services a una llamada de reunión de Teams, consulte Interoperabilidad 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 habilite la interoperabilidad de Teams, un usuario de Communication Services puede unirse a la llamada de Teams como usuario externo mediante el SDK de llamadas. Al unirse a la llamada, también se les añade como participantes al chat de la reunión. Desde el chat, pueden enviar y recibir mensajes con otros usuarios en la llamada. El usuario no tiene acceso a los mensajes del chat enviados antes de que se haya unido a la llamada. Para permitir que los usuarios finales se unan a las reuniones de Teams y empiecen a chatear, complete 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: cuando se usa Android Chat y Calling SDK juntos en la misma aplicación, la característica de notificaciones en tiempo real del SDK de chat no funciona. Se genera 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 corresponding 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 MainActivity.joinTeamsMeeting() método 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 del SDK de chat no funciona junto con los SDK de llamada, debe sondear la GetMessages API a intervalos predefinidos. En este ejemplo, usamos intervalos de 3 segundos.
Podemos obtener los siguientes datos de la lista de mensajes devueltos por la API GetMessages:
- Los mensajes
textyhtmlen el subproceso desde la unión - Cambios en la lista de subprocesos
- Actualizaciones en el tema de subprocesos
Para la MainActivity clase , agregue un controlador y una tarea ejecutable que se ejecute 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);
}
};
La tarea ya se inició 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 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 de visualización de los participantes del hilo de 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 puede iniciar la aplicación desde 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 admita y se una al chat, puede enviar y recibir mensajes.
Nota:
Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Para obtener más información sobre las características admitidas, consulte Funcionalidades de reunión de Teams para usuarios externos de Teams.
En este artículo se describe cómo chatear en una reunión de Teams mediante el SDK de chat de Azure Communication Services para C#.
Código de ejemplo
Descargue este código en GitHub Azure Samples Join your chat app to a Teams meeting (Unirse a la aplicación de chat en GitHub Azure Samples) a una reunión de Teams.
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 de forma anónima mediante el SDK de llamadas. Unirse a la reunión también los agrega al chat de la reunión como participantes. En el chat, pueden enviar y recibir mensajes con otros usuarios de la reunión. El usuario no tiene acceso a los mensajes de chat que se enviaron antes de unirse a la reunión. No pueden enviar ni recibir mensajes después de que finalice la reunión. Para permitir que los usuarios se unan a las reuniones de Teams e inicien el chat, complete los pasos siguientes.
Ejecución del código
Puede compilar y ejecutar el código en Visual Studio. Las plataformas de solución admitidas son: 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.gitAbra el proyecto
ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csprojen 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.1Con 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, como
x64 - Asegúrese de habilitar el modo de desarrollador en Windows (Configuración del desarrollador)
Los pasos siguientes no funcionan si la plataforma no está configurada correctamente.
- Seleccione la plataforma adecuada en la lista desplegable Plataformas de solución de Visual Studio antes de ejecutar el código, como
Presione F5 para iniciar el proyecto en modo de depuración.
Pegue un vínculo de reunión de Teams válido en el cuadro Vínculo a la reunión de Teams (consulte la sección siguiente).
Los usuarios finales hacen clic en Unirse a la reunión de Teams para empezar a chatear.
Importante
Una vez que el SDK que realiza la llamada establece una 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 el ChatTeamsInteropQuickStart\MainPage.xaml.cs archivo
/// <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
Recupere el vínculo de reunión de Teams mediante las API de Graph, como se describe 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.
Un vínculo de reunión de Teams es similar a la siguiente dirección URL:
https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here`.
Si el vínculo de Teams tiene un formato diferente, debe recuperar el identificador del hilo mediante la Graph API.
Nota:
Actualmente, algunas características no se admiten para escenarios de interoperabilidad con Teams. Para obtener más información sobre las características admitidas, consulte Funcionalidades de reunión 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.
Artículos relacionados
- Consulte nuestro ejemplo de elementos principales de un chat.
- Más información sobre el funcionamiento del chat.