Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call Features Stream Util Implementation and Call client state for Together mode feature #5362

Merged
merged 13 commits into from
Nov 5, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"area": "feature",
"workstream": "togetherMode",
"comment": "Stream utils implementation for call feature streams. This also included the client state for together mode feature",
"packageName": "@azure/communication-react",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "prerelease",
"area": "feature",
"workstream": "togetherMode",
"comment": "Included a common util method that set call features renderer info",
"packageName": "@azure/communication-react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
84 changes: 55 additions & 29 deletions packages/calling-stateful-client/src/CallClientState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,45 +272,70 @@ export interface RaiseHandCallFeatureState {

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeCallFeature}. {@link StatefulCallClient} will
* automatically listen for raised hands on the call and update the state exposed by {@link StatefulCallClient} accordingly.
* @alpha
* @beta
*/
export interface TogetherModeCallFeatureState {
/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature.togetherModeStream}.
*/
stream: TogetherModeStreamState[];
export type CallFeatureStreamName = 'togetherMode';

/* @conditional-compile-remove(together-mode) */
/**
* @beta
*/
export interface CallFeatureStreamState extends RemoteVideoStreamState {
feature?: CallFeatureStreamName;
}

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeVideoStream}.
* @alpha
* State only version of {@link @azure/communication-calling#TogetherModeSeatingMap}.
* @beta
*
* Represents the seating position of a participant in Together Mode.
*/
export interface TogetherModeStreamState {
/**
* Proxy of {@link @azure/communication-calling#TogetherModeVideoStream.id}.
*/
id: number;
/**
* Proxy of {@link @azure/communication-calling#TogetherModeVideoStream.mediaStreamType}.
*/
mediaStreamType: MediaStreamType;
/**
* Proxy of {@link @azure/communication-calling#TogetherModeVideoStream.isReceiving}.
* @public
*/
isReceiving: boolean;
export interface TogetherModeSeatingPositionState {
// The top left offset from the top of the together mode view.
top: number;
// The left offset position from the left of the together mode view.
left: number;
// The width of the seating area
width: number;
// The height of the seating area.
height: number;
}

/* @conditional-compile-remove(together-mode) */
/**
* Represents the seating positions of participants in Together Mode.
*
* @beta
*/
export type TogetherModeParticipantSeatingState = Record<string, TogetherModeSeatingPositionState>;

/* @conditional-compile-remove(together-mode) */
/**
* Interface representing the streams in Together Mode.
*
* @beta
*/
export interface TogetherModeStreamsState {
mainVideoStream?: CallFeatureStreamState;
}

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeCallFeature}. {@link StatefulCallClient} will
* automatically listen for raised hands on the call and update the state exposed by {@link StatefulCallClient} accordingly.
* @beta
*/
export interface TogetherModeCallFeatureState {
isActive: boolean;
/**
* {@link VideoStreamRendererView} that is managed by createView/disposeView in {@link StatefulCallClient}
* API. This can be undefined if the stream has not yet been rendered and defined after createView creates the view.
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature.togetherModeStream}.
*/
view?: VideoStreamRendererViewState;
streams: TogetherModeStreamsState;
/**
* Proxy of {@link @azure/communication-calling#RemoteVideoStream.size}.
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature.TogetherModeSeatingMap}.
*/
streamSize?: { width: number; height: number };
seatingPositions: TogetherModeParticipantSeatingState;
}

/**
Expand Down Expand Up @@ -621,6 +646,7 @@ export interface CallState {
/* @conditional-compile-remove(together-mode) */
/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature}.
* @beta
*/
togetherMode: TogetherModeCallFeatureState;
/**
Expand Down
114 changes: 108 additions & 6 deletions packages/calling-stateful-client/src/CallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { TeamsCaptionsInfo } from '@azure/communication-calling';
import { CaptionsKind, CaptionsInfo as AcsCaptionsInfo } from '@azure/communication-calling';
import { EnvironmentInfo } from '@azure/communication-calling';
/* @conditional-compile-remove(together-mode) */
import { TogetherModeVideoStream } from '@azure/communication-calling';
import { TogetherModeVideoStream, TogetherModeSeatingMap } from '@azure/communication-calling';
import { AzureLogger, createClientLogger, getLogLevel } from '@azure/logger';
import { EventEmitter } from 'events';
import { enableMapSet, enablePatches, Patch, produce } from 'immer';
Expand Down Expand Up @@ -65,6 +65,8 @@ import { SpotlightedParticipant } from '@azure/communication-calling';
import { LocalRecordingInfo } from '@azure/communication-calling';
/* @conditional-compile-remove(local-recording-notification) */
import { RecordingInfo } from '@azure/communication-calling';
/* @conditional-compile-remove(together-mode) */
import { CallFeatureStreamState, TogetherModeSeatingPositionState } from './CallClientState';

enableMapSet();
// Needed to generate state diff for verbose logging.
Expand Down Expand Up @@ -455,11 +457,78 @@ export class CallContext {
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStream(callId: string, addedStream: TogetherModeVideoStream[]): void {
public setTogetherModeVideoStreams(
callId: string,
addedStreams: CallFeatureStreamState[],
removedStreams: CallFeatureStreamState[]
): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
for (const stream of removedStreams) {
if (stream.mediaStreamType === 'Video') {
call.togetherMode.streams.mainVideoStream = undefined;
call.togetherMode.isActive = false;
call.togetherMode.seatingPositions = {};
}
}

for (const newStream of addedStreams) {
// This should only be called by the subscriber and some properties are add by other components so if the
// stream already exists, only update the values that subscriber knows about.
const mainVideoStream = call.togetherMode.streams.mainVideoStream;
if (mainVideoStream && mainVideoStream.id === newStream.id) {
mainVideoStream.mediaStreamType = newStream.mediaStreamType;
mainVideoStream.isAvailable = newStream.isAvailable;
mainVideoStream.isReceiving = newStream.isReceiving;
} else {
call.togetherMode.streams.mainVideoStream = newStream;
}
call.togetherMode.isActive = true;
}
}
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStreamIsAvailable(callId: string, streamId: number, isAvailable: boolean): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
const stream = call.togetherMode.streams.mainVideoStream;
if (stream && stream?.id === streamId) {
stream.isReceiving = isAvailable;
}
}
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStreamIsReceiving(callId: string, streamId: number, isReceiving: boolean): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
const stream = call.togetherMode.streams.mainVideoStream;
if (stream && stream?.id === streamId) {
stream.isReceiving = isReceiving;
}
}
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStreamSize(
callId: string,
streamId: number,
size: { width: number; height: number }
): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
call.togetherMode = { stream: addedStream };
const stream = call.togetherMode.streams.mainVideoStream;
if (stream && stream?.id === streamId) {
stream.streamSize = size;
}
}
});
}
Expand All @@ -470,15 +539,29 @@ export class CallContext {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
for (const stream of removedStream) {
if (stream.mediaStreamType in call.togetherMode.stream) {
// Temporary lint fix: Remove the stream from the list
call.togetherMode.stream = [];
if (stream.mediaStreamType === 'Video') {
call.togetherMode.streams.mainVideoStream = undefined;
call.togetherMode.isActive = false;
}
}
}
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeSeatingCoordinates(callId: string, seatingMap: TogetherModeSeatingMap): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
const seatingPositions: Record<string, TogetherModeSeatingPositionState> = {};
for (const [userId, seatingPosition] of seatingMap.entries()) {
seatingPositions[userId] = seatingPosition;
}
call.togetherMode.seatingPositions = seatingPositions;
}
});
}

public setCallRaisedHands(callId: string, raisedHands: RaisedHand[]): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
Expand Down Expand Up @@ -718,6 +801,25 @@ export class CallContext {
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStreamRendererView(
callId: string,
togetherModeStreamType: string,
view: VideoStreamRendererViewState | undefined
): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
if (togetherModeStreamType === 'Video') {
const togetherModeStream = call.togetherMode.streams.mainVideoStream;
if (togetherModeStream) {
togetherModeStream.view = view;
}
}
}
});
}

public setParticipantState(callId: string, participantKey: string, state: RemoteParticipantStatus): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
Expand Down
Loading
Loading