diff --git a/docs/Components/CallComposite.md b/docs/Components/CallComposite.md
index 25fcfc8..391d7b2 100644
--- a/docs/Components/CallComposite.md
+++ b/docs/Components/CallComposite.md
@@ -105,12 +105,19 @@ To raise the hand during a call, call the `RaiseHandAsync()` method.
To lower the hand during a call, call the `LowerHandAsync()` method.
+### Gets the state of the CallAdapter
+To retrieve the current state of the `CallAdapter`, call the `GetStateAsync()` method.
+
+You can also subscribe to the `OnStateChanged` event which is raised when the state of the `CallAdapter` is changed.
+
### Events
You can subsribe to the following asynchronous events using a standard delegate method:
- `OnCallEnded`: Occurs then the call is ended.
- `OnMicrophoneMuteChanged`: Occurs when the microphone of a participant is mute/unmute.
- `OnParticipantJoined`: Occurs when a participant join the call.
- `OnParticipantLeft`: Occurs when a participant leave the call.
+- `OnStateChanged`: Occurs when the `CallAdapterState` has been changed.
+ You can also call manually the `GetStateAsync()` method to retrieve the last state of the `CallAdapter`
### Dispose the resources
It is recommanded to implement the `IAsyncDisposable` method in the class which create
diff --git a/docs/PortedApi.md b/docs/PortedApi.md
index 0f0015e..0446ae5 100644
--- a/docs/PortedApi.md
+++ b/docs/PortedApi.md
@@ -10,9 +10,9 @@ which has been ported to this library.
| Method | Available | Remarks |
|-------------------------------|------------|------------------------------------------------------|
-| onStateChange | TODO | |
-| offStateChange | TODO | |
-| getState | TODO | |
+| onStateChange | **Done** | |
+| offStateChange | **Done** | |
+| getState | Partially | Need to fully wrap the CallAdapterState object |
| dispose | **Done** | |
| holdCall (Beta) | No | Currently in beta in Microsoft library |
| joinCall (Deprecated) | No | Deprecated |
@@ -68,3 +68,29 @@ which has been ported to this library.
| transferAccepted | TODO | |
| capabilitiesChanged | TODO | |
| spotlightChanged | TODO | |
+
+
+### CallAdapterState
+| Name | Available | Remarks |
+|--------------------------------------|-----------|---------|
+| userId | **Done** | |
+| displayName | **Done** | |
+| call | TODO | |
+| targetCallees | TODO | |
+| devices | TODO | |
+| endedCall | TODO | |
+| isTeamsCall | **Done** | |
+| isRoomsCall | **Done** | |
+| latestErrors | TODO | |
+| alternateCallerId | TODO | |
+| environmentInfo | TODO | |
+| cameraStatus | **Done** | |
+| videoBackgroundImages | TODO | |
+| onResolveVideoEffectDependency | TODO | |
+| selectedVideoBackgroundEffect | TODO | |
+| acceptedTransferCallState | TODO | |
+| hideAttendeeNames | TODO | |
+| sounds | TODO | |
+| isLocalPreviewMicrophoneEnabled | **Done** | |
+| page | **Done** | |
+| unsupportedBrowserVersionsAllowed | TODO | |
\ No newline at end of file
diff --git a/src/Communication.UI.Blazor/Calling/CallAdapter.cs b/src/Communication.UI.Blazor/Calling/CallAdapter.cs
index 1e6fe04..42aeca8 100644
--- a/src/Communication.UI.Blazor/Calling/CallAdapter.cs
+++ b/src/Communication.UI.Blazor/Calling/CallAdapter.cs
@@ -38,10 +38,42 @@ internal CallAdapter(IJSObjectReference module)
///
public event AsyncEventHandler? OnParticipantLeft;
+ ///
+ public event AsyncEventHandler? OnStateChanged;
+
internal Guid Id { get; }
internal IJSObjectReference Module { get; }
+ ///
+ public void Dispose()
+ {
+ if (this.callbackEvent != null)
+ {
+ this.callbackEvent.Dispose();
+ this.callbackEvent = null;
+ }
+ }
+
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (this.callbackEvent != null)
+ {
+ await this.Module.InvokeVoidAsync("dispose", this.Id);
+ }
+
+ this.Dispose();
+ }
+
+ ///
+ public async Task GetStateAsync()
+ {
+ ObjectDisposedException.ThrowIf(this.callbackEvent is null, this);
+
+ return await this.Module.InvokeAsync("adapterGetState", this.Id);
+ }
+
///
public async Task JoinCallAsync(JoinCallOptions options)
{
@@ -130,27 +162,6 @@ public async Task UnmuteAsync()
await this.Module.InvokeVoidAsync("adapterUnmute", this.Id);
}
- ///
- public async ValueTask DisposeAsync()
- {
- if (this.callbackEvent != null)
- {
- await this.Module.InvokeVoidAsync("dispose", this.Id);
- }
-
- this.Dispose();
- }
-
- ///
- public void Dispose()
- {
- if (this.callbackEvent != null)
- {
- this.callbackEvent.Dispose();
- this.callbackEvent = null;
- }
- }
-
internal async Task InitializeAsync(CallAdapterArgs args)
{
await this.Module.InvokeVoidAsync("createCallAdapter", this.Id, args, this.callbackEvent!.Reference);
@@ -218,6 +229,15 @@ public async Task OnParticipantsLeftAsync(RemoteParticipant[] removed)
}
}
}
+
+ [JSInvokable]
+ public async Task OnStateChangedAsync(CallAdapterState state)
+ {
+ if (this.owner.OnStateChanged is not null)
+ {
+ await this.owner.OnStateChanged(new StateChangedEvent(state));
+ }
+ }
}
}
}
diff --git a/src/Communication.UI.Blazor/Calling/CallAdapterState.cs b/src/Communication.UI.Blazor/Calling/CallAdapterState.cs
new file mode 100644
index 0000000..7da7637
--- /dev/null
+++ b/src/Communication.UI.Blazor/Calling/CallAdapterState.cs
@@ -0,0 +1,83 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) P.O.S Informatique. All rights reserved.
+//
+//-----------------------------------------------------------------------
+
+namespace PosInformatique.Azure.Communication.UI.Blazor
+{
+ using System.Text.Json.Serialization;
+
+ ///
+ /// Represents the current state.
+ ///
+ public class CallAdapterState
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The of the current user.
+ public CallAdapterState(CommunicationUserKind userId)
+ {
+ this.UserId = userId;
+ }
+
+ ///
+ /// Gets the of the current user.
+ ///
+ [JsonPropertyName("userId")]
+ [JsonPropertyOrder(1)]
+ public CommunicationUserKind UserId { get; }
+
+ ///
+ /// Gets the display name of the current user.
+ ///
+ [JsonPropertyName("displayName")]
+ [JsonPropertyOrder(2)]
+ [JsonInclude]
+ public string? DisplayName { get; init; }
+
+ ///
+ /// Gets a value indicating whether if the current call is Teams call.
+ ///
+ [JsonPropertyName("isTeamsCall")]
+ [JsonPropertyOrder(7)]
+ [JsonInclude]
+ public bool IsTeamsCall { get; init; }
+
+ ///
+ /// Gets a value indicating whether if the call is a rooms call.
+ ///
+ [JsonPropertyName("isRoomsCall")]
+ [JsonPropertyOrder(8)]
+ [JsonInclude]
+ public bool IsRoomsCall { get; init; }
+
+ ///
+ /// Gets a value indicating whether the local participant's camera is on.
+ /// To be used when creating a custom control bar with the CallComposite.
+ ///
+ [JsonPropertyName("cameraStatus")]
+ [JsonPropertyOrder(12)]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ [JsonInclude]
+ public CameraStatus? CameraStatus { get; init; }
+
+ ///
+ /// Gets a value indicating whether if the microphone is enabled.
+ ///
+ [JsonPropertyName("isLocalPreviewMicrophoneEnabled")]
+ [JsonPropertyOrder(50)]
+ [JsonInclude]
+ public bool IsLocalPreviewMicrophoneEnabled { get; init; }
+
+ ///
+ /// Gets the current page display on the .
+ ///
+ [JsonPropertyName("page")]
+ [JsonPropertyOrder(51)]
+ [JsonConverter(typeof(JsonCamelCaseStringEnumConverter))]
+ [JsonInclude]
+ public CallCompositePage Page { get; init; }
+ }
+}
diff --git a/src/Communication.UI.Blazor/Calling/CallComposite.razor.js b/src/Communication.UI.Blazor/Calling/CallComposite.razor.js
index 978e292..da2c060 100644
--- a/src/Communication.UI.Blazor/Calling/CallComposite.razor.js
+++ b/src/Communication.UI.Blazor/Calling/CallComposite.razor.js
@@ -36,6 +36,11 @@ export async function createCallAdapter(id, args, eventCallback) {
return eventCallback.invokeMethodAsync('OnParticipantsLeftAsync', event.removed.map(createRemoteParticipant));
});
+ adapter.onStateChange((state) => {
+ console.log(state);
+ return eventCallback.invokeMethodAsync('OnStateChangedAsync', createState(state));
+ });
+
registerAdapter(id, adapter);
}
@@ -48,6 +53,13 @@ export function initializeControl(divElement, adapterId, callControls) {
createRoot(divElement).render(element);
}
+export function adapterGetState(id) {
+
+ const adapter = getAdapter(id);
+
+ return createState(adapter.getState());
+}
+
export function adapterJoinCall(id, options) {
const adapter = getAdapter(id);
@@ -169,6 +181,18 @@ function createRemoteParticipant(remoteParticipant) {
};
}
+function createState(state) {
+ return {
+ cameraStatus: state.cameraStatus,
+ displayName: state.displayName,
+ isLocalPreviewMicrophoneEnabled: state.isLocalPreviewMicrophoneEnabled,
+ isRoomsCall: state.isRoomsCall,
+ isTeamsCall: state.isTeamsCall,
+ page: state.page,
+ userId: state.userId,
+ };
+}
+
function createVideoDevice(videoDevice) {
return {
name: videoDevice.name,
diff --git a/src/Communication.UI.Blazor/Calling/CallCompositePage.cs b/src/Communication.UI.Blazor/Calling/CallCompositePage.cs
new file mode 100644
index 0000000..157e113
--- /dev/null
+++ b/src/Communication.UI.Blazor/Calling/CallCompositePage.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) P.O.S Informatique. All rights reserved.
+//
+//-----------------------------------------------------------------------
+
+namespace PosInformatique.Azure.Communication.UI.Blazor
+{
+ ///
+ /// Major UI screens shown in the .
+ ///
+ public enum CallCompositePage
+ {
+ ///
+ /// The teams meeting is denied.
+ ///
+ AccessDeniedTeamsMeeting,
+
+ ///
+ /// Call is ongoing.
+ ///
+ Call,
+
+ ///
+ /// Configuration step page.
+ ///
+ Configuration,
+
+ ///
+ /// The call is currently hold.
+ ///
+ Hold,
+
+ ///
+ /// The join call has been failed to network issues.
+ ///
+ JoinCallFailedDueToNoNetwork,
+
+ ///
+ /// The user has left the call.
+ ///
+ LeftCall,
+
+ ///
+ /// The user is currently leaving the call.
+ ///
+ Leaving,
+
+ ///
+ /// The user is waiting in the lobby.
+ ///
+ Lobby,
+
+ ///
+ /// The user has been removed from the call.
+ ///
+ RemovedFromCall,
+
+ ///
+ /// The can not be loaded because the current environment is not supported.
+ ///
+ UnsupportedEnvironment,
+
+ ///
+ /// Transferring the current call.
+ ///
+ Transferring,
+ }
+}
diff --git a/src/Communication.UI.Blazor/Calling/CameraStatus.cs b/src/Communication.UI.Blazor/Calling/CameraStatus.cs
new file mode 100644
index 0000000..0d6acf8
--- /dev/null
+++ b/src/Communication.UI.Blazor/Calling/CameraStatus.cs
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) P.O.S Informatique. All rights reserved.
+//
+//-----------------------------------------------------------------------
+
+namespace PosInformatique.Azure.Communication.UI.Blazor
+{
+ ///
+ /// Represents the camera status.
+ ///
+ public enum CameraStatus
+ {
+ ///
+ /// The camera is off.
+ ///
+ Off,
+
+ ///
+ /// The camera is on.
+ ///
+ On,
+ }
+}
diff --git a/src/Communication.UI.Blazor/Calling/CommunicationUserIdentifier.cs b/src/Communication.UI.Blazor/Calling/CommunicationUserIdentifier.cs
index 57cea4c..2e17db9 100644
--- a/src/Communication.UI.Blazor/Calling/CommunicationUserIdentifier.cs
+++ b/src/Communication.UI.Blazor/Calling/CommunicationUserIdentifier.cs
@@ -9,7 +9,7 @@ namespace PosInformatique.Azure.Communication.UI.Blazor
using System.Text.Json.Serialization;
///
- /// Represents an Azure Communication user.
+ /// Represents an Azure Communication Services user.
///
[JsonDerivedType(typeof(CommunicationUserKind), "communicationUser")]
[JsonPolymorphic(TypeDiscriminatorPropertyName = "kind")]
@@ -18,17 +18,26 @@ public abstract class CommunicationUserIdentifier
///
/// Initializes a new instance of the class.
///
- /// Id of the CommunicationUser as returned from the Communication Service.
+ /// Id of the CommunicationUser as returned from the Communication Services.
protected CommunicationUserIdentifier(string communicationUserId)
{
this.CommunicationUserId = communicationUserId;
}
///
- /// Gets the id of the CommunicationUser as returned from the Communication Service.
+ /// Gets the id of the CommunicationUser as returned from the Communication Servicse.
///
[JsonPropertyName("communicationUserId")]
[JsonPropertyOrder(1)]
public string CommunicationUserId { get; }
+
+ ///
+ /// Returns the id of the CommunicationUser as returned from the Communication Services.
+ ///
+ /// the id of the CommunicationUser as returned from the Communication Services.
+ public override string ToString()
+ {
+ return this.CommunicationUserId;
+ }
}
}
diff --git a/src/Communication.UI.Blazor/Calling/Events/StateChangedEvent.cs b/src/Communication.UI.Blazor/Calling/Events/StateChangedEvent.cs
new file mode 100644
index 0000000..f62c8c7
--- /dev/null
+++ b/src/Communication.UI.Blazor/Calling/Events/StateChangedEvent.cs
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) P.O.S Informatique. All rights reserved.
+//
+//-----------------------------------------------------------------------
+
+namespace PosInformatique.Azure.Communication.UI.Blazor
+{
+ ///
+ /// Contains information when the state of the has been changed.
+ ///
+ public class StateChangedEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// New state of the .
+ public StateChangedEvent(CallAdapterState state)
+ {
+ this.State = state;
+ }
+
+ ///
+ /// Gets the new state of the .
+ ///
+ public CallAdapterState State { get; }
+ }
+}
diff --git a/src/Communication.UI.Blazor/Calling/ICallAdapter.cs b/src/Communication.UI.Blazor/Calling/ICallAdapter.cs
index eaa9ed2..9c221fb 100644
--- a/src/Communication.UI.Blazor/Calling/ICallAdapter.cs
+++ b/src/Communication.UI.Blazor/Calling/ICallAdapter.cs
@@ -33,6 +33,18 @@ public interface ICallAdapter : IAsyncDisposable
///
event AsyncEventHandler? OnParticipantLeft;
+ ///
+ /// Occurs when the state of the has been changed.
+ ///
+ event AsyncEventHandler? OnStateChanged;
+
+ ///
+ /// Get the current state of the .
+ ///
+ /// A that represents the asynchronous invocation which contains the .
+ /// If the has already been disposed.
+ Task GetStateAsync();
+
///
/// Join an existing call.
///
diff --git a/src/Communication.UI.Blazor/Communication.UI.Blazor.csproj b/src/Communication.UI.Blazor/Communication.UI.Blazor.csproj
index e5062de..b4343ed 100644
--- a/src/Communication.UI.Blazor/Communication.UI.Blazor.csproj
+++ b/src/Communication.UI.Blazor/Communication.UI.Blazor.csproj
@@ -13,11 +13,13 @@
1.2.0
- Add the following APIs in the CallAdapter:
+ - GetStateAsync()
+ - LowerHandAsync()
+ - OnStateChanged event.
+ - RaiseHandAsync()
- QueryCamerasAsync()
- QueryMicrophonesAsync()
- QuerySpeakersAsync()
- - LowerHandAsync()
- - RaiseHandAsync()
1.1.0
- Refactoring to separate the CallAdapter and the CallComposite to reflect the architecture of the Communication UI Library.
diff --git a/src/Communication.UI.Blazor/JsonCamelCaseStringEnumConverter.cs b/src/Communication.UI.Blazor/JsonCamelCaseStringEnumConverter.cs
new file mode 100644
index 0000000..e2b6f7b
--- /dev/null
+++ b/src/Communication.UI.Blazor/JsonCamelCaseStringEnumConverter.cs
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) P.O.S Informatique. All rights reserved.
+//
+//-----------------------------------------------------------------------
+
+namespace PosInformatique.Azure.Communication.UI.Blazor
+{
+ using System.Text.Json;
+ using System.Text.Json.Serialization;
+
+ internal class JsonCamelCaseStringEnumConverter : JsonStringEnumConverter
+ {
+ public JsonCamelCaseStringEnumConverter()
+ : base(JsonNamingPolicy.CamelCase)
+ {
+ }
+ }
+}
diff --git a/tests/Communication.UI.Blazor.Demo/Pages/Home.razor b/tests/Communication.UI.Blazor.Demo/Pages/Home.razor
index 36920e3..b1962cb 100644
--- a/tests/Communication.UI.Blazor.Demo/Pages/Home.razor
+++ b/tests/Communication.UI.Blazor.Demo/Pages/Home.razor
@@ -63,6 +63,8 @@
+
+