Skip to content

Commit

Permalink
Restore interface of ICallAdapter and fix the unit tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
GillesTourreau committed Jun 11, 2024
1 parent 7765473 commit 08d3b73
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 97 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Azure.Communication.Identity" Version="1.3.1" />
<PackageVersion Include="bunit" Version="1.28.9" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
Expand Down
56 changes: 28 additions & 28 deletions docs/Components/CallComposite.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,29 @@ To use the component:
builder.Services.AddCalling();
```

- Inject the `ICallingService` dependency a use it to create an instance of `CallAdapter`.
- Add the `CallComposite` component and bind the `Adapter` property with the `CallAdapter` previously created.
- Inject the `ICallingService` dependency a use it to create an instance of `ICallAdapter`.
- Add the `CallComposite` component and bind the `Adapter` property with the `ICallAdapter` previously created.

Example:
```razor
@inject ICallingService CallingService
<CallComposite Adapter="this.callAdapter" />
<CallComposite Adapter="this.callAdapter"
CameraButton="true"
DevicesButton="true"
EndCallButton="true"
MicrophoneButton="true"
MoreButton="@this.moreButton"
ParticipantsButton="true"
PeopleButton="true"
RaiseHandButton="true"
ScreenShareButton="true" />
<button @onclick="this.LoadAsync">Load</button>
@code
{
private CallAdapter? callAdapter;
private ICallAdapter? callAdapter;
private async Task LoadAsync()
{
Expand All @@ -37,21 +46,6 @@ Example:
new TokenCredential("The ACS token"))
{
DisplayName = "John doe",
Options =
{
CallControls =
{
CameraButton = true,
DevicesButton = true,
EndCallButton = true,
MicrophoneButton = true,
MoreButton = true,
ParticipantsButton = true,
PeopleButton = true,
RaiseHandButton = true,
ScreenShareButton = true,
}
}
};
this.callAdapter = await this.CallingService.CreateAdapterAsync(args);
Expand All @@ -63,13 +57,13 @@ Example:
}
```

You can manage the `CallComposite` component using the `CallAdapter` associated. For example, you can
You can manage the `CallComposite` component using the `ICallAdapter` associated. For example, you can
subscribe to different events using a simple delegate.

### Join/Leave the call
After the `CallAdapter` has been associated to the `CallComposite` component
After the `ICallAdapter` has been associated to the `CallComposite` component
(or after leaving a call), it is possible to join the call
by calling the `JoinCall()` method on the `CallAdapter`.
by calling the `JoinCall()` method on the `ICallAdapter`.
You can define if the camera and/or the microphone have to be activated.

```csharp
Expand All @@ -85,18 +79,18 @@ private async Task JoinCallAsync()
}
```

To leave the call, call the `LeaveCallAsync()` method on the `CallAdapter`. This method
To leave the call, call the `LeaveCallAsync()` method on the `ICallAdapter`. This method
take a boolean parameter `forEveryone` to remove all participants when leaving.

### Start/Stop screen share
To start sharing the screen on the current device, call the `StartScreenShare()` method on the `CallAdapter`.
To start sharing the screen on the current device, call the `StartScreenShare()` method on the `ICallAdapter`.

To stop sharing the screen on the current device, call the `StopScreenShare()` method on the `CallAdapter`.
To stop sharing the screen on the current device, call the `StopScreenShare()` method on the `ICallAdapter`.

### Mute/Unmute
To mute the microphone of the current user, call the `MuteAsync()` method on the `CallAdapter`.
To mute the microphone of the current user, call the `MuteAsync()` method on the `ICallAdapter`.

To unmute the microphone of the current user, call the `UnmuteAsync()` method on the `CallAdapter`.
To unmute the microphone of the current user, call the `UnmuteAsync()` method on the `ICallAdapter`.

### Events
You can subsribe to the following asynchronous events using a standard delegate method:
Expand All @@ -107,4 +101,10 @@ You can subsribe to the following asynchronous events using a standard delegate

### Dispose the resources
It is recommanded to implement the `IAsyncDisposable` method in the class which create
and manage the `CallAdapter` instance.
and manage the `ICallAdapter` instance.

### Unit tests
The `ICallingService.CreateAdapterAsync()` method returns an instance of `ICallAdapter`
implemented by the `CallAdapter`. By returning interface implementation, developers
have no excuses to perform some units in their code by mocking the `ICallingService`
and `ICallAdapter` interfaces.
56 changes: 11 additions & 45 deletions src/Communication.UI.Blazor/Calling/CallAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace PosInformatique.Azure.Communication.UI.Blazor
/// <summary>
/// An adapter interface specific for Azure Communication identity which extends <see cref="CommonCallAdapter"/>.
/// </summary>
public class CallAdapter : CommonCallAdapter, IDisposable, IAsyncDisposable
public class CallAdapter : CommonCallAdapter, ICallAdapter, IDisposable
{
private CallbackEvent? callbackEvent;

Expand All @@ -26,97 +26,63 @@ internal CallAdapter(IJSObjectReference module)
this.callbackEvent = new CallbackEvent(this);
}

/// <summary>
/// Occurs when the call is ended.
/// </summary>
/// <inheritdoc />
public event AsyncEventHandler<CallEndedEvent>? OnCallEnded;

/// <summary>
/// Occurs when the microphone is muted/unmuted on a participant.
/// </summary>
/// <inheritdoc />
public event AsyncEventHandler<MicrophoneMuteChangedEvent>? OnMicrophoneMuteChanged;

/// <summary>
/// Occurs when a participant join the call.
/// </summary>
/// <inheritdoc />
public event AsyncEventHandler<RemoteParticipantJoinedEvent>? OnParticipantJoined;

/// <summary>
/// Occurs when a participant leave the call.
/// </summary>
/// <inheritdoc />
public event AsyncEventHandler<RemoteParticipantLeftEvent>? OnParticipantLeft;

internal Guid Id { get; }

internal IJSObjectReference Module { get; }

/// <summary>
/// Join an existing call.
/// </summary>
/// <param name="options">Options of the call.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task JoinCallAsync(JoinCallOptions options)
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);

await this.Module.InvokeVoidAsync("adapterJoinCall", this.Id, options);
}

/// <summary>
/// Leave the call.
/// </summary>
/// <param name="forEveryone">Whether to remove all participants when leaving.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task LeaveCallAsync(bool forEveryone)
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);

await this.Module.InvokeVoidAsync("adapterLeaveCall", this.Id, forEveryone);
}

/// <summary>
/// Mute the current user during the call or disable microphone locally.
/// </summary>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task MuteAsync()
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);

await this.Module.InvokeVoidAsync("adapterMute", this.Id);
}

/// <summary>
/// Unmute the current user during the call or enable microphone locally.
/// </summary>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task UnmuteAsync()
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);

await this.Module.InvokeVoidAsync("adapterUnmute", this.Id);
}

/// <summary>
/// Start sharing the screen during a call.
/// </summary>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task StartScreenShareAsync()
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);

await this.Module.InvokeVoidAsync("adapterStartScreenShare", this.Id);
}

/// <summary>
/// Stop sharing the screen.
/// </summary>
/// <returns>A <see cref="Task"/> that represents the asynchronous invocation.</returns>
/// <exception cref="ObjectDisposedException">If the <see cref="CallAdapter"/> has already been disposed.</exception>
/// <inheritdoc />
public async Task StopScreenShareAsync()
{
ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);
Expand Down
29 changes: 18 additions & 11 deletions src/Communication.UI.Blazor/Calling/CallComposite.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace PosInformatique.Azure.Communication.UI.Blazor
/// </summary>
public sealed partial class CallComposite
{
private static readonly CallControlOptions DefaultOptions = new CallControlOptions();

private ElementReference callContainer;

/// <summary>
Expand All @@ -22,85 +24,90 @@ public sealed partial class CallComposite
/// </summary>
[Parameter]
[EditorRequired]
public CallAdapter? Adapter { get; set; }
public ICallAdapter? Adapter { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to
/// show or hide Camera Button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool CameraButton { get; set; }
public bool CameraButton { get; set; } = DefaultOptions.CameraButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show or hide Devices button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool DevicesButton { get; set; }
public bool DevicesButton { get; set; } = DefaultOptions.DevicesButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show or hide EndCall button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool EndCallButton { get; set; }
public bool EndCallButton { get; set; } = DefaultOptions.EndCallButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show or hide Microphone button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool MicrophoneButton { get; set; }
public bool MicrophoneButton { get; set; } = DefaultOptions.MicrophoneButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show, hide or disable the more button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool MoreButton { get; set; }
public bool MoreButton { get; set; } = DefaultOptions.MoreButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show, hide or disable participants button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool ParticipantsButton { get; set; }
public bool ParticipantsButton { get; set; } = DefaultOptions.ParticipantsButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show, hide or disable the people button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool PeopleButton { get; set; }
public bool PeopleButton { get; set; } = DefaultOptions.PeopleButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show, hide or disable the raise hand button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool RaiseHandButton { get; set; }
public bool RaiseHandButton { get; set; } = DefaultOptions.RaiseHandButton;

/// <summary>
/// Gets or sets a value indicating whether to
/// show, hide or disable the screen share button during a call.
/// Default value: <see cref="true"/>.
/// </summary>
[Parameter]
public bool ScreenShareButton { get; set; }
public bool ScreenShareButton { get; set; } = DefaultOptions.ScreenShareButton;

/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (this.Adapter is not null)
{
if (this.Adapter is not CallAdapter adapter)
{
throw new InvalidOperationException("The Adapter property must an instance of the CallAdapter class.");
}

var options = new CallControlOptions()
{
CameraButton = this.CameraButton,
Expand All @@ -114,7 +121,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
ScreenShareButton = this.ScreenShareButton,
};

await this.Adapter.Module.InvokeVoidAsync("initializeControl", this.callContainer, this.Adapter.Id, options);
await adapter.Module.InvokeVoidAsync("initializeControl", this.callContainer, adapter.Id, options);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Communication.UI.Blazor/Calling/CallingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async ValueTask DisposeAsync()
}

/// <inheritdoc />
public async Task<CallAdapter> CreateAdapterAsync(CallAdapterArgs args)
public async Task<ICallAdapter> CreateAdapterAsync(CallAdapterArgs args)
{
await this.EnsureModuleLoadAsync();

Expand Down
Loading

0 comments on commit 08d3b73

Please sign in to comment.