快速入門:將您的通話應用程式加入至小組會議
在本快速入門中,您會了解如何使用適用於 JavaScript 的 Azure 通訊服務通話 SDK 加入 Teams 會議。
範例程式碼
在 GitHub 上尋找本快速入門的最終程式碼。
必要條件
- 運作中的通訊服務通話 Web 應用程式。
- Teams 部署。
- Teams 會議標識碼和密碼加入 API 支援的最低版本:1.17.1
- 存取權杖。
新增 Teams UI 控制項
使用下列程式碼片段取代 index.html 中的程式碼。 透過 Teams 會議連結或 Teams MeetingId 和密碼加入 Teams 會議。 文字輸入框是用來輸入 Teams 會議內容,而按鈕則是用來加入指定的會議:
<!DOCTYPE html>
<html>
<head>
<title>Communication Client - Calling Sample</title>
</head>
<body>
<h4>Azure Communication Services</h4>
<h1>Teams meeting join quickstart</h1>
<input id="teams-link-input" type="text" placeholder="Teams meeting link"
style="margin-bottom:1em; width: 300px;" />
<p><input id="teams-meetingId-input" type="text" placeholder="Teams meetingId"
style="margin-bottom:1em; width: 300px;" /></p>
<p><input id="teams-passcode-input" type="text" placeholder="Teams meeting Passcode"
style="margin-bottom:1em; width: 300px;" /></p>
<p>Call state <span style="font-weight: bold" id="call-state">-</span></p>
<p><span style="font-weight: bold" id="recording-state"></span></p>
<div>
<button id="join-meeting-button" type="button" disabled="false">
Join Teams Meeting
</button>
<button id="hang-up-button" type="button" disabled="true">
Hang Up
</button>
</div>
<script src="./app.js" type="module"></script>
</body>
</html>
啟用 Teams UI 控制項
使用下列程式碼片段取代 app.js 檔案的內容。
import { CallClient } from "@azure/communication-calling";
import { Features } from "@azure/communication-calling";
import { AzureCommunicationTokenCredential } from '@azure/communication-common';
let call;
let callAgent;
const meetingLinkInput = document.getElementById('teams-link-input');
const meetingIdInput = document.getElementById('teams-meetingId-input');
const meetingPasscodeInput = document.getElementById('teams-passcode-input');
const hangUpButton = document.getElementById('hang-up-button');
const teamsMeetingJoinButton = document.getElementById('join-meeting-button');
const callStateElement = document.getElementById('call-state');
const recordingStateElement = document.getElementById('recording-state');
async function init() {
const callClient = new CallClient();
const tokenCredential = new AzureCommunicationTokenCredential("<USER ACCESS TOKEN>");
callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'Test user'});
teamsMeetingJoinButton.disabled = false;
}
init();
hangUpButton.addEventListener("click", async () => {
// end the current call
await call.hangUp();
// toggle button states
hangUpButton.disabled = true;
teamsMeetingJoinButton.disabled = false;
callStateElement.innerText = '-';
});
teamsMeetingJoinButton.addEventListener("click", () => {
// join with meeting link
call = callAgent.join({meetingLink: meetingLinkInput.value}, {});
//(or) to join with meetingId and passcode use the below code snippet.
//call = callAgent.join({meetingId: meetingIdInput.value, passcode: meetingPasscodeInput.value}, {});
call.on('stateChanged', () => {
callStateElement.innerText = call.state;
})
call.api(Features.Recording).on('isRecordingActiveChanged', () => {
if (call.api(Features.Recording).isRecordingActive) {
recordingStateElement.innerText = "This call is being recorded";
}
else {
recordingStateElement.innerText = "";
}
});
// toggle button states
hangUpButton.disabled = false;
teamsMeetingJoinButton.disabled = true;
});
取得 Teams 會議連結
您可以使用圖形 API 擷取 Teams 會議連結,如 Graph 文件所詳述。
通訊服務通話 SDK 會接受完整的 Teams 會議連結。 此連結也可以作為 onlineMeeting
資源的一部分傳回,並可以在 joinWebUrl
屬性底下存取。您也可以從 Teams 會議邀請本身的 [加入會議] URL 取得所需的會議資訊。
取得 Teams 會議識別碼和密碼
- 圖形 API:使用圖形 API 擷取 onlineMeeting 資源的相關資訊,並檢查屬性 joinMeetingIdSettings 中的物件。
- Teams:在您的 Teams 應用程式中,移至 [行事曆] 應用程式並開啟會議的詳細資料。 線上會議在會議定義中具有會議識別碼和密碼。
- Outlook:您可以在行事曆活動或電子郵件會議邀請中找到會議識別碼和密碼。
執行程式碼
執行下列命令,在本機 Web 伺服器上組合應用程式主機:
npx webpack serve --config webpack.config.js
開啟瀏覽器並瀏覽至 http://localhost:8080/. 您應該看到下列內容:
將 Teams 內容插入到文字方塊中,然後按 [加入 Teams 會議] 從通訊服務應用程式內加入 Teams 會議。
在本快速入門中,您將會了解如何使用適用於 Windows 的 Azure 通訊服務通話 SDK 加入 Teams 會議。
範例程式碼
在 GitHub for UWP 和 WinUI 3 上尋找本快速入門的完成程式代碼。
必要條件
- 運作中的通訊服務通話 Windows 應用程式。
- Teams 部署。
- Teams 會議標識碼和密碼加入 API 支援的最低版本:1.7.0
- 存取權杖。
新增 Teams UI 控制項和啟用 Teams UI 控制項
使用下列程式碼片段取代 MainPage.xaml 中的程式碼。 文字方塊將用來輸入 Teams 會議內容,而按鈕則用來加入指定的會議:
<Page
x:Class="CallingQuickstart.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CallingQuickstart"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="800" Height="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="16*"/>
<RowDefinition Height="30*"/>
<RowDefinition Height="200*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="16*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" x:Name="AppTitleBar" Background="LightSeaGreen">
<!-- Width of the padding columns is set in LayoutMetricsChanged handler. -->
<!-- Using padding columns instead of Margin ensures that the background paints the area under the caption control buttons (for transparent buttons). -->
<TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="7,7,0,0"/>
</Grid>
<StackPanel Grid.Row="1">
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" />
<TextBlock Text="or" Padding="7,7,0,0" />
<TextBox x:Name="CalleeMeetingId" PlaceholderText="Teams Meeting Id" TextWrapping="Wrap" VerticalAlignment="Center" />
<TextBox x:Name="CalleeMeetingPasscode" PlaceholderText="Teams Meeting Passcode" TextWrapping="Wrap" VerticalAlignment="Center" />
</StackPanel>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<MediaPlayerElement x:Name="LocalVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="0" VerticalAlignment="Center" AutoPlay="True" />
<MediaPlayerElement x:Name="RemoteVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="1" VerticalAlignment="Center" AutoPlay="True" />
</Grid>
<StackPanel Grid.Row="3" Orientation="Vertical" Grid.RowSpan="2">
<StackPanel Orientation="Horizontal">
<Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
<Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
</StackPanel>
</StackPanel>
<TextBox Grid.Row="5" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
</Grid>
</Page>
啟用 Teams UI 控制項
將 MainPage.xaml.cs
的內容取代為下列程式碼片段:
using Azure.Communication.Calling.WindowsClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Media.Core;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace CallingQuickstart
{
public sealed partial class MainPage : Page
{
private const string authToken = "<AUTHENTICATION_TOKEN>";
private CallClient callClient;
private CallTokenRefreshOptions callTokenRefreshOptions = new CallTokenRefreshOptions(false);
private CallAgent callAgent;
private CommunicationCall call;
private LocalOutgoingAudioStream micStream;
private LocalOutgoingVideoStream cameraStream;
#region Page initialization
public MainPage()
{
this.InitializeComponent();
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
await InitCallAgentAndDeviceManagerAsync();
base.OnNavigatedTo(e);
}
#endregion
#region UI event handlers
private async void CallButton_Click(object sender, RoutedEventArgs e)
{
var callString = CalleeTextBox.Text.Trim();
var meetingId = CalleeMeetingId.Text.Trim();
var passcode = CalleeMeetingPasscode.Text.Trim();
// join with meeting link
if (!string.IsNullOrEmpty(callString))
{
call = await JoinTeamsMeetingByLinkAsync(teamsMeetinglink);
}
// (or) to join with meetingId and passcode use the below code snippet.
// call = await JoinTeamsMeetingByMeetingIdAsync(meetingId, passcode);
if (call != null)
{
call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
call.StateChanged += OnStateChangedAsync;
}
}
private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
foreach (var localVideoStream in call.OutgoingVideoStreams)
{
await call.StopVideoAsync(localVideoStream);
}
if (cameraStream != null)
{
await cameraStream.StopPreviewAsync();
}
await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
}
}
#endregion
#region API event handlers
private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
{
var call = sender as CommunicationCall;
if (call != null)
{
var state = call.State;
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
QuickstartTitle.Text = $"{Package.Current.DisplayName} - {state.ToString()}";
Window.Current.SetTitleBar(AppTitleBar);
HangupButton.IsEnabled = state == CallState.Connected || state == CallState.Ringing;
CallButton.IsEnabled = !HangupButton.IsEnabled;
});
switch (state)
{
case CallState.Connected:
{
await call.StartAudioAsync(micStream);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
Stats.Text = $"Call id: {Guid.Parse(call.Id).ToString("D")}, Remote caller id: {call.RemoteParticipants.FirstOrDefault()?.Identifier.RawId}";
});
break;
}
case CallState.Disconnected:
{
call.RemoteParticipantsUpdated -= OnRemoteParticipantsUpdatedAsync;
call.StateChanged -= OnStateChangedAsync;
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
Stats.Text = $"Call ended: {call.CallEndReason.ToString()}";
});
call.Dispose();
break;
}
default: break;
}
}
}
private async void OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
await OnParticipantChangedAsync(
args.RemovedParticipants.ToList<RemoteParticipant>(),
args.AddedParticipants.ToList<RemoteParticipant>());
}
private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
foreach (var participant in removedParticipants)
{
foreach(var incomingVideoStream in participant.IncomingVideoStreams)
{
var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
if (remoteVideoStream != null)
{
await remoteVideoStream.StopPreviewAsync();
}
}
participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
}
foreach (var participant in addedParticipants)
{
participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
}
}
private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
CallVideoStream callVideoStream = e.Stream;
switch (callVideoStream.Direction)
{
case StreamDirection.Outgoing:
OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
break;
case StreamDirection.Incoming:
OnIncomingVideoStreamStateChangedAsync(callVideoStream as IncomingVideoStream);
break;
}
}
private async void OnIncomingVideoStreamStateChangedAsync(IncomingVideoStream incomingVideoStream)
{
switch (incomingVideoStream.State)
{
case VideoStreamState.Available:
switch (incomingVideoStream.Kind)
{
case VideoStreamKind.RemoteIncoming:
var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
var uri = await remoteVideoStream.StartPreviewAsync();
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RemoteVideo.Source = MediaSource.CreateFromUri(uri);
});
break;
case VideoStreamKind.RawIncoming:
break;
}
break;
case VideoStreamState.Started:
break;
case VideoStreamState.Stopping:
case VideoStreamState.Stopped:
if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
{
var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
await remoteVideoStream.StopPreviewAsync();
}
break;
case VideoStreamState.NotAvailable:
break;
}
}
#endregion
#region Helpers
private async Task InitCallAgentAndDeviceManagerAsync()
{
this.callClient = new CallClient(new CallClientOptions() {
Diagnostics = new CallDiagnosticsOptions() {
AppName = "CallingQuickstart",
AppVersion="1.0",
Tags = new[] { "Calling", "ACS", "Windows" }
}
});
// Set up local video stream using the first camera enumerated
var deviceManager = await this.callClient.GetDeviceManagerAsync();
var camera = deviceManager?.Cameras?.FirstOrDefault();
var mic = deviceManager?.Microphones?.FirstOrDefault();
micStream = new LocalOutgoingAudioStream();
if (camera != null)
{
cameraStream = new LocalOutgoingVideoStream(selectedCamerea);
var localUri = await cameraStream.StartPreviewAsync();
LocalVideo.Source = MediaSource.CreateFromUri(localUri);
if (call != null) {
await call?.StartVideoAsync(cameraStream);
}
}
var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);
var callAgentOptions = new CallAgentOptions()
{
DisplayName = $"{Environment.MachineName}/{Environment.UserName}",
};
this.callAgent = await this.callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
// Sets up additional event sinks
}
private async Task<CommunicationCall> JoinTeamsMeetingByLinkAsync(Uri teamsCallLink)
{
var joinCallOptions = GetJoinCallOptions();
var teamsMeetingLinkLocator = new TeamsMeetingLinkLocator(teamsCallLink.AbsoluteUri);
var call = await callAgent.JoinAsync(teamsMeetingLinkLocator, joinCallOptions);
return call;
}
private async Task<CommunicationCall> JoinTeamsMeetingByMeetingIdAsync(String meetingId, String passcode)
{
var joinCallOptions = GetJoinCallOptions();
var teamsMeetingIdLocator = new TeamsMeetingIdLocator(meetingId, passcode);
var call = await callAgent.JoinAsync(teamsMeetingIdLocator, joinCallOptions);
return call;
}
private JoinCallOptions GetJoinCallOptions()
{
return new JoinCallOptions() {
OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true },
OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
};
}
#endregion
}
}
取得 Teams 會議連結
您可以使用圖形 API 來擷取 Teams 會議連結。 詳細說明請見 Graph 文件。
通訊服務通話 SDK 會接受完整的 Teams 會議連結。 此連結會作為 onlineMeeting
資源的一部分傳回,可在 joinWebUrl
屬性下存取。 您也可以從 Teams 會議邀請本身的加入會議 URL 取得所需的會議資訊。
取得 Teams 會議識別碼和密碼
- 圖形 API:使用圖形 API 擷取 onlineMeeting 資源的相關信息,並在屬性
joinMeetingIdSettings
中檢查 物件。 - Teams:在您的 Teams 應用程式中,移至 [行事曆] 應用程式並開啟會議的詳細資料。 線上會議在會議定義中具有會議識別碼和密碼。
- Outlook:您可以在行事曆活動或電子郵件會議邀請中找到會議識別碼和密碼。
啟動應用程式並加入 Teams 會議
您可以藉由選取 [偵錯] > [開始偵錯] 或使用 (F5) 鍵盤快速鍵,在 Visual Studio 上建置及執行您的應用程式。
將 Teams 內容插入到文字方塊中,然後按 [加入 Teams 會議] 從通訊服務應用程式內加入 Teams 會議。
在本快速入門中,您將會了解如何使用適用於 Android 的 Azure 通訊服務通話 SDK 加入 Teams 會議。
範例程式碼
在 GitHub 上尋找本快速入門的最終程式碼。
必要條件
- 運作中的通訊服務通話 Android 應用程式。
- Teams 部署。
- Teams 會議標識碼和密碼加入 API 支援的最低版本:2.9.0
- 存取權杖。
新增 Teams UI 控制項
使用下列程式碼片段取代 activity_main.xml 中的程式碼。 文字方塊將用來輸入 Teams 會議內容,而按鈕則用來加入指定的會議:
<?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">
<LinearLayout
android:id="@+id/meetingInfoLinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="100dp">
<EditText
android:id="@+id/teams_meeting_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Teams meeting link"
android:inputType="textUri" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="or"
android:textAlignment="center"
android:layout_marginTop="10dp"/>
<EditText
android:id="@+id/teams_meeting_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Teams meeting id"
android:inputType="textUri" />
<EditText
android:id="@+id/teams_meeting_passcode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Teams meeting passcode"
android:inputType="textUri" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="70dp"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<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" />
</androidx.constraintlayout.widget.ConstraintLayout>
啟用 Teams UI 控制項
將 MainActivity.java
的內容取代為下列程式碼片段:
package com.contoso.acsquickstart;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import com.azure.android.communication.calling.Call;
import com.azure.android.communication.calling.CallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.HangUpOptions;
import com.azure.android.communication.calling.JoinCallOptions;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.TeamsMeetingLinkLocator;
// import for meeting id and passcode join
// import com.azure.android.communication.calling.TeamsMeetingIdLocator;
public class MainActivity extends AppCompatActivity {
private static final String[] allPermissions = new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE };
private static final String UserToken = "<User_Access_Token>";
TextView callStatusBar;
TextView recordingStatusBar;
private CallAgent agent;
private Call call;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getAllPermissions();
createAgent();
Button joinMeetingButton = findViewById(R.id.join_meeting_button);
joinMeetingButton.setOnClickListener(l -> joinTeamsMeeting());
Button hangupButton = findViewById(R.id.hangup_button);
hangupButton.setOnClickListener(l -> leaveMeeting());
callStatusBar = findViewById(R.id.call_status_bar);
recordingStatusBar = findViewById(R.id.recording_status_bar);
}
/**
* Join Teams meeting
*/
private void joinTeamsMeeting() {
if (UserToken.startsWith("<")) {
Toast.makeText(this, "Please enter token in source code", Toast.LENGTH_SHORT).show();
return;
}
EditText calleeIdView = findViewById(R.id.teams_meeting_link);
EditText calleeMeetingId = findViewById(R.id.teams_meeting_id);
EditText calleeMeetingPasscode = findViewById(R.id.teams_meeting_passcode);
String meetingLink = calleeIdView.getText().toString();
String meetingId = calleeMeetingId.getText().toString();
String passcode = calleeMeetingPasscode.getText().toString();
if (meetingLink.isEmpty()) {
Toast.makeText(this, "Please enter Teams meeting link", Toast.LENGTH_SHORT).show();
return;
}
JoinCallOptions options = new JoinCallOptions();
// join with meeting link
TeamsMeetingLinkLocator teamsMeetingLocator = new TeamsMeetingLinkLocator(meetingLink);
// (or) to join with meetingId and passcode use the below code snippet.
//TeamsMeetingIdLocator teamsMeetingIdLocator = new TeamsMeetingIdLocator(meetingId, passcode);
call = agent.join(
getApplicationContext(),
teamsMeetingLocator,
options);
call.addOnStateChangedListener(p -> setCallStatus(call.getState().toString()));
call.addOnIsRecordingActiveChangedListener(p -> setRecordingStatus(call.isRecordingActive()));
}
/**
* Leave from the meeting
*/
private void leaveMeeting() {
try {
call.hangUp(new HangUpOptions()).get();
} catch (ExecutionException | InterruptedException e) {
Toast.makeText(this, "Unable to leave meeting", Toast.LENGTH_SHORT).show();
}
}
/**
* Create the call agent
*/
private void createAgent() {
try {
CommunicationTokenCredential credential = new CommunicationTokenCredential(UserToken);
agent = new CallClient().createCallAgent(getApplicationContext(), credential).get();
} catch (Exception ex) {
Toast.makeText(getApplicationContext(), "Failed to create call agent.", Toast.LENGTH_SHORT).show();
}
}
/**
* Request each required permission if the app doesn't already have it.
*/
private void getAllPermissions() {
ArrayList<String> permissionsToAskFor = new ArrayList<>();
for (String permission : allPermissions) {
if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionsToAskFor.add(permission);
}
}
if (!permissionsToAskFor.isEmpty()) {
ActivityCompat.requestPermissions(this, permissionsToAskFor.toArray(new String[0]), 1);
}
}
/**
* Ensure all permissions were granted, otherwise inform the user permissions are missing.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
boolean allPermissionsGranted = true;
for (int result : grantResults) {
allPermissionsGranted &= (result == PackageManager.PERMISSION_GRANTED);
}
if (!allPermissionsGranted) {
Toast.makeText(this, "All permissions are needed to make the call.", Toast.LENGTH_LONG).show();
finish();
}
}
/**
* Shows call status in status bar
*/
private void setCallStatus(String status) {
runOnUiThread(() -> callStatusBar.setText(status));
}
/**
* Shows recording status bar
*/
private void setRecordingStatus(boolean status) {
if (status == true) {
runOnUiThread(() -> recordingStatusBar.setText("This call is being recorded"));
}
else {
runOnUiThread(() -> recordingStatusBar.setText(""));
}
}
}
取得 Teams 會議連結
您可以使用圖形 API 來擷取 Teams 會議連結。 詳細說明請見 Graph 文件。
通訊服務通話 SDK 會接受完整的 Teams 會議連結。 此連結會作為 onlineMeeting
資源的一部分傳回,可在 joinWebUrl
屬性下存取。 您也可以從 Teams 會議邀請本身的加入會議 URL 取得所需的會議資訊。
取得 Teams 會議識別碼和密碼
- 圖形 API:使用圖形 API 擷取 onlineMeeting 資源的相關資訊,並檢查屬性 joinMeetingIdSettings 中的物件。
- Teams:在您的 Teams 應用程式中,移至 [行事曆] 應用程式並開啟會議的詳細資料。 線上會議在會議定義中具有會議識別碼和密碼。
- Outlook:您可以在行事曆活動或電子郵件會議邀請中找到會議識別碼和密碼。
啟動應用程式並加入 Teams 會議
應用程式現在可以使用工具列上的 [執行應用程式] 按鈕 (Shift+F10) 來啟動。 您應該看到下列內容:
將 Teams 內容插入到文字方塊中,然後按 [加入會議] 從通訊服務應用程式內加入 Teams 會議。
在本快速入門中,您將會了解如何使用適用於 iOS 的 Azure 通訊服務通話 SDK 加入 Teams 會議。
必要條件
- 運作中的通訊服務通話 iOS 應用程式。
- Teams 部署。
- Teams 會議標識碼和密碼加入 API 支援的最低版本:2.11.0
- 存取權杖。
我們將會針對本快速入門使用 AzureCommunicationCalling SDK 的 beta.12,因此我們需要更新 Podfile 並再次安裝 Pod。
以下列程式碼取代您的 Podfile 並儲存 (請確定 "target" 與您專案的名稱相符):
platform :ios, '13.0'
use_frameworks!
target 'AzureCommunicationCallingSample' do
pod 'AzureCommunicationCalling', '1.0.0-beta.12'
end
刪除 Pods 資料夾、Podfile.lock 和 .xcworkspace.
檔案。
使用 Xcode 執行 pod install
並開啟 .xcworkspace
。
新增 Teams UI 控制項和啟用 Teams UI 控制項
使用下列程式碼片段取代 ContentView.swift 中的程式碼。 文字方塊將用來輸入 Teams 會議內容,而按鈕則用來加入指定的會議:
import SwiftUI
import AzureCommunicationCalling
import AVFoundation
struct ContentView: View {
@State var meetingLink: String = ""
@State var meetingId: String = ""
@State var meetingPasscode: String = ""
@State var callStatus: String = ""
@State var message: String = ""
@State var recordingStatus: String = ""
@State var callClient: CallClient?
@State var callAgent: CallAgent?
@State var call: Call?
@State var callObserver: CallObserver?
var body: some View {
NavigationView {
Form {
Section {
TextField("Teams meeting link", text: $meetingLink)
TextField("Teams meeting id", text: $meetingId)
TextField("Teams meeting passcode", text: $meetingPasscode)
Button(action: joinTeamsMeeting) {
Text("Join Teams Meeting")
}.disabled(callAgent == nil)
Button(action: leaveMeeting) {
Text("Leave Meeting")
}.disabled(call == nil)
Text(callStatus)
Text(message)
Text(recordingStatus)
}
}
.navigationBarTitle("Calling Quickstart")
}.onAppear {
// Initialize call agent
var userCredential: CommunicationTokenCredential?
do {
userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
print("ERROR: It was not possible to create user credential.")
self.message = "Please enter your token in source code"
return
}
self.callClient = CallClient()
// Creates the call agent
self.callClient?.createCallAgent(userCredential: userCredential!) { (agent, error) in
if error != nil {
self.message = "Failed to create CallAgent."
return
} else {
self.callAgent = agent
self.message = "Call agent successfully created."
}
}
}
}
func joinTeamsMeeting() {
// Ask permissions
AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
if granted {
let joinCallOptions = JoinCallOptions()
// join with meeting link
let teamsMeetingLocator = TeamsMeetingLinkLocator(meetingLink: self.meetingLink)
// (or) to join with meetingId and passcode use the below code snippet.
// let teamsMeetingLocator = TeamsMeetingIdLocator(with: self.meetingId, passcode: self.meetingPasscode)
self.callAgent?.join(with: teamsMeetingLocator, joinCallOptions: joinCallOptions) {(call, error) in
if (error == nil) {
self.call = call
self.callObserver = CallObserver(self)
self.call!.delegate = self.callObserver
self.message = "Teams meeting joined successfully"
} else {
print("Failed to get call object")
return
}
}
}
}
}
func leaveMeeting() {
if let call = call {
call.hangUp(options: nil, completionHandler: { (error) in
if error == nil {
self.message = "Leaving Teams meeting was successful"
} else {
self.message = "Leaving Teams meeting failed"
}
})
} else {
self.message = "No active call to hangup"
}
}
}
class CallObserver : NSObject, CallDelegate {
private var owner:ContentView
init(_ view:ContentView) {
owner = view
}
public func call(_ call: Call, didChangeState args: PropertyChangedEventArgs) {
owner.callStatus = 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 !!"
} else if call.state == .connected {
owner.message = "Joined Meeting !!"
}
}
public func call(_ call: Call, didChangeRecordingState args: PropertyChangedEventArgs) {
if (call.isRecordingActive == true) {
owner.recordingStatus = "This call is being recorded"
}
else {
owner.recordingStatus = ""
}
}
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"
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
取得 Teams 會議連結
您可以使用圖形 API 來擷取 Teams 會議連結。 詳細說明請見 Graph 文件。
通訊服務通話 SDK 會接受完整的 Teams 會議連結。 此連結會作為 onlineMeeting
資源的一部分傳回,可在 joinWebUrl
屬性下存取。 您也可以從 Teams 會議邀請本身的加入會議 URL 取得所需的會議資訊。
啟動應用程式並加入 Teams 會議
您可以藉由選取 [產品] > [執行] 或使用 (⌘-R) 鍵盤快速鍵,在 iOS 模擬器上建置並執行應用程式。
將 Teams 內容插入到文字方塊中,然後按 [加入 Teams 會議] 從通訊服務應用程式內加入 Teams 會議。
清除資源
如果您想要清除並移除通訊服務訂用帳戶,您可以刪除資源或資源群組。 刪除資源群組也會刪除與其相關聯的任何其他資源。 深入了解如何清除資源。
下一步
如需詳細資訊,請參閱下列文章:
- 查看我們的通話 Hero 範例
- 開始使用 UI 程式庫
- 了解通話 SDK 功能
- 深入了解通話的運作方式