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

Implementation of call feature streams Support #5241

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"area": "feature",
"workstream": "togetherMode",
"comment": "Implemented support logic for call feature streams. This functionality will facilitate the creation of call feature streams that are independent of any participant in the call or the device manager",
"packageName": "@azure/communication-react",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { Features } from '@azure/communication-calling';
import { TeamsCaptions } from '@azure/communication-calling';
import { Reaction } from '@azure/communication-calling';
import { _ComponentCallingHandlers } from './createHandlers';
/* @conditional-compile-remove(together-mode) */
import { TogetherModeStreamViewResult } from '@internal/react-components/dist/dist-esm/types/TogetherModeTypes';

/**
* Object containing all the handlers required for calling components.
Expand Down Expand Up @@ -106,6 +108,20 @@ export interface CommonCallingHandlers {
onMuteParticipant: (userId: string) => Promise<void>;
/* @conditional-compile-remove(soft-mute) */
onMuteAllRemoteParticipants: () => Promise<void>;
/* @conditional-compile-remove(together-mode) */
/**
* Call back to create a view for together mode
*
* @beta
*/
onCreateTogetherModeStreamView: (options?: VideoStreamOptions) => Promise<void | TogetherModeStreamViewResult>;
/* @conditional-compile-remove(together-mode) */
/**
* Call back to dispose together mode views
*
* @beta
*/
onDisposeTogetherModeStreamViews: () => Promise<void>;
}

/**
Expand Down Expand Up @@ -715,6 +731,59 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
}
: undefined;

/* @conditional-compile-remove(together-mode) */
const onCreateTogetherModeStreamView = async (
options = { scalingMode: 'Fit', isMirrored: true } as VideoStreamOptions
): Promise<void | TogetherModeStreamViewResult> => {
if (!call) {
return;
}
const callState = callClient.getState().calls[call.id];
if (!callState) {
return;
}
const togetherModeStreams = callState.togetherMode.streams;
const togetherModeCreateViewResult: TogetherModeStreamViewResult = {};
if (!togetherModeStreams.size) {
const togetherModeFeature = call?.feature(Features.TogetherMode);
await togetherModeFeature?.start();
} else {
for (const stream of togetherModeStreams) {
if (!stream[1].view) {
const createViewResult = await callClient.createCallFeatureView(call.id, stream[1], options);
// SDK currently only supports 1 Video media stream type
if (stream[1].mediaStreamType === 'Video') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can tell from the Together mode feature which stream is which? I feel that we might be at the mercy here of which stream is first for the positions in the array. We need to make sure that when we are assigning the stream to the MainVideoView position that it is indeed the main video and not the screenshare view.

togetherModeCreateViewResult.mainVideoView = createViewResult?.view
? { view: createViewResult?.view }
: undefined;
}
}
}
}
return togetherModeCreateViewResult;
};
/* @conditional-compile-remove(together-mode) */
const onDisposeTogetherModeStreamViews = async (): Promise<void> => {
if (!call) {
return;
}
const callState = callClient.getState().calls[call.id];
if (!callState) {
throw new Error(`Call Not Found: ${call.id}`);
}

const togetherModeStreams = callState.togetherMode.streams;

if (!togetherModeStreams.size) {
return;
}

for (const stream of togetherModeStreams) {
if (stream[1].view) {
callClient.disposeCallFeatureView(call.id, stream[1]);
}
}
};
return {
onHangUp,
onToggleHold,
Expand Down Expand Up @@ -768,7 +837,11 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
/* @conditional-compile-remove(soft-mute) */
onMuteAllRemoteParticipants,
onAcceptCall: notImplemented,
onRejectCall: notImplemented
onRejectCall: notImplemented,
/* @conditional-compile-remove(together-mode) */
onCreateTogetherModeStreamView,
/* @conditional-compile-remove(together-mode) */
onDisposeTogetherModeStreamViews
};
}
);
43 changes: 39 additions & 4 deletions packages/calling-stateful-client/src/CallClientState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,39 @@ export interface RaiseHandCallFeatureState {
/**
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit with these tags in the UI lib we don't have a use for Alpha tags so anything not ready to go public just hit them with the @beta tag from the start

*/
export interface TogetherModeCallFeatureState {
/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature.togetherModeStream}.
*/
stream: TogetherModeStreamState[];
streams: Map<string, TogetherModeStreamState>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way that we can assign this as a object like { primaryStream: TogetherModeStreamState, secondary: TogetherModeStreamState }

If this is a map Contoso won't know what the keys are defined as when trying to access this state.

Same applies with the seatingCoordinates as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, like we discussed on previously Chuk, having an object is much more simple for contoso to tell what is available and what is not. Especially if we are keeping this under the TogeterModeCallFeatureState

/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature.TogetherModeSeatingMap}.
*/
seatingCoordinates: Map<string, TogetherModeSeatingCoordinatesState>;
}

/* @conditional-compile-remove(together-mode) */
/**
* @beta
*/
export type CallFeatureStreamName = 'togetherMode';

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

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeVideoStream}.
* @alpha
* @beta
*/
export interface TogetherModeStreamState {
export interface TogetherModeStreamState extends CallFeatureStreamState {
/**
* Proxy of {@link @azure/communication-calling#TogetherModeVideoStream.id}.
*/
Expand All @@ -317,6 +335,22 @@ export interface TogetherModeStreamState {
streamSize?: { width: number; height: number };
}

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeSeatingMap}.
* @beta
*/
export interface TogetherModeSeatingCoordinatesState {
// the y coordinate of the participant seating position in the together mode stream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we denote here which corner of the bounding box this will be?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing top left

top: number;
// the x coordinate of the participant seating position in the together mode stream
left: number;
// the width of the participant in the together mode stream
width: number;
// the height of the participant in the together mode stream
height: number;
}

/**
* State only version of {@link @azure/communication-calling#PPTLiveCallFeature}. {@link StatefulCallClient} will
* automatically listen for pptLive on the call and update the state exposed by {@link StatefulCallClient} accordingly.
Expand Down Expand Up @@ -625,6 +659,7 @@ export interface CallState {
/* @conditional-compile-remove(together-mode) */
/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature}.
* @beta
*/
togetherMode: TogetherModeCallFeatureState;
/**
Expand Down
55 changes: 49 additions & 6 deletions packages/calling-stateful-client/src/CallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { CaptionsKind, CaptionsInfo as AcsCaptionsInfo } from '@azure/communicat
/* @conditional-compile-remove(unsupported-browser) */
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 All @@ -47,6 +47,8 @@ import {
CallErrorTarget,
CallError
} from './CallClientState';
/* @conditional-compile-remove(together-mode) */
import { TogetherModeStreamState, TogetherModeSeatingCoordinatesState } from './CallClientState';
/* @conditional-compile-remove(breakout-rooms) */
import { NotificationTarget, CallNotification, CallNotifications } from './CallClientState';
import { TeamsIncomingCallState } from './CallClientState';
Expand Down Expand Up @@ -457,11 +459,25 @@ export class CallContext {
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeVideoStream(callId: string, addedStream: TogetherModeVideoStream[]): void {
public setTogetherModeVideoStream(callId: string, addedStreams: TogetherModeVideoStream[]): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, can we have this an an object for the TogetherModeVideoStream instead of an array?

this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
call.togetherMode = { stream: addedStream };
const streamsToAdd: Map<string, TogetherModeStreamState> = new Map();
for (const stream of addedStreams) {
const streamToAdd: TogetherModeStreamState = {
feature: 'togetherMode',
id: stream.id,
view: undefined,
mediaStreamType: stream.mediaStreamType,
isReceiving: stream.isReceiving
};
streamsToAdd.set(stream.mediaStreamType, streamToAdd);
}
call.togetherMode.streams = streamsToAdd;
if (!call.togetherMode.seatingCoordinates) {
call.togetherMode.seatingCoordinates = new Map<string, TogetherModeSeatingCoordinatesState>();
}
}
});
}
Expand All @@ -472,15 +488,24 @@ 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 in call.togetherMode.streams) {
call.togetherMode.streams.delete(stream.mediaStreamType);
}
}
}
});
}

/* @conditional-compile-remove(together-mode) */
public setTogetherModeSeatingCoordinatesState(callId: string, seatingMap: TogetherModeSeatingMap): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
if (call) {
call.togetherMode.seatingCoordinates = seatingMap;
}
});
}

public setCallRaisedHands(callId: string, raisedHands: RaisedHand[]): void {
this.modifyState((draft: CallClientState) => {
const call = draft.calls[this._callIdHistory.latestCallId(callId)];
Expand Down Expand Up @@ -720,6 +745,24 @@ 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)];
/* @conditional-compile-remove(together-mode) */
if (call) {
const togetherModeStream = call.togetherMode.streams.get(togetherModeStreamType);
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
2 changes: 1 addition & 1 deletion packages/calling-stateful-client/src/Converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function convertSdkCallToDeclarativeCall(call: CallCommon): CallState {
pptLive: { isActive: false },
raiseHand: { raisedHands: [] },
/* @conditional-compile-remove(together-mode) */
togetherMode: { stream: [] },
togetherMode: { streams: new Map(), seatingCoordinates: new Map() },
localParticipantReaction: undefined,
transcription: { isTranscriptionActive: false },
screenShareRemoteParticipant: undefined,
Expand Down
Loading
Loading