빠른 시작: 채팅 앱으로 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/
로 이동합니다. 다음 스크린샷과 같이 앱이 시작된 것을 볼 수 있습니다.
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로 설정해야 합니다. 이 빠른 시작 중에는 테스트를 만들지 않습니다. 테스트 포함을 선택 취소합니다.
CocoaPods 설치
이 가이드를 사용하여 Mac에 CocoaPods를 설치합니다.
CocoaPods를 사용하여 패키지 및 종속성 설치
애플리케이션에 대한
Podfile
을 만들려면 터미널을 열고 프로젝트 폴더로 이동한 후 pod init을 실행합니다.대상 아래의
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
pod install
를 실행합니다.Xcode로
.xcworkspace
파일을 엽니다.
마이크에 대한 액세스 요청
디바이스의 마이크에 액세스하려면 앱의 정보 속성 목록을 NSMicrophoneUsageDescription
으로 업데이트해야 합니다. 연결된 값을 시스템이 사용자에게 액세스를 요청하는 데 사용하는 대화 상자에 포함된 string
으로 설정합니다.
대상에서 Info
탭을 선택하고 ‘개인 정보 - 마이크 사용 설명’에 문자열을 추가합니다.
사용자 스크립트 샌드박싱 사용 안 함
연결된 라이브러리 내의 일부 스크립트는 빌드 프로세스 중에 파일을 씁니다. 이를 허용하려면 Xcode에서 사용자 스크립트 샌드박싱을 사용하지 않도록 설정합니다.
빌드 설정에서 sandbox
을(를) 검색하고 User Script Sandboxing
을(를) No
(으)로 설정합니다.
모임 채팅 참가
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
함수에서 다음 코드를 추가하여 CallClient
및 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
}
모임 참가 함수 추가
모임 참가를 처리하기 위해 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에 입력합니다.
팀의 모임에 참가한 후에는 팀의 클라이언트에서 사용자의 모임 참가를 수락해야 합니다. 사용자가 수락되고 채팅에 참가하면 메시지를 보내고 받을 수 있습니다.
참고 항목
특정 기능은 현재 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);
}
메시지 보내기 사용
MainActivity
에 sendMessage()
메서드를 추가합니다. 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가 반환한 메시지 목록에서 다음 데이터를 얻을 수 있습니다.
- 조인 이후 스레드의
text
및html
메시지 - 스레드 명단에 대한 변경 사항
- 스레드 토픽에 대한 업데이트
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()
메서드의 끝에서 이미 시작되었습니다.
마지막으로 스레드에서 액세스 가능한 모든 메시지를 쿼리하고, 메시지 유형별로 구문 분석하고, html
및 text
메시지를 표시하는 메서드를 추가합니다.
private void retrieveMessages() throws InterruptedException {
// Initialize the list of messages not yet processed
ArrayList<ChatMessage> newChatMessages = new ArrayList<>();
// Retrieve all messages accessible to the user
PagedAsyncStream<ChatMessage> messagePagedAsyncStream
= this.chatThreadAsyncClient.listMessages(new ListChatMessagesOptions(), null);
// Set up a lock to wait until all returned messages have been inspected
CountDownLatch latch = new CountDownLatch(1);
// Traverse the returned messages
messagePagedAsyncStream.forEach(new AsyncStreamHandler<ChatMessage>() {
@Override
public void onNext(ChatMessage message) {
// Messages that should be displayed in the chat
if ((message.getType().equals(ChatMessageType.TEXT)
|| message.getType().equals(ChatMessageType.HTML))
&& !chatMessages.contains(message.getId())) {
newChatMessages.add(message);
chatMessages.add(message.getId());
}
if (message.getType().equals(ChatMessageType.PARTICIPANT_ADDED)) {
// Handle participants added to chat operation
List<ChatParticipant> participantsAdded = message.getContent().getParticipants();
CommunicationIdentifier participantsAddedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
if (message.getType().equals(ChatMessageType.PARTICIPANT_REMOVED)) {
// Handle participants removed from chat operation
List<ChatParticipant> participantsRemoved = message.getContent().getParticipants();
CommunicationIdentifier participantsRemovedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
if (message.getType().equals(ChatMessageType.TOPIC_UPDATED)) {
// Handle topic updated
String newTopic = message.getContent().getTopic();
CommunicationIdentifier topicUpdatedBy = message.getContent().getInitiatorCommunicationIdentifier();
}
}
@Override
public void onError(Throwable throwable) {
latch.countDown();
}
@Override
public void onComplete() {
latch.countDown();
}
});
// Wait until the operation completes
latch.await(1, TimeUnit.MINUTES);
// Returned messages should be ordered by the createdOn field to be guaranteed a proper chronological order
// For the purpose of this demo we will just reverse the list of returned messages
Collections.reverse(newChatMessages);
for (ChatMessage chatMessage : newChatMessages)
{
LinearLayout chatBoxLayout = findViewById(R.id.chat_box_layout);
// For the purpose of this demo UI, we don't need to use HTML formatting for displaying messages
// The Teams client always sends html messages in meeting chats
String message = Html.fromHtml(chatMessage.getContent().getMessage(), Html.FROM_HTML_MODE_LEGACY).toString().trim();
TextView messageView = new TextView(this);
messageView.setText(message);
// Compare with sender identifier and align LEFT/RIGHT accordingly
// Azure Communication Services users are of type CommunicationUserIdentifier
CommunicationIdentifier senderId = chatMessage.getSenderCommunicationIdentifier();
if (senderId instanceof CommunicationUserIdentifier
&& InitiatorId.equals(((CommunicationUserIdentifier) senderId).getId())) {
messageView.setTextColor(Color.GREEN);
messageView.setGravity(Gravity.RIGHT);
} else {
messageView.setTextColor(Color.BLUE);
messageView.setGravity(Gravity.LEFT);
}
// Note: messages with the deletedOn property set to a timestamp, should be marked as deleted
// Note: messages with the editedOn property set to a timestamp, should be marked as edited
messageView.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
chatBoxLayout.addView(messageView);
}
}
채팅 스레드 참가자의 표시 이름은 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를 입력합니다.
팀의 모임에 참가한 후에는 팀의 클라이언트에서 사용자의 모임 참가를 수락해야 합니다. 사용자가 수락되고 채팅에 참가하면 메시지를 보내고 받을 수 있습니다.
참고 항목
특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.
이 빠른 시작에서는 C#용 Azure Communication Services 채팅 SDK를 사용하여 Teams 모임에서 채팅하는 방법을 알아봅니다.
샘플 코드
GitHub에서 이 빠른 시작에 대한 코드를 찾습니다.
필수 조건
- Teams 배포
- 활성 구독이 있는 Azure 계정. 체험 계정을 만듭니다.
- 유니버설 Windows 플랫폼 개발 워크로드가 있는 Visual Studio 2019를 설치합니다.
- 배포된 Communication Services 리소스. Communication Services 리소스 만들기
- Teams 모임 링크.
모임 채팅 참가
Communication Services 사용자는 호출 SDK를 사용하여 익명 사용자로 Teams 모임에 참가할 수 있습니다. 모임에 참가하면 모임 채팅에도 참가자로 추가됩니다. 그러면 모임의 다른 사용자와 메시지를 보내고 받을 수 있습니다. 사용자는 모임에 참가하기 전에 전송된 채팅 메시지에 액세스할 수 없으며 모임이 끝난 후에는 메시지를 보내거나 받을 수 없습니다. 모임에 참가하고 채팅을 시작하려면 다음 단계를 수행하면 됩니다.
코드 실행
Visual Studio에서 코드를 빌드하고 실행할 수 있습니다. 지원되는 솔루션 플랫폼인 x64
,x86
및 ARM64
에 유의해야 합니다.
- PowerShell, Windows 터미널, 명령 프롬프트 또는 그에 상응하는 인스턴스를 열고 샘플을 복제할 디렉터리로 이동합니다.
git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git
- Visual Studio에서 ChatTeamsInteropQuickStart/ChatTeamsInteropQuickStart.csproj 프로젝트를 엽니다.
- 다음 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
- 사전 요구 사항에서 확보한 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에서 '개발자 모드'를 사용하는지 확인합니다(개발자 설정).
올바르게 구성되지 않으면 다음 단계가 작동하지 않습니다.
- F5 키를 눌러 디버깅 모드에서 프로젝트를 시작합니다.
- 'Teams 모임 링크' 상자에 유효한 팀 회의 링크를 붙여넣습니다(다음 섹션 참조).
- '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 미팅 링크 가져오기
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를 검색해야 합니다.
참고 항목
특정 기능은 현재 Teams와의 상호 운용성 시나리오에서 지원되지 않습니다. 지원되는 기능에 대해 자세히 알아보려면 Teams 외부 사용자를 위한 Teams 모임 기능을 참조하세요.
리소스 정리
Communication Services 구독을 정리하고 제거하려면 리소스 또는 리소스 그룹을 삭제하면 됩니다. 리소스 그룹을 삭제하면 해당 리소스 그룹에 연결된 다른 모든 리소스가 함께 삭제됩니다. 리소스 정리에 대해 자세히 알아보세요.
다음 단계
자세한 내용은 다음 문서를 참조하세요.