Skip to content
Merged
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
55 changes: 55 additions & 0 deletions packages/@webex/plugin-meetings/src/locus-info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,51 @@ import LocusDeltaParser from './parser';
import Metrics from '../metrics';
import BEHAVIORAL_METRICS from '../metrics/constants';

export type LocusDTO = {
controls?: any;
fullState?: {
active: boolean;
count: number;
lastActive: string;
locked: boolean;
sessionId: string;
seessionIds: string[];
startTime: number;
state: string;
type: string;
};
host?: {
id: string;
incomingCallProtocols: any[];
isExternal: boolean;
name: string;
orgId: string;
};
info?: any;
links?: any;
mediaShares?: any[];
meetings?: any[];
participants: any[];
replaces?: any[];
self?: any;
sequence?: {
dirtyParticipants: number;
entries: number[];
rangeEnd: number;
rangeStart: number;
sequenceHash: number;
sessionToken: string;
since: string;
totalParticipants: number;
};
syncUrl?: string;
url?: string;
};

export type LocusApiResponseBody = {
locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
};

/**
* @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
* @export
Expand Down Expand Up @@ -323,6 +368,16 @@ export default class LocusInfo extends EventsScope {
this.emitChange = true;
}

/**
* Handles HTTP response from Locus API call.
* @param {Meeting} meeting meeting object
* @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
* @returns {void}
*/
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
this.handleLocusDelta(responseBody.locus, meeting);
}

/**
* @param {Meeting} meeting
* @param {Object} data
Expand Down
8 changes: 2 additions & 6 deletions packages/@webex/plugin-meetings/src/meeting/muteState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,14 @@ export class MuteState {
);

return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
.then((locus) => {
.then((response) => {
LoggerProxy.logger.info(
`Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
);

this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;

if (locus) {
meeting.locusInfo.handleLocusDelta(locus, meeting);
}

return locus;
return MeetingUtil.updateLocusFromApiResponse(meeting, response);
})
.catch((remoteUpdateError) => {
LoggerProxy.logger.warn(
Expand Down
36 changes: 16 additions & 20 deletions packages/@webex/plugin-meetings/src/meeting/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,16 @@ const MeetingUtil = {
);
}

return meeting.locusMediaRequest
.send({
type: 'LocalMute',
selfUrl: meeting.selfUrl,
mediaId: meeting.mediaId,
sequence: meeting.locusInfo.sequence,
muteOptions: {
audioMuted,
videoMuted,
},
})
.then((response) => response?.body?.locus);
return meeting.locusMediaRequest.send({
type: 'LocalMute',
selfUrl: meeting.selfUrl,
mediaId: meeting.mediaId,
sequence: meeting.locusInfo.sequence,
muteOptions: {
audioMuted,
videoMuted,
},
});
},

hasOwner: (info) => info && info.owner,
Expand Down Expand Up @@ -695,22 +693,20 @@ const MeetingUtil = {
},

/**
* Updates the locus info for the meeting with the delta locus
* returned from requests that include the sequence information
* Updates the locus info for the meeting with the locus
* information returned from API requests made to Locus
* Returns the original response object
* @param {Object} meeting The meeting object
* @param {Object} response The response of the http request
* @returns {Object}
*/
updateLocusWithDelta: (meeting, response) => {
updateLocusFromApiResponse: (meeting, response) => {
if (!meeting) {
return response;
}

const locus = response?.body?.locus;

if (locus) {
meeting.locusInfo.handleLocusDelta(locus, meeting);
if (response?.body?.locus) {
meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
}

return response;
Expand Down Expand Up @@ -757,7 +753,7 @@ const MeetingUtil = {

return meeting
.request(options)
.then((response) => MeetingUtil.updateLocusWithDelta(meeting, response));
.then((response) => MeetingUtil.updateLocusFromApiResponse(meeting, response));
};

return locusDeltaRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,18 @@ describe('plugin-meetings', () => {
});
});

describe('#handleLocusAPIResponse', () => {
it('calls handleLocusDelta', () => {
const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};

sinon.stub(locusInfo, 'handleLocusDelta');

locusInfo.handleLocusAPIResponse(mockMeeting, {locus: fakeLocus});

assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
});
});

describe('#LocusDeltaEvents', () => {
const fakeMeeting = 'fakeMeeting';
let sandbox = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('plugin-meetings', () => {
let video;
let originalRemoteUpdateAudioVideo;

const fakeLocus = {info: 'this is a fake locus'};
const fakeLocusResponse = {body: {locus: {info: 'this is a fake locus'}}};

const createFakeLocalStream = (id, userMuted, systemMuted) => {
return {
Expand All @@ -38,9 +38,6 @@ describe('plugin-meetings', () => {
unmuteAllowed: true,
remoteVideoMuted: false,
unmuteVideoAllowed: true,
locusInfo: {
handleLocusDelta: sinon.stub(),
},
members: {
selfId: 'fake self id',
muteMember: sinon.stub().resolves(),
Expand All @@ -49,7 +46,8 @@ describe('plugin-meetings', () => {

originalRemoteUpdateAudioVideo = MeetingUtil.remoteUpdateAudioVideo;

MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves(fakeLocus);
MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves(fakeLocusResponse);
MeetingUtil.updateLocusFromApiResponse = sinon.stub();

audio = createMuteState(AUDIO, meeting, true);
video = createMuteState(VIDEO, meeting, true);
Expand Down Expand Up @@ -141,6 +139,7 @@ describe('plugin-meetings', () => {
// and local unmute was sent to server
assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
assert.calledWith(MeetingUtil.remoteUpdateAudioVideo, meeting, false, undefined);
assert.calledWith(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);

assert.isFalse(audio.isMuted());
});
Expand Down Expand Up @@ -173,6 +172,7 @@ describe('plugin-meetings', () => {

// system was muted so local unmute was not sent to server
assert.notCalled(MeetingUtil.remoteUpdateAudioVideo);
assert.notCalled(MeetingUtil.updateLocusFromApiResponse);

assert.isTrue(audio.isMuted());
});
Expand Down Expand Up @@ -207,6 +207,7 @@ describe('plugin-meetings', () => {
// and local unmute was sent to server
assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
assert.calledWith(MeetingUtil.remoteUpdateAudioVideo, meeting, undefined, false);
assert.calledWith(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);

assert.isFalse(video.isMuted());
});
Expand All @@ -219,7 +220,9 @@ describe('plugin-meetings', () => {

assert.isTrue(video.isMuted());

await testUtils.flushPromises();
MeetingUtil.remoteUpdateAudioVideo.resetHistory();
MeetingUtil.updateLocusFromApiResponse.resetHistory();

// now simulate server requiring us to locally unmute
// assuming setServerMuted succeeds at updating userMuted
Expand All @@ -239,6 +242,7 @@ describe('plugin-meetings', () => {

// system was muted so local unmute was not sent to server
assert.notCalled(MeetingUtil.remoteUpdateAudioVideo);
assert.notCalled(MeetingUtil.updateLocusFromApiResponse);

assert.isTrue(video.isMuted());
});
Expand Down Expand Up @@ -443,6 +447,7 @@ describe('plugin-meetings', () => {

assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
assert.calledWith(MeetingUtil.remoteUpdateAudioVideo, meeting, true, undefined);
assert.calledWith(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);

// now allow the first request to complete
serverResponseResolve();
Expand Down Expand Up @@ -559,6 +564,7 @@ describe('plugin-meetings', () => {
await testUtils.flushPromises();

MeetingUtil.remoteUpdateAudioVideo.resetHistory();
MeetingUtil.updateLocusFromApiResponse.resetHistory();
};

const setupSpies = (mediaType) => {
Expand Down Expand Up @@ -605,13 +611,15 @@ describe('plugin-meetings', () => {
{mediaType: VIDEO, title: 'video'},
];

const fakeLocusResponse = {body: {locus: {info: 'fake locus'}}};

tests.forEach(({mediaType, title}) =>
describe(title, () => {
let originalRemoteUpdateAudioVideo;

beforeEach(() => {
originalRemoteUpdateAudioVideo = MeetingUtil.remoteUpdateAudioVideo;
MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves({info: 'fake locus'});
MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves(fakeLocusResponse);
});

afterEach(() => {
Expand Down Expand Up @@ -660,6 +668,7 @@ describe('plugin-meetings', () => {
assert.calledWith(setUnmuteAllowedSpy, muteState.state.server.unmuteAllowed);
assert.notCalled(setServerMutedSpy);
assert.notCalled(MeetingUtil.remoteUpdateAudioVideo);
assert.notCalled(MeetingUtil.updateLocusFromApiResponse);
assert.isTrue(muteState.state.client.localMute);
});

Expand All @@ -672,6 +681,7 @@ describe('plugin-meetings', () => {
assert.calledWith(setUnmuteAllowedSpy, muteState.state.server.unmuteAllowed);
assert.notCalled(setServerMutedSpy);
assert.notCalled(MeetingUtil.remoteUpdateAudioVideo);
assert.notCalled(MeetingUtil.updateLocusFromApiResponse);
assert.isTrue(muteState.state.client.localMute);
});

Expand All @@ -681,9 +691,12 @@ describe('plugin-meetings', () => {

muteState.init(meeting);

await testUtils.flushPromises();

assert.calledWith(setUnmuteAllowedSpy, muteState.state.server.unmuteAllowed);
assert.notCalled(setServerMutedSpy);
assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
assert.calledOnceWithExactly(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);
assert.isFalse(muteState.state.client.localMute);
});

Expand All @@ -707,6 +720,7 @@ describe('plugin-meetings', () => {
simulateUserMute(mediaType, true);
muteState.handleLocalStreamMuteStateChange(meeting);
assert.notCalled(MeetingUtil.remoteUpdateAudioVideo);
assert.notCalled(MeetingUtil.updateLocusFromApiResponse);

assert.isFalse(muteState.state.client.localMute);
});
Expand All @@ -716,35 +730,47 @@ describe('plugin-meetings', () => {

simulateUserMute(mediaType, false);
muteState.handleLocalStreamMuteStateChange(meeting);
await testUtils.flushPromises();

assert.equal(muteState.state.client.localMute, false);
assert.called(MeetingUtil.remoteUpdateAudioVideo);
assert.calledOnceWithExactly(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);
});

it('tests localMute - user mute from false to true', async () => {
await setup(mediaType, false, false, false, true);

simulateUserMute(mediaType, true);
muteState.handleLocalStreamMuteStateChange(meeting);
await testUtils.flushPromises();

assert.equal(muteState.state.client.localMute, true);
assert.called(MeetingUtil.remoteUpdateAudioVideo);
assert.calledOnceWithExactly(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);
});

it('tests localMute - system mute from true to false', async () => {
await setup(mediaType, false, false, true, true);

simulateSystemMute(mediaType, false);
muteState.handleLocalStreamMuteStateChange(meeting);
await testUtils.flushPromises();

assert.equal(muteState.state.client.localMute, false);
assert.called(MeetingUtil.remoteUpdateAudioVideo);
assert.calledOnceWithExactly(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);
});

it('tests localMute - system mute from false to true', async () => {
await setup(mediaType, false, false, false, true);

simulateSystemMute(mediaType, true);
muteState.handleLocalStreamMuteStateChange(meeting);
await testUtils.flushPromises();

assert.equal(muteState.state.client.localMute, true);
assert.called(MeetingUtil.remoteUpdateAudioVideo);
assert.calledOnceWithExactly(MeetingUtil.updateLocusFromApiResponse, meeting, fakeLocusResponse);
});
});

Expand Down
Loading
Loading