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 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
27492b0
Implementation of call feature streams. This will be used to create s…
cn0151 Oct 3, 2024
723da07
Included docs and conditional compilation tag
cn0151 Oct 4, 2024
6f0c40d
Include missing conditional compile tags
cn0151 Oct 8, 2024
4e95fed
fixed failing test
cn0151 Oct 8, 2024
f0a2396
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 8, 2024
9985249
Updated CallAdapterCallOperations APIs
cn0151 Oct 8, 2024
ea8d7fa
fixed lint issue
cn0151 Oct 8, 2024
8bd4960
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 8, 2024
d1c525a
fixed failing test
cn0151 Oct 8, 2024
cd62937
Updated tags
cn0151 Oct 8, 2024
bf66778
Updated together mode create view result type
cn0151 Oct 9, 2024
1354911
Updated conditional tags
cn0151 Oct 9, 2024
87c7d45
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 10, 2024
3b61bad
Updated call feature streams
cn0151 Oct 10, 2024
ecb470d
fix merge conflict
cn0151 Oct 10, 2024
3abfd2d
change log update
cn0151 Oct 10, 2024
46d2d8f
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 14, 2024
8601b16
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 15, 2024
ce9cb8d
Addressed comments
cn0151 Oct 15, 2024
87e9def
Addresse comments by moving createCallFeatureView and disposeCallFeat…
cn0151 Oct 24, 2024
863f722
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 24, 2024
e04a9ca
Addressed comments
cn0151 Oct 24, 2024
03e0f8f
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 24, 2024
6bf4544
Fix lint issue
cn0151 Oct 24, 2024
a931f58
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 24, 2024
37e426f
Merge branch 'cnwankwo/callFeatureStreams_implementation' of https://…
cn0151 Oct 24, 2024
91f4321
Merge branch 'main' into cnwankwo/callFeatureStreams_implementation
cn0151 Oct 25, 2024
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
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"area": "feature",
"workstream": "togetherMode",
"comment": "Addressed comments by moving createCallFeatureView and disposeCallFeatureView to its own file",
"packageName": "@azure/communication-react",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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 @@ -103,6 +105,20 @@ export interface CommonCallingHandlers {
onStopAllSpotlight: () => Promise<void>;
onMuteParticipant: (userId: string) => Promise<void>;
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 @@ -712,6 +728,54 @@ 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.mainVideoStream) {
const togetherModeFeature = call?.feature(Features.TogetherMode);
await togetherModeFeature?.start();
} else {
const mainVideoStream = togetherModeStreams.mainVideoStream;
if (mainVideoStream && !mainVideoStream.view) {
const createViewResult = await callClient.createCallFeatureView(call.id, mainVideoStream, options);
// SDK currently only supports 1 Video media stream type
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.mainVideoStream) {
return;
}

if (togetherModeStreams.mainVideoStream.view) {
callClient.disposeCallFeatureView(call.id, togetherModeStreams.mainVideoStream);
}
};
return {
onHangUp,
onToggleHold,
Expand Down Expand Up @@ -761,7 +825,11 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
onMuteParticipant,
onMuteAllRemoteParticipants,
onAcceptCall: notImplemented,
onRejectCall: notImplemented
onRejectCall: notImplemented,
/* @conditional-compile-remove(together-mode) */
onCreateTogetherModeStreamView,
/* @conditional-compile-remove(together-mode) */
onDisposeTogetherModeStreamViews
};
}
);
83 changes: 55 additions & 28 deletions packages/calling-stateful-client/src/CallClientState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,45 +272,71 @@ 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 {
feature?: CallFeatureStreamName;
}

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeVideoStream}.
* @alpha
* @beta
*/
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 TogetherModeStreamViewState extends RemoteVideoStreamState, CallFeatureStreamState {}

/* @conditional-compile-remove(together-mode) */
/**
* State only version of {@link @azure/communication-calling#TogetherModeSeatingMap}.
* @beta
*
* Represents the seating position of a participant in Together Mode.
*/
export interface TogetherModeSeatingPositionState {
// The participant id of the participant in the seating position.
participantId: string;
// 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) */
/**
* Interface representing the streams in Together Mode.
*
* @beta
*/
export interface TogetherModeStreamsState {
mainVideoStream?: TogetherModeStreamViewState;
}

/* @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: TogetherModeSeatingPositionState[];
}

/**
Expand Down Expand Up @@ -621,6 +647,7 @@ export interface CallState {
/* @conditional-compile-remove(together-mode) */
/**
* Proxy of {@link @azure/communication-calling#TogetherModeCallFeature}.
* @beta
*/
togetherMode: TogetherModeCallFeatureState;
/**
Expand Down
122 changes: 116 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 { TogetherModeStreamViewState, 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: TogetherModeStreamViewState[],
removedStreams: TogetherModeStreamViewState[]
): 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,37 @@ 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: TogetherModeSeatingPositionState[] = [];
for (const [key, value] of seatingMap.entries()) {
const participantPosition: TogetherModeSeatingPositionState = {
participantId: key,
top: value.top,
left: value.left,
width: value.width,
height: value.height
};

seatingPositions.push(participantPosition);
}
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 +809,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