Skip to content

Commit

Permalink
feat(plugin-meetings): Introduce SDK changes to call WCME and Locus A…
Browse files Browse the repository at this point in the history
…PIs when current user steps away or returns (#3942)

Co-authored-by: Anna Tsukanova <[email protected]>
Co-authored-by: Filip Nowakowski <[email protected]>
Co-authored-by: evujici <[email protected]>
  • Loading branch information
4 people authored Nov 24, 2024
1 parent 52fb49e commit 2ff58a4
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 70 deletions.
27 changes: 22 additions & 5 deletions docs/samples/browser-plugin-meetings/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const breakoutsList = document.getElementById('breakouts-list');
const breakoutTable = document.getElementById('breakout-table');
const breakoutHostOperation = document.getElementById('breakout-host-operation');
const getStatsButton = document.getElementById('get-stats');
const tcpReachabilityConfigElm = document.getElementById('enable-tcp-reachability');
const tcpReachabilityConfigElm = document.getElementById('enable-tcp-reachability');
const tlsReachabilityConfigElm = document.getElementById('enable-tls-reachability');

const guestName = document.querySelector('#guest-name');
Expand Down Expand Up @@ -388,7 +388,7 @@ createMeetingSelectElm.addEventListener('change', (event) => {
}
else {
notes.classList.add('hidden');

}
});

Expand Down Expand Up @@ -950,7 +950,7 @@ function cleanUpMedia() {
elem.srcObject.getTracks().forEach((track) => track.stop());
// eslint-disable-next-line no-param-reassign
elem.srcObject = null;

if(elem.id === "local-video") {
clearVideoResolutionCheckInterval(localVideoResElm, localVideoResolutionInterval);
}
Expand Down Expand Up @@ -1566,7 +1566,7 @@ async function stopStartVideo() {
console.error(error);
}
}

}

async function stopStartAudio() {
Expand Down Expand Up @@ -1608,7 +1608,7 @@ async function stopStartAudio() {
console.error(error);
}
}

}

function populateSourceDevices(mediaDevice) {
Expand Down Expand Up @@ -3330,6 +3330,23 @@ function toggleBreakout() {
}
}

async function toggleBrb() {
const meeting = getCurrentMeeting();

if (meeting) {
const enabled = document.getElementById('brb').checked;
try {
const result = await meeting.beRightBack(enabled);
console.log(`meeting.beRightBack(${enabled}): success. Result: ${result}`);
} catch (error) {
console.error(`meeting.beRightBack({${enabled}): error: `, error);
} finally {
localMedia?.microphoneStream?.setUserMuted(enabled);
localMedia?.cameraStream?.setUserMuted(enabled);
}
}
}

const createAdmitDiv = () => {

const containerDiv = document.createElement('div');
Expand Down
8 changes: 8 additions & 0 deletions docs/samples/browser-plugin-meetings/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ <h2 class="collapsible">
</div>
</div>

<div>
<fieldset>
<legend>Step Away</legend>
<input type="checkbox" id="brb" onclick="toggleBrb()">
<label for="brb">Enable Step Away</label><br>
</fieldset>
</div>

</div>
</fieldset>
</form>
Expand Down
2 changes: 1 addition & 1 deletion packages/@webex/media-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/internal-media-core": "2.11.3",
"@webex/internal-media-core": "2.12.0",
"@webex/ts-events": "^1.1.0",
"@webex/web-media-effects": "2.19.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/@webex/plugin-meetings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"dependencies": {
"@webex/common": "workspace:*",
"@webex/internal-media-core": "2.11.3",
"@webex/internal-media-core": "2.12.0",
"@webex/internal-plugin-conversation": "workspace:*",
"@webex/internal-plugin-device": "workspace:*",
"@webex/internal-plugin-llm": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/@webex/plugin-meetings/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ export const EVENT_TRIGGERS = {
MEETING_SELF_CANNOT_VIEW_PARTICIPANT_LIST: 'meeting:self:cannotViewParticipantList',
MEETING_SELF_IS_SHARING_BLOCKED: 'meeting:self:isSharingBlocked',
MEETING_SELF_ROLES_CHANGED: 'meeting:self:rolesChanged',
MEETING_SELF_BRB_UPDATE: 'meeting:self:brbUpdate',
MEETING_CONTROLS_LAYOUT_UPDATE: 'meeting:layout:update',
MEETING_ENTRY_EXIT_TONE_UPDATE: 'meeting:entryExitTone:update',
MEETING_BREAKOUTS_UPDATE: 'meeting:breakouts:update',
Expand Down Expand Up @@ -700,6 +701,7 @@ export const LOCUSINFO = {
SELF_IS_SHARING_BLOCKED_CHANGE: 'SELF_IS_SHARING_BLOCKED_CHANGE',
SELF_MEETING_BREAKOUTS_CHANGED: 'SELF_MEETING_BREAKOUTS_CHANGED',
SELF_MEETING_INTERPRETATION_CHANGED: 'SELF_MEETING_INTERPRETATION_CHANGED',
SELF_MEETING_BRB_CHANGED: 'SELF_MEETING_BRB_CHANGED',
MEDIA_INACTIVITY: 'MEDIA_INACTIVITY',
LINKS_SERVICES: 'LINKS_SERVICES',
},
Expand Down
13 changes: 13 additions & 0 deletions packages/@webex/plugin-meetings/src/locus-info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,19 @@ export default class LocusInfo extends EventsScope {
);
}

if (parsedSelves.updates.brbChanged) {
this.emitScoped(
{
file: 'locus-info',
function: 'updateSelf',
},
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
{
brb: parsedSelves.current.brb,
}
);
}

if (parsedSelves.updates.interpretationChanged) {
this.emitScoped(
{
Expand Down
6 changes: 6 additions & 0 deletions packages/@webex/plugin-meetings/src/locus-info/selfUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
breakoutSessions: SelfUtils.getBreakoutSessions(self),
breakout: SelfUtils.getBreakout(self),
interpretation: SelfUtils.getInterpretation(self),
brb: SelfUtils.getBrb(self),
};
}

Expand All @@ -75,6 +76,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
SelfUtils.getBreakoutSessions = (self) => self?.controls?.breakout?.sessions;
SelfUtils.getBreakout = (self) => self?.controls?.breakout;
SelfUtils.getInterpretation = (self) => self?.controls?.interpretation;
SelfUtils.getBrb = (self) => self?.controls?.brb;

SelfUtils.getLayout = (self) =>
Array.isArray(self?.controls?.layouts) ? self.controls.layouts[0].type : undefined;
Expand Down Expand Up @@ -128,6 +130,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
updates.isSharingBlockedChanged = previous?.isSharingBlocked !== current.isSharingBlocked;
updates.breakoutsChanged = SelfUtils.breakoutsChanged(previous, current);
updates.interpretationChanged = SelfUtils.interpretationChanged(previous, current);
updates.brbChanged = SelfUtils.brbChanged(previous, current);

return {
previous,
Expand Down Expand Up @@ -159,6 +162,9 @@ SelfUtils.breakoutsChanged = (previous, current) =>
SelfUtils.interpretationChanged = (previous, current) =>
!isEqual(previous?.interpretation, current?.interpretation) && !!current?.interpretation;

SelfUtils.brbChanged = (previous, current) =>
!isEqual(previous?.brb, current?.brb) && current?.brb !== undefined;

SelfUtils.isMediaInactive = (previous, current) => {
if (
previous &&
Expand Down
58 changes: 58 additions & 0 deletions packages/@webex/plugin-meetings/src/meeting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3303,6 +3303,20 @@ export default class Meeting extends StatelessWebexPlugin {
}
});

this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
Trigger.trigger(
this,
{
file: 'meeting/index',
function: 'setUpLocusInfoSelfListener',
},
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
{
payload,
}
);
});

this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
const isModeratorOrCohost =
payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
Expand Down Expand Up @@ -3505,6 +3519,50 @@ export default class Meeting extends StatelessWebexPlugin {
return this.members.admitMembers(memberIds, locusUrls);
}

/**
* Manages be right back status updates for the current participant.
*
* @param {boolean} enabled - Indicates whether the user enabled brb or not.
* @returns {Promise<void>} resolves when the brb status is updated or does nothing if not in a multistream meeting.
* @throws {Error} - Throws an error if the request fails.
*/
public async beRightBack(enabled: boolean): Promise<void> {
if (!this.isMultistream) {
const errorMessage = 'Meeting:index#beRightBack --> Not a multistream meeting';
const error = new Error(errorMessage);

LoggerProxy.logger.error(error);

return Promise.reject(error);
}

if (!this.mediaProperties.webrtcMediaConnection) {
const errorMessage = 'Meeting:index#beRightBack --> WebRTC media connection is not defined';
const error = new Error(errorMessage);

LoggerProxy.logger.error(error);

return Promise.reject(error);
}

// this logic should be applied only to multistream meetings
return this.meetingRequest
.sendBrb({
enabled,
locusUrl: this.locusUrl,
deviceUrl: this.deviceUrl,
selfId: this.selfId,
})
.then(() => {
this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
})
.catch((error) => {
LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);

return Promise.reject(error);
});
}

/**
* Remove the member from the meeting, boot them
* @param {String} memberId
Expand Down
27 changes: 26 additions & 1 deletion packages/@webex/plugin-meetings/src/meeting/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
ANNOTATION,
IP_VERSION,
} from '../constants';
import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
import {SendReactionOptions, BrbOptions, ToggleReactionsOptions} from './request.type';
import MeetingUtil from './util';
import {AnnotationInfo} from '../annotation/annotation.types';

Expand Down Expand Up @@ -916,4 +916,29 @@ export default class MeetingRequest extends StatelessWebexPlugin {
uri: locusUrl,
});
}

/**
* Sends a request to set be right back status.
*
* @param {Object} options - The options for brb request.
* @param {boolean} options.enabled - Whether brb status is enabled.
* @param {string} options.locusUrl - The URL of the locus.
* @param {string} options.deviceUrl - The URL of the device.
* @param {string} options.selfId - The ID of the participant.
* @returns {Promise}
*/
sendBrb({enabled, locusUrl, deviceUrl, selfId}: BrbOptions) {
const uri = `${locusUrl}/${PARTICIPANT}/${selfId}/${CONTROLS}`;

return this.locusDeltaRequest({
method: HTTP_VERBS.PATCH,
uri,
body: {
brb: {
enabled,
deviceUrl,
},
},
});
}
}
7 changes: 7 additions & 0 deletions packages/@webex/plugin-meetings/src/meeting/request.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ export type ToggleReactionsOptions = {
locusUrl: string;
requestingParticipantId: string;
};

export type BrbOptions = {
enabled: boolean;
locusUrl: string;
deviceUrl: string;
selfId: string;
};
31 changes: 31 additions & 0 deletions packages/@webex/plugin-meetings/src/multistream/sendSlotManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
LocalStream,
MultistreamRoapMediaConnection,
NamedMediaGroup,
StreamState,
} from '@webex/internal-media-core';

export default class SendSlotManager {
Expand Down Expand Up @@ -83,6 +84,36 @@ export default class SendSlotManager {
);
}

/**
* Sets the source state override for the given media type.
* @param {MediaType} mediaType - The type of media (must be MediaType.VideoMain to apply source state changes).
* @param {StreamState | null} state - The state to set or null to clear the override value.
* @returns {void}
*/
public setSourceStateOverride(mediaType: MediaType, state: StreamState | null) {
if (mediaType !== MediaType.VideoMain) {
throw new Error(
`sendSlotManager cannot set source state override which media type is ${mediaType}`
);
}

const slot = this.slots.get(mediaType);

if (!slot) {
throw new Error(`Slot for ${mediaType} does not exist`);
}

if (state) {
slot.setSourceStateOverride(state);
} else {
slot.clearSourceStateOverride();
}

this.LoggerProxy.logger.info(
`SendSlotsManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
);
}

/**
* This method publishes the given stream to the sendSlot for the given mediaType
* @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
Expand Down
Loading

0 comments on commit 2ff58a4

Please sign in to comment.