Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.37k stars 4.78k forks source link

[BUG] HangUpAsync never returns #26152

Closed juanmalm closed 12 months ago

juanmalm commented 2 years ago

Library name and version

Azure.Communication.Calling 1.0.0-beta.29

Describe the bug

Following the official quick start guide:

https://docs.microsoft.com/en-us/azure/communication-services/quickstarts/voice-video-calling/get-started-teams-interop?pivots=platform-windows

HangUpAsync does not work properly.

Expected behavior

await call_.HangUpAsync(new HangUpOptions()); works properly

Actual behavior

await call_.HangUpAsync(new HangUpOptions()); never returns and the user is not disconnected properly from the call

Reproduction Steps

  1. Follow the official quick start guide: https://docs.microsoft.com/en-us/azure/communication-services/quickstarts/voice-video-calling/get-started-teams-interop?pivots=platform-windows
  2. Run the project
  3. Join to an scheduled Teams meeting
  4. Press leave button

Environment

UWP Target version: Windows 10, version 2004 (10.0; Build 19041) Min version: Windows 10, version 1809 (10.0; Build 17763)

Microsoft Visual Studio Professional 2022 (64-bit) Version 17.0.4

ghost commented 2 years ago

Thank you for your feedback. This has been routed to the support team for assistance.

MughundhanRaveendran-MSFT commented 2 years ago

@juanmalm , Thanks for reporting, I am looking into this issue. I will get back to you with an update.

MughundhanRaveendran-MSFT commented 2 years ago

@juanmalm , Apologies for the delay in response. I followed the steps and I am able to leave the meeting without any issues. The await call_.HangUpAsync(new HangUpOptions()) works properly. Only difference is that I am using Visual Studio 2019.

How frequently are you getting this issue?

In the meantime, I will try to reproduce the issue in Visual Studio 2022 as well

MughundhanRaveendran-MSFT commented 2 years ago

@juanmalm , Following up to check if you got the chance to look into my previous comment. Please let me know

juanmalm commented 2 years ago

Hi @MughundhanRaveendran-MSFT. My issue is reproducible 100%. It happened previously with VS2019 (I already uninstalled it) and right now it is happening with VS2022. I forgot to mention in repro steps that I'm joining to a meeting which currently has a Teams user connected with a Teams client in a MacBook Pro. I will try to reproduce it connecting with a user on a Windows client.

Later I will attach a project in which I reproduce the issue. Also if you are able to provide me an email address, I can record a video showing the issue.

juanmalm commented 2 years ago

Here you have the code I'm using. I can share with you the project and a video showing the issue if you provide me an email address.

MainPage.xaml.cs

using Azure.Communication.Calling;
using Azure.Communication.Identity;
using Azure.WinRT.Communication;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace poc_teams_integration
{
    public sealed partial class MainPage : Page
    {
        private const string communicationServicesConnectionString = "USE A VALID CONNECTION STRING!!!!!!!!!!!!!";

        private CommunicationIdentityClient communicationIdentityClient;
        private Call call_;
        private CallClient call_client;
        private CallAgent call_agent;
        private DeviceManager deviceManager;
        private LocalVideoStream[] localVideoStream;
        private Authenticator authenticator;
        private string userToken;

        public MainPage()
        {
            InitializeComponent();
            InitCommunicationIdentityClient();
            InitCallClientAndDeviceManager();
            authenticator = new Authenticator();
        }

        private async void InitCallClientAndDeviceManager()
        {
            call_client = new CallClient();
            deviceManager = await call_client.GetDeviceManager();
        }

        private void InitCommunicationIdentityClient()
        {
            communicationIdentityClient = new CommunicationIdentityClient(communicationServicesConnectionString);
        }

        private async void JoinButton_ClickAsync(object sender, RoutedEventArgs e)
        {
            if (!await ValidateInput())
            {
                return;
            }

            await CreateCallAgent();

            await CreateLocalVideoStream();

            await JoinTeamsMeeting();
        }

        private async Task CreateCallAgent()
        {
            try
            {
                if (string.IsNullOrEmpty(userToken))
                {
                    var token = await authenticator.AcquireTokenAsync();

                    // https://docs.microsoft.com/en-us/azure/communication-services/quickstarts/manage-teams-identity?pivots=programming-language-csharp
                    var accessToken = await communicationIdentityClient.GetTokenForTeamsUserAsync(token);
                    userToken = accessToken.Value.Token;
                }

                if (call_agent is null)
                {
                    var token_credential = new CommunicationTokenCredential(userToken);
                    call_agent = await call_client.CreateCallAgent(token_credential, new CallAgentOptions());

                    call_agent.OnCallsUpdated += Agent_OnCallsUpdated;
                }
            }
            catch (Exception ex)
            {
                await new MessageDialog($"It was not possible to create call agent. Please check if token is valid:{Environment.NewLine}{ex.Message}").ShowAsync();
                return;
            }
        }

        private async Task CreateLocalVideoStream()
        {
            try
            {
                if (deviceManager.Cameras.Count > 0)
                {
                    var videoDeviceInfo = deviceManager.Cameras[0];
                    localVideoStream = new LocalVideoStream[1];
                    localVideoStream[0] = new LocalVideoStream(videoDeviceInfo);

                    var localUri = await localVideoStream[0].CreateBindingAsync();

                    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                        LocalVideo.Source = localUri;
                        LocalVideo.Play();
                    });

                }
            }
            catch (Exception ex)
            {
                await new MessageDialog($"Error creating local video stream:{Environment.NewLine}{ex.Message}").ShowAsync();
                return;
            }
        }

        private async Task JoinTeamsMeeting()
        {
            try
            {
                var joinCallOptions = new JoinCallOptions();
                joinCallOptions.VideoOptions = new VideoOptions(localVideoStream);
                var teamsMeetingLinkLocator = new TeamsMeetingLinkLocator(TeamsLinkTextBox.Text);
                call_ = await call_agent.JoinAsync(teamsMeetingLinkLocator, joinCallOptions);

                call_.OnStateChanged += Call_OnStateChangedAsync;
            }
            catch (Exception ex)
            {
                await new MessageDialog($"It was not possible to join the Teams meeting. Please check if Teams Link is valid:{Environment.NewLine}{ex.Message}").ShowAsync();
                return;
            }
        }

        private async void Call_OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            try
            {
                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    CallStatusTextBlock.Text = call_.State.ToString();
                });

                switch (((Call)sender).State)
                {
                    case CallState.Disconnected:
                        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                        {
                            LocalVideo.Stop();
                            LocalVideo.Source = null;
                            RemoteVideo.Stop();
                            RemoteVideo.Source = null;
                        });

                        localVideoStream[0].ReleaseBinding();
                        localVideoStream[0] = null;
                        break;
                    default:
                        Debug.WriteLine(((Call)sender).State);
                        break;
                }
            }
            catch (Exception ex)
            {
                await new MessageDialog($"Call_OnStateChangedAsync error:{Environment.NewLine}{ex.Message}").ShowAsync();
            }
        }

        private async void LeaveButton_ClickAsync(object sender, RoutedEventArgs e)
        {
            try
            {
                if (call_ is null)
                {
                    return;
                }

                await call_.StopVideo(localVideoStream[0]);
                await call_.HangUpAsync(new HangUpOptions());
            }
            catch (Exception ex)
            {
                await new MessageDialog($"It was not possible to leave the Teams meeting:{Environment.NewLine}{ex.Message}").ShowAsync();
            }
        }

        private async Task<bool> ValidateInput()
        {
            if (TeamsLinkTextBox.Text.Trim().Length == 0 || !TeamsLinkTextBox.Text.StartsWith("http"))
            {
                await new MessageDialog("Please enter Teams meeting link.").ShowAsync();
                return false;
            }

            return true;
        }

        private async void Agent_OnCallsUpdated(object sender, CallsUpdatedEventArgs args)
        {
            foreach (var call in args.AddedCalls)
            {
                foreach (var remoteParticipant in call.RemoteParticipants)
                {
                    await AddVideoStreams(remoteParticipant.VideoStreams);
                    remoteParticipant.OnVideoStreamsUpdated += async (s, a) => await AddVideoStreams(a.AddedRemoteVideoStreams);
                }
                call.OnRemoteParticipantsUpdated += Call_OnRemoteParticipantsUpdated;
                call.OnStateChanged += Call_OnStateChangedAsync;
            }
        }

        private async void Call_OnRemoteParticipantsUpdated(object sender, ParticipantsUpdatedEventArgs args)
        {
            foreach (var remoteParticipant in args.AddedParticipants)
            {
                await AddVideoStreams(remoteParticipant.VideoStreams);
                remoteParticipant.OnVideoStreamsUpdated += async (s, a) => await AddVideoStreams(a.AddedRemoteVideoStreams);
            }
        }

        private async Task AddVideoStreams(IReadOnlyList<RemoteVideoStream> streams)
        {

            foreach (var remoteVideoStream in streams)
            {
                var remoteUri = await remoteVideoStream.CreateBindingAsync();

                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    RemoteVideo.Source = remoteUri;
                    RemoteVideo.Play();
                });
                remoteVideoStream.Start();
            }
        }
    }
}

Authenticator.cs

using Microsoft.Identity.Client;
using System;
using System.Threading.Tasks;
using Windows.UI.Popups;

namespace poc_teams_integration
{
    internal class Authenticator
    {
        private IPublicClientApplication PCA;
        private string ClientID = "USE A VALID CLIENT ID!!!!!!!!!!!!!";
        readonly static string[] Scopes = { "https://auth.msft.communication.azure.com/Teams.ManageCalls" };

        public Authenticator()
        {
            PCA = PublicClientApplicationBuilder.Create(ClientID)
                .WithRedirectUri($"msal{ClientID}://auth")
                .Build();
        }

        public async Task<string> AcquireTokenAsync()
        {
            try
            {
                var authResult = await PCA.AcquireTokenInteractive(Scopes)
                    .ExecuteAsync();
                return authResult.AccessToken;
            }
            catch (Exception e)
            {
                await new MessageDialog($"Error adquiring token: {e.Message}").ShowAsync();
                return string.Empty;
            }
        }
    }
}
juanmalm commented 2 years ago

Hi @MughundhanRaveendran-MSFT . Any updates on this?

MughundhanRaveendran-MSFT commented 2 years ago

@juanmalm , Apologies for the delay. I was trying to run your code however I was unable to resolve Remotevideo and Localvideo properties. Could you please provide those as well?

juanmalm commented 2 years ago

Sure! My fault. Here you have the missing file.

MainPage.xaml

<Page x:Class="poc_teams_integration.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:poc_teams_integration" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" mc:Ignorable="d">

<StackPanel>
    <TextBox
        x:Name="TeamsLinkTextBox"
        Margin="10,10,10,10"
        PlaceholderText="Please enter the Teams meeting link."
        TextWrapping="Wrap" />
    <StackPanel
        HorizontalAlignment="Center"
        Orientation="Horizontal">
        <Button
            x:Name="JoinButton"
            Margin="10,10,10,10"
            Click="JoinButton_ClickAsync"
            Content="Join Teams Meeting" />
        <Button
            x:Name="LeaveButton"
            Margin="10,10,10,10"
            Click="LeaveButton_ClickAsync"
            Content="Leave Meeting" />
    </StackPanel>
    <TextBlock
        x:Name="CallStatusTextBlock"
        Margin="10,10,10,10"
        TextWrapping="Wrap" />
    <TextBlock
        x:Name="RecordingStatusTextBlock"
        Margin="10,10,10,10"
        TextWrapping="Wrap" />
    <Grid
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <MediaElement
            x:Name="LocalVideo"
            Grid.Column="0"
            HorizontalAlignment="Stretch"
            AutoPlay="True"
            Stretch="UniformToFill" />
        <MediaElement
            x:Name="RemoteVideo"
            Grid.Column="1"
            HorizontalAlignment="Stretch"
            AutoPlay="True"
            Stretch="UniformToFill" />
    </Grid>
</StackPanel>

MughundhanRaveendran-MSFT commented 2 years ago

@juanmalm , I am able to reproduce the issue by using your latest code. The issue only occurs when a user who has joined from Mac client is already present in the Teams call. I will have a discussion internally and get back to you.

Appreciate your patience on this issue.

ghost commented 2 years ago

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @acsdevx-msft.

Issue Details
### Library name and version Azure.Communication.Calling 1.0.0-beta.29 ### Describe the bug Following the official quick start guide: https://docs.microsoft.com/en-us/azure/communication-services/quickstarts/voice-video-calling/get-started-teams-interop?pivots=platform-windows HangUpAsync does not work properly. ### Expected behavior `await call_.HangUpAsync(new HangUpOptions());` works properly ### Actual behavior `await call_.HangUpAsync(new HangUpOptions());` never returns and the user is not disconnected properly from the call ### Reproduction Steps 1. Follow the official quick start guide: https://docs.microsoft.com/en-us/azure/communication-services/quickstarts/voice-video-calling/get-started-teams-interop?pivots=platform-windows 2. Run the project 3. Join to an scheduled Teams meeting 4. Press leave button ### Environment UWP Target version: Windows 10, version 2004 (10.0; Build 19041) Min version: Windows 10, version 1809 (10.0; Build 17763) Microsoft Visual Studio Professional 2022 (64-bit) Version 17.0.4
Author: juanmalm
Assignees: MughundhanRaveendran-MSFT
Labels: `Service Attention`, `Client`, `customer-reported`, `question`, `needs-team-attention`, `Communication`, `CXP Attention`
Milestone: -
juanmalm commented 2 years ago

Any updates?

navba-MSFT commented 2 years ago

@acsdevx-msft Could you please look into this issue and provide an update once you get a chance ?

jowang-msft commented 12 months ago

Azure.Communication.Calling 1.0.0-beta.29 was deprecated and we have released GA versions of the SDK at https://www.nuget.org/packages/Azure.Communication.Calling.WindowsClient#versions-body-tab. We have verified this scenario is working properly.

Closing this issue for now and please file new tickets for issues discovered in your tests with our new SDK. Thanks.