다음을 통해 공유


빠른 시작: 채팅 앱으로 Teams 미팅에 참가

채팅 솔루션을 Microsoft Teams에 연결하여 Azure Communication Services를 시작하세요.

이 빠른 시작에서는 JavaScript용 Azure Communication Services 채팅 SDK를 사용하여 Teams 모임에서 채팅하는 방법을 알아봅니다.

예제 코드

GitHub에서 이 빠른 시작에 대한 최종 코드를 찾습니다.

필수 조건

모임 채팅 참가

Communication Services 사용자는 호출 SDK를 사용하여 익명 사용자로 Teams 모임에 참가할 수 있습니다. 모임에 참가하면 모임 채팅에도 참가자로 추가됩니다. 그러면 모임의 다른 사용자와 메시지를 보내고 받을 수 있습니다. 사용자는 모임에 참가하기 전에 전송된 채팅 메시지에 액세스할 수 없으며 모임이 끝난 후에는 메시지를 보내거나 받을 수 없습니다. 모임에 참가하고 채팅을 시작하려면 다음 단계를 수행하면 됩니다.

새 Node.js 애플리케이션 만들기

터미널 또는 명령 창을 열어 앱에 대한 새 디렉터리를 만들고 해당 디렉터리로 이동합니다.

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

npm init -y를 실행하여 기본 설정으로 package.json 파일을 만듭니다.

npm init -y

채팅 패키지 설치

npm install 명령을 사용하여 필수 JavaScript용 Communication Services SDK를 설치합니다.

npm install @azure/communication-common --save

npm install @azure/communication-identity --save

npm install @azure/communication-chat --save

npm install @azure/communication-calling --save

--save 옵션은 라이브러리를 package.json 파일의 종속성으로 나열합니다.

앱 프레임워크 설정

이 빠른 시작에서는 webpack을 사용하여 애플리케이션 자산을 번들로 묶습니다. 다음 명령을 실행하여 Webpack, webpack-cli 및 webpack-dev-server npm 패키지를 설치하고 package.json에서 개발 종속성으로 나열합니다.

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

프로젝트의 루트 디렉터리에 index.html 파일을 만듭니다. 이 파일을 사용하여 사용자가 회의에 참석하고 채팅을 시작할 수 있는 기본 레이아웃을 구성합니다.

Teams UI 컨트롤 추가

index.html의 코드를 다음 코드 조각으로 바꿉니다. 페이지 상단의 텍스트 상자는 Teams 모임 컨텍스트를 입력하는 데 사용됩니다. 'Teams 모임 참가' 단추는 지정된 모임에 참가하는 데 사용됩니다. 페이지 아래쪽에 채팅 팝업이 표시됩니다. 이를 사용하여 모임 스레드에서 메시지를 보낼 수 있으며 Communication Services 사용자가 구성원인 동안 스레드에서 보낸 메시지를 실시간으로 표시합니다.

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

Teams UI 컨트롤 사용

client.js 파일의 내용을 다음 코드 조각으로 바꿉니다.

코드 조각 내에서 다음과 같이 바꿉니다.

  • SECRET_CONNECTION_STRING을 Communication Service의 연결 문자열로
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}`);
});

채팅 스레드 참가자의 표시 이름은 Teams 클라이언트에서 설정하지 않습니다. 이름이 참가자 나열을 위한 API, participantsAdded 이벤트 및 participantsRemoved 이벤트에서 null로 반환됩니다. 채팅 참가자의 표시 이름은 call 개체의 remoteParticipants 필드에서 검색할 수 있습니다. 명단 변경에 대한 알림을 받으면 이 코드를 사용하여 추가되거나 제거된 사용자의 이름을 검색할 수 있습니다.

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

코드 실행

webpack 사용자는 webpack-dev-server를 사용하여 앱을 빌드하고 실행할 수 있습니다. 다음 명령을 실행하여 로컬 웹 서버에서 애플리케이션 호스트를 번들로 묶습니다.

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

브라우저를 열고 http://localhost:8080/로 이동합니다. 다음 스크린샷과 같이 앱이 시작된 것을 볼 수 있습니다.

완성된 JavaScript 애플리케이션의 스크린샷.

Teams 모임 링크를 텍스트 상자에 삽입합니다. Teams 모임에 참가하려면 팀 모임 참가를 누릅니다. Communication Services 사용자가 모임에 입장한 후 Communication Services 애플리케이션에서 채팅할 수 있습니다. 페이지 맨 아래에 있는 상자로 이동하여 채팅을 시작합니다. 간단히 하기 위해 애플리케이션은 채팅의 마지막 두 메시지만 표시합니다.

참고 항목

특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.

이 빠른 시작에서는 iOS용 Azure Communication Services 채팅 SDK를 사용하여 Teams 모임에서 채팅하는 방법을 알아봅니다.

예제 코드

끝으로 건너뛰려면 GitHub에서 이 빠른 시작을 샘플로 다운로드할 수 있습니다.

필수 조건

  • 활성 구독이 있는 Azure 계정. 무료로 계정 만들기
  • 키 집합에 설치된 유효한 개발자 인증서와 함께 Xcode를 실행하는 Mac
  • Teams 배포
  • 사용자 액세스 토큰(Azure Communication Service용) 또한 Azure CLI를 사용하고 연결 문자열과 함께 명령을 실행하여 사용자 및 액세스 토큰을 만들 수 있습니다.
az communication user-identity token issue --scope voip chat --connection-string "yourConnectionString"

자세한 내용은 Azure CLI를 사용하여 액세스 토큰 만들기 및 관리를 참조하세요.

설정

Xcode 프로젝트 만들기

Xcode에서 새 iOS 프로젝트를 만들고 단일 보기 앱 템플릿을 선택합니다. 이 자습서에서는 SwiftUI 프레임워크를 사용하므로 언어를 Swift로, 사용자 인터페이스는 SwiftUI로 설정해야 합니다. 이 빠른 시작 중에는 테스트를 만들지 않습니다. 테스트 포함을 선택 취소합니다.

Xcode 내에서 새 프로젝트 창을 보여 주는 스크린샷

CocoaPods 설치

이 가이드를 사용하여 Mac에 CocoaPods를 설치합니다.

CocoaPods를 사용하여 패키지 및 종속성 설치

  1. 애플리케이션에 대한 Podfile을 만들려면 터미널을 열고 프로젝트 폴더로 이동한 후 pod init을 실행합니다.

  2. 대상 아래의 Podfile에 다음 코드를 추가하고 저장합니다.

target 'Chat Teams Interop' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Chat Teams Interop
  pod 'AzureCommunicationCalling'
  pod 'AzureCommunicationChat'
  
end
  1. pod install를 실행합니다.

  2. Xcode로 .xcworkspace 파일을 엽니다.

마이크에 대한 액세스 요청

디바이스의 마이크에 액세스하려면 앱의 정보 속성 목록을 NSMicrophoneUsageDescription으로 업데이트해야 합니다. 연결된 값을 시스템이 사용자에게 액세스를 요청하는 데 사용하는 대화 상자에 포함된 string으로 설정합니다.

대상에서 Info 탭을 선택하고 ‘개인 정보 - 마이크 사용 설명’에 문자열을 추가합니다.

Xcode 내에서 마이크 사용량 추가를 보여 주는 스크린샷.

사용자 스크립트 샌드박싱 사용 안 함

연결된 라이브러리 내의 일부 스크립트는 빌드 프로세스 중에 파일을 씁니다. 이를 허용하려면 Xcode에서 사용자 스크립트 샌드박싱을 사용하지 않도록 설정합니다. 빌드 설정에서 sandbox을(를) 검색하고 User Script Sandboxing을(를) No(으)로 설정합니다.

Xcode 내에서 사용자 스크립트 샌드박싱을 사용하지 않도록 설정하는 스크린샷.

모임 채팅 참가

Communication Services 사용자는 호출 SDK를 사용하여 익명 사용자로 Teams 모임에 참가할 수 있습니다. 사용자가 Teams 모임에 참가하면 다른 모임 참석자와 메시지를 보내고 받을 수 있습니다. 사용자는 참가하기 전에 보낸 채팅 메시지에 액세스할 수 없으며 모임에 없을 때 메시지를 보내거나 받을 수 없습니다. 모임에 참가하고 채팅을 시작하려면 다음 단계를 수행하면 됩니다.

앱 프레임워크 설정

다음 조각을 추가하여 ContentView.swift에서 Azure Communication 패키지를 가져옵니다.

import AVFoundation
import SwiftUI

import AzureCommunicationCalling
import AzureCommunicationChat

ContentView.swift에서 struct ContentView: View 선언 바로 위에 다음 코드 조각을 추가합니다.

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

<ADD_YOUR_ENDPOINT_URL_HERE>를 Communication Services 리소스의 엔드포인트로 바꿉니다. Azure 클라이언트 명령줄을 통해 <ADD_YOUR_USER_TOKEN_HERE> 위에서 생성된 토큰으로 바꿉니다. 사용자 액세스 토큰에 대해 자세히 알아보기: 사용자 액세스 토큰

Quickstart User를 채팅에서 사용할 표시 이름으로 바꿉니다.

상태를 유지하려면 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] = []

이제 UI 요소를 저장할 본문 var을 추가해 보겠습니다. 이 빠른 시작에서는 비즈니스 논리를 이러한 컨트롤에 연결합니다. 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
    }
  }

ChatClient 초기화

ChatClient를 인스턴스화하고 메시지 알림을 사용하도록 설정합니다. 실시간 알림은 채팅 메시지를 수신하는 데 사용합니다.

본문을 설정하여 통화 및 채팅 클라이언트의 설정을 처리하는 함수를 추가해 보겠습니다.

onAppear 함수에서 다음 코드를 추가하여 CallClientChatClient을(를) 초기화하세요.

  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
  }

모임 참가 함수 추가

모임 참가를 처리하기 위해 ContentView 구조체에 다음 함수를 추가합니다.

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

ChatThreadClient 초기화

사용자가 모임에 참가한 후 ChatThreadClient을(를) 초기화합니다. 이렇게 하려면 대리인의 모임 상태를 확인한 다음 모임에 참가할 때 threadId을(를) 사용하여 ChatThreadClient을(를) 초기화해야 합니다.

다음 코드를 사용하여 connectChat() 함수를 만듭니다.

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

가능한 경우 팀의 모임 링크에서 채팅 스레드 ID를 구문 분석하는 데 사용되는 ContentView에 다음 도우미 함수를 추가합니다. 이 추출이 실패하는 경우 사용자는 Graph API를 사용하여 스레드 ID를 검색하기 위해 채팅 스레드 ID를 수동으로 입력해야 합니다.

 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
}

메시지 보내기 사용

sendMessage() 함수를 ContentView에 추가합니다. 이 함수는 ChatThreadClient를 사용하여 사용자의 메시지를 보냅니다.

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

메시지 받기 사용

메시지를 받기 위해 ChatMessageReceived 이벤트에 대한 처리기를 구현합니다. 새 메시지가 스레드로 전송되면 이 처리기는 해당 메시지를 meetingMessages 변수에 추가하여 UI에 표시될 수 있도록 합니다.

먼저 다음 구조체를 ContentView.swift에 추가합니다. UI는 구조체의 데이터를 사용하여 채팅 메시지를 표시합니다.

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

다음으로 receiveMessage() 함수를 ContentView에 추가합니다. 이 호출은 메시징 이벤트가 발생할 때 호출됩니다. chatClient?.register() 메서드를 통해 switch 문에서 처리하려는 모든 이벤트에 등록해야 합니다.

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

마지막으로 호출 클라이언트에 대한 대리자 처리기를 구현해야 합니다. 이 처리기는 사용자가 모임에 참가할 때 통화 상태를 확인하고 채팅 클라이언트를 초기화하는 데 사용됩니다.

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

채팅 나가기

사용자가 팀 모임을 떠날 때 UI에서 채팅 메시지를 지우고 전화를 끊습니다. 전체 코드는 다음과 같습니다.

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

Communication Services 사용자를 위한 Teams 미팅 채팅 스레드 가져오기

Teams 모임 링크 및 채팅은 그래프 설명서에 자세히 설명된 Graph API를 사용하여 검색할 수 있습니다. Communication Services Calling SDK는 전체 Teams 모임 링크를 수락합니다. 이 링크는 joinWebUrl 속성에서 액세스할 수 있는 onlineMeeting 리소스의 일부로 반환됩니다.

Graph API를 사용하면 threadID도 가져올 수 있습니다. 응답에는 threadID를 포함하는 chatInfo 개체가 있습니다.

코드 실행

애플리케이션을 실행합니다.

Teams 모임에 참가하려면 Teams의 모임 링크를 UI에 입력합니다.

팀의 모임에 참가한 후에는 팀의 클라이언트에서 사용자의 모임 참가를 수락해야 합니다. 사용자가 수락되고 채팅에 참가하면 메시지를 보내고 받을 수 있습니다.

완료된 iOS 애플리케이션 스크린샷.

참고 항목

특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.

이 빠른 시작에서는 Android용 Azure Communication Services 채팅 SDK를 사용하여 Teams 모임에서 채팅하는 방법을 알아봅니다.

예제 코드

끝으로 건너뛰려면 GitHub에서 이 빠른 시작을 샘플로 다운로드할 수 있습니다.

필수 조건

Teams 상호 운용성 사용

게스트 사용자로 Teams 미팅에 조인하는 Communication Services 사용자는 Teams 미팅 호출에 조인한 경우에만 미팅 채팅에 액세스할 수 있습니다. Teams 미팅 호출에 Communication Services 사용자를 추가하는 방법을 알아보려면 Teams interop 설명서를 참조하세요.

이 기능을 사용하려면 두 엔터티를 소유하는 조직의 구성원이어야 합니다.

모임 채팅 참가

Teams 상호 운용성을 사용하도록 설정하면 Communication Services 사용자가 Calling SDK를 사용하여 외부 사용자로 Teams 통화에 조인할 수 있습니다. 통화에 참가하면 채팅 통화에 참가자로 추가됩니다. 그러면 통화 중인 다른 사용자와 메시지를 주고받을 수 있습니다. 사용자는 통화에 참여하기 전에 전송된 채팅 메시지에 액세스할 수 없습니다. 모임에 참가하고 채팅을 시작하려면 다음 단계를 수행하면 됩니다.

Teams 통화 앱에 채팅 추가

모듈 수준 build.gradle에서 채팅 SDK에 대한 종속성을 추가합니다.

Important

알려진 문제: Android 채팅 및 통화 SDK를 동일한 애플리케이션에서 함께 사용하는 경우 채팅 SDK의 실시간 알림 기능이 작동하지 않습니다. 종속성 해결 문제가 발생합니다. 솔루션에 대해 작업하는 동안 앱의 build.gradle 파일에 있는 채팅 SDK 종속성에 다음 제외 항목을 추가하여 실시간 알림 기능을 끌 수 있습니다.

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

Teams UI 레이아웃 추가

activity_main.xml의 코드를 다음 코드 조각으로 바꿉니다. 스레드 ID 및 메시지 전송을 위한 입력, 입력된 메시지 전송 버튼 및 기본 채팅 레이아웃을 추가합니다.

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

Teams UI 컨트롤 사용

패키지 가져오기 및 상태 변수 정의

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;

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

<USER_ID>를 채팅을 시작한 사용자의 ID로 바꿉니다. <COMMUNICATION_SERVICES_RESOURCE_ENDPOINT>를 Communication Services 리소스의 엔드포인트로 바꿉니다.

ChatThreadClient 초기화

회의에 참가한 후 ChatThreadClient를 인스턴스화하고 채팅 구성 요소를 표시합니다.

아래 코드로 MainActivity.joinTeamsMeeting() 메서드의 끝을 업데이트합니다.

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

메시지 보내기 사용

MainActivitysendMessage() 메서드를 추가합니다. ChatThreadClient를 사용하여 사용자를 대신하여 메시지를 보냅니다.

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

메시지에 대한 폴링을 사용하도록 설정하고 애플리케이션에서 메시지를 렌더링합니다.

Important

알려진 문제: 채팅 SDK의 실시간 알림 기능은 통화 SDK와 함께 작동하지 않으므로 사전 정의된 간격으로 GetMessages API를 폴링해야 합니다. 이 샘플에서는 3초 간격을 사용합니다.

GetMessages API가 반환한 메시지 목록에서 다음 데이터를 얻을 수 있습니다.

  • 조인 이후 스레드의 texthtml 메시지
  • 스레드 명단에 대한 변경 사항
  • 스레드 토픽에 대한 업데이트

MainActivity 클래스에 처리기와 3초 간격으로 실행될 실행 가능한 작업을 추가합니다.

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

작업은 초기화 단계에서 업데이트된 MainActivity.joinTeamsMeeting() 메서드의 끝에서 이미 시작되었습니다.

마지막으로 스레드에서 액세스 가능한 모든 메시지를 쿼리하고, 메시지 유형별로 구문 분석하고, htmltext 메시지를 표시하는 메서드를 추가합니다.

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

채팅 스레드 참가자의 표시 이름은 Teams 클라이언트에서 설정하지 않습니다. 이름이 참가자 나열을 위한 API, participantsAdded 이벤트 및 participantsRemoved 이벤트에서 null로 반환됩니다. 채팅 참가자의 표시 이름은 call 개체의 remoteParticipants 필드에서 검색할 수 있습니다.

Communication Services 사용자를 위한 Teams 미팅 채팅 스레드 가져오기

Teams 모임 링크 및 채팅은 그래프 설명서에 자세히 설명된 Graph API를 사용하여 검색할 수 있습니다. Communication Services Calling SDK는 전체 Teams 모임 링크를 수락합니다. 이 링크는 joinWebUrl 속성에서 액세스할 수 있는 onlineMeeting 리소스의 일부로 반환됩니다.

Graph API를 사용하면 threadID도 가져올 수 있습니다. 응답에는 threadID를 포함하는 chatInfo 개체가 있습니다.

코드 실행

이제 도구 모음의 "앱 실행" 단추(Shift+F10)를 사용하여 앱을 시작할 수 있습니다.

Teams 모임에 참여하고 채팅하려면 UI에 팀의 모임 링크와 스레드 ID를 입력합니다.

팀의 모임에 참가한 후에는 팀의 클라이언트에서 사용자의 모임 참가를 수락해야 합니다. 사용자가 수락되고 채팅에 참가하면 메시지를 보내고 받을 수 있습니다.

완성된 Android 애플리케이션의 스크린샷.

참고 항목

특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.

이 빠른 시작에서는 C#용 Azure Communication Services 채팅 SDK를 사용하여 Teams 모임에서 채팅하는 방법을 알아봅니다.

샘플 코드

GitHub에서 이 빠른 시작에 대한 코드를 찾습니다.

필수 조건

모임 채팅 참가

Communication Services 사용자는 호출 SDK를 사용하여 익명 사용자로 Teams 모임에 참가할 수 있습니다. 모임에 참가하면 모임 채팅에도 참가자로 추가됩니다. 그러면 모임의 다른 사용자와 메시지를 보내고 받을 수 있습니다. 사용자는 모임에 참가하기 전에 전송된 채팅 메시지에 액세스할 수 없으며 모임이 끝난 후에는 메시지를 보내거나 받을 수 없습니다. 모임에 참가하고 채팅을 시작하려면 다음 단계를 수행하면 됩니다.

코드 실행

Visual Studio에서 코드를 빌드하고 실행할 수 있습니다. 지원되는 솔루션 플랫폼인 x64,x86ARM64에 유의해야 합니다.

  1. PowerShell, Windows 터미널, 명령 프롬프트 또는 그에 상응하는 인스턴스를 열고 샘플을 복제할 디렉터리로 이동합니다.
  2. git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
  3. Visual Studio에서 ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj 프로젝트를 엽니다.
  4. 다음 NuGet 패키지 버전(또는 그 이상)을 설치합니다.
Install-Package Azure.Communication.Calling -Version 1.0.0-beta.29
Install-Package Azure.Communication.Chat -Version 1.1.0
Install-Package Azure.Communication.Common -Version 1.0.1
Install-Package Azure.Communication.Identity -Version 1.0.1

  1. 사전 요구 사항에서 확보한 Communication Services 리소스를 사용하여 ChatTeamsInteropQuickStart/MainPage.xaml.cs 파일에 connectionstring을 추가합니다.
//Azure Communication Services resource connection string, i.e., = "endpoint=https://your-resource.communication.azure.net/;accesskey=your-access-key";
private const string connectionString_ = "";

Important

  • 코드(예: x64)를 실행하기 전에 Visual Studio의 '솔루션 플랫폼' 드롭다운 목록에서 적절한 플랫폼을 선택합니다.
  • Windows 10에서 '개발자 모드'를 사용하는지 확인합니다(개발자 설정).

올바르게 구성되지 않으면 다음 단계가 작동하지 않습니다.

  1. F5 키를 눌러 디버깅 모드에서 프로젝트를 시작합니다.
  2. 'Teams 모임 링크' 상자에 유효한 팀 회의 링크를 붙여넣습니다(다음 섹션 참조).
  3. 'Teams 모임 참가'를 눌러 채팅을 시작합니다.

Important

호출 SDK가 Windows 앱을 호출하는 Communication Services 참조 팀 모임과 연결되면 채팅 작업을 처리하는 주요 기능은 StartPollingForChatMessages와 SendMessageButton_Click입니다. 두 코드 조각 모두 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;
        }

Teams 모임 링크는 그래프 설명서에 자세히 설명된 Graph API를 사용하여 검색할 수 있습니다. 이 링크는 joinWebUrl 속성에서 액세스할 수 있는 onlineMeeting 리소스의 일부로 반환됩니다.

또한 Teams 모임 초대 자체의 모임 참가 URL에서 필요한 모임 링크를 가져올 수 있습니다. Teams 미팅 링크는 https://teams.microsoft.com/l/meetup-join/meeting_chat_thread_id/1606337455313?context=some_context_here와 같습니다. 팀 링크의 형식이 이와 다른 경우 Graph API를 사용하여 스레드 ID를 검색해야 합니다.

완료된 csharp 애플리케이션 스크린샷

참고 항목

특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.

리소스 정리

Communication Services 구독을 정리하고 제거하려면 리소스 또는 리소스 그룹을 삭제하면 됩니다. 리소스 그룹을 삭제하면 해당 리소스 그룹에 연결된 다른 모든 리소스가 함께 삭제됩니다. 리소스 정리에 대해 자세히 알아보세요.

다음 단계

자세한 내용은 다음 문서를 참조하세요.