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

feat(calling): adding metrics for bnr #3161

Closed
wants to merge 7 commits into from
Closed
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
32 changes: 17 additions & 15 deletions docs/samples/calling/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const directoryNumberCFA = document.querySelector('#directoryNumber');
const cfaDataElem = document.querySelector('#callforwardalways-data');
const makeCallBtn = document.querySelector('#create-call-action');
const muteElm = document.getElementById('mute_button');
const bnrButton = document.getElementById('bnr_button');

let base64;
let audio64;
Expand Down Expand Up @@ -415,7 +416,7 @@ function muteUnmute() {
if (callTransferObj){
callTransferObj.mute(localAudioStream)
}
else {
else {
call.mute(localAudioStream);
}
}
Expand All @@ -428,7 +429,7 @@ function holdResume() {
holdResumeElm.value = 'Resume';
}
});

callTransferObj.on('resumed', (correlationId) => {
if (holdResumeElm.value === 'Resume') {
callDetailsElm.innerText = 'Call is Resumed';
Expand All @@ -444,7 +445,7 @@ function holdResume() {
holdResumeElm.value = 'Resume';
}
});

call.on('resumed', (correlationId) => {
if (holdResumeElm.value === 'Resume') {
callDetailsElm.innerText = 'Call is Resumed';
Expand Down Expand Up @@ -666,22 +667,23 @@ async function getMediaStreams() {
makeCallBtn.disabled = false;
}

async function addNoiseReductionEffect() {
effect = await localAudioStream.getEffect('background-noise-removal');
async function toggleNoiseReductionEffect() {
effect = await localAudioStream.getEffectByKind('noise-reduction-effect');

if (!effect) {
effect = await Calling.createNoiseReductionEffect(tokenElm.value);
await localAudioStream.addEffect(effect);
await effect.enable();
bnrButton.innerHTML = 'Disable BNR()';
} else {

await localAudioStream.addEffect('background-noise-removal', effect);
}

await effect.enable();
}

async function removeNoiseReductionEffect() {
effect = await localAudioStream.getEffect('background-noise-removal');
if (effect) {
await effect.disable();
if (effect.isEnabled) {
await effect.disable();
bnrButton.innerHTML = 'Enable BNR()';
} else {
await effect.enable();
bnrButton.innerHTML = 'Disable BNR()';
}
}
}

Expand Down
4 changes: 1 addition & 3 deletions docs/samples/calling/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,8 @@ <h2 class="collapsible">Call initialization</h2>
class="btn-code">getMediaStreams()</button>
</div>
<div class="u-mv">
<button id="sd-add-bnr" type="button" onclick="addNoiseReductionEffect()"
<button id="bnr_button" type="button" onclick="toggleNoiseReductionEffect()"
class="btn-code">Enable BNR()</button>
<button id="sd-add-bnr" type="button" onclick="removeNoiseReductionEffect()"
class="btn-code">Disable BNR()</button>
</div>
</fieldset>

Expand Down
4 changes: 2 additions & 2 deletions packages/@webex/media-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/internal-media-core": "^2.0.0",
"@webex/internal-media-core": "^2.2.2",
"@webex/ts-events": "^1.1.0",
"@webex/web-media-effects": "^2.13.1"
"@webex/web-media-effects": "^2.15.6"
},
"browserify": {
"transform": [
Expand Down
2 changes: 1 addition & 1 deletion packages/calling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"dependencies": {
"@types/platform": "1.3.4",
"@webex/internal-media-core": "2.0.0",
"@webex/internal-media-core": "2.2.2",
"@webex/media-helpers": "workspace:^",
"async-mutex": "0.4.0",
"buffer": "6.0.3",
Expand Down
130 changes: 110 additions & 20 deletions packages/calling/src/CallingClient/calling/call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as Utils from '../../common/Utils';
import {CALL_EVENT_KEYS, CallEvent, RoapEvent, RoapMessage} from '../../Events/types';
import {DEFAULT_SESSION_TIMER} from '../constants';
import {CallDirection, CallType, ServiceIndicator, WebexRequestPayload} from '../../common/types';
import {METRIC_EVENT, TRANSFER_ACTION, METRIC_TYPE} from '../../Metrics/types';
import {METRIC_EVENT, TRANSFER_ACTION, METRIC_TYPE, MEDIA_EFFECT_ACTION} from '../../Metrics/types';
import {Call, createCall} from './call';
import {
MobiusCallState,
Expand Down Expand Up @@ -36,25 +36,6 @@ const defaultServiceIndicator = ServiceIndicator.CALLING;
const activeUrl = 'FakeActiveUrl';
const mockLineId = 'e4e8ee2a-a154-4e52-8f11-ef4cde2dce72';

// class MockMediaStream {
// private track;

// constructor(track: any) {
// this.track = track;
// }
// }

// globalThis.MediaStream = MockMediaStream;

// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// jest.spyOn(window, 'MediaStream').mockImplementation((tracks: MediaStreamTrack[]) => {
// return {} as MediaStream;
// });

// // Object.defineProperty(window, 'MediaStream', {
// // writable: true,
// // });

describe('Call Tests', () => {
const deviceId = '55dfb53f-bed2-36da-8e85-cee7f02aa68e';
const dest = {
Expand Down Expand Up @@ -111,6 +92,10 @@ describe('Call Tests', () => {
enabled: false,
} as MediaStreamTrack;

const mockEffect = {
isEnabled: true,
};

const roapMediaConnectionConfig = {
skipInactiveTransceivers: true,
iceServers: [],
Expand Down Expand Up @@ -287,6 +272,9 @@ describe('Call Tests', () => {
getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
},
on: jest.fn(),
getEffect: jest.fn().mockImplementation(() => {
return mockEffect;
}),
};

const localAudioStream = mockStream as unknown as MediaSDK.LocalMicrophoneStream;
Expand All @@ -303,6 +291,8 @@ describe('Call Tests', () => {
defaultServiceIndicator
);

const mediaMetricSpy = jest.spyOn(call['metricManager'], 'submitBNRMetric');

call.dial(localAudioStream);

expect(mockTrack.enabled).toEqual(true);
Expand All @@ -312,6 +302,15 @@ describe('Call Tests', () => {
expect.any(String)
);
expect(call['mediaStateMachine'].state.value).toBe('S_SEND_ROAP_OFFER');

expect(mediaMetricSpy).toBeCalledOnceWith(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
call.getCallId(),
call.getCorrelationId()
);

/* Now change the state and recall to check for error */
call['mediaStateMachine'].state.value = 'S_SEND_ROAP_OFFER';
call.dial(localAudioStream);
Expand All @@ -328,6 +327,9 @@ describe('Call Tests', () => {
getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
},
on: jest.fn(),
getEffect: jest.fn().mockImplementation(() => {
return mockEffect;
}),
};

const localAudioStream = mockStream as unknown as MediaSDK.LocalMicrophoneStream;
Expand All @@ -345,6 +347,8 @@ describe('Call Tests', () => {
);
/** Cannot answer in idle state */

const mediaMetricSpy = jest.spyOn(call['metricManager'], 'submitBNRMetric');

call.answer(localAudioStream);
expect(mockTrack.enabled).toEqual(true);
expect(mockMediaSDK.RoapMediaConnection).toBeCalledOnceWith(
Expand All @@ -362,6 +366,92 @@ describe('Call Tests', () => {
call['callStateMachine'].state.value = 'S_SEND_CALL_PROGRESS';
call.answer(localAudioStream);
expect(call['callStateMachine'].state.value).toBe('S_SEND_CALL_CONNECT');

expect(mediaMetricSpy).toBeCalledOnceWith(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
call.getCallId(),
call.getCorrelationId()
);
});

it('testing enabling/disabling the BNR on an active call', async () => {
const mockStream = {
outputStream: {
getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
},
on: jest.fn(),
getEffect: jest.fn(),
};

const localAudioStream = mockStream as unknown as MediaSDK.LocalMicrophoneStream;
const onSpy = jest.spyOn(localAudioStream, 'on');

const call = createCall(
activeUrl,
webex,
dest,
CallDirection.OUTBOUND,
deviceId,
mockLineId,
deleteCallFromCollection,
defaultServiceIndicator
);

call.dial(localAudioStream);

expect(mockTrack.enabled).toEqual(true);
expect(mockMediaSDK.RoapMediaConnection).toBeCalledOnceWith(
roapMediaConnectionConfig,
roapMediaConnectionOptions,
expect.any(String)
);
expect(call['mediaStateMachine'].state.value).toBe('S_SEND_ROAP_OFFER');

const updateLocalTracksSpy = jest.spyOn(call['mediaConnection'], 'updateLocalTracks');
const mediaMetricSpy = jest.spyOn(call['metricManager'], 'submitBNRMetric');

/* Send metrics for BNR enabled */
jest.spyOn(localAudioStream, 'getEffect').mockReturnValue(mockEffect as any);

expect(onSpy).toBeCalledOnceWith(
MediaSDK.LocalStreamEventNames.OutputTrackChange,
expect.any(Function)
);

mediaMetricSpy.mockClear();
onSpy.mock.calls[0][1](mockTrack as MediaStreamTrack);

expect(updateLocalTracksSpy).toBeCalledOnceWith({audio: mockTrack});

expect(mediaMetricSpy).toBeCalledOnceWith(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
call.getCallId(),
call.getCorrelationId()
);

/* Clear the mocks */
updateLocalTracksSpy.mockClear();
mediaMetricSpy.mockClear();

/* Send metrics for BNR disabled */
mockEffect.isEnabled = false;
jest.spyOn(localAudioStream, 'getEffect').mockReturnValue(mockEffect as any);

onSpy.mock.calls[0][1](mockTrack as MediaStreamTrack);

expect(updateLocalTracksSpy).toBeCalledOnceWith({audio: mockTrack});

expect(mediaMetricSpy).toBeCalledOnceWith(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_DISABLED,
METRIC_TYPE.BEHAVIORAL,
call.getCallId(),
call.getCorrelationId()
);
});

it('answer fails if localAudioTrack is empty', async () => {
Expand Down
60 changes: 57 additions & 3 deletions packages/calling/src/CallingClient/calling/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@webex/internal-media-core';
import {createMachine, interpret} from 'xstate';
import {v4 as uuid} from 'uuid';
import {EffectEvent, TrackEffect} from '@webex/web-media-effects';
import {ERROR_LAYER, ERROR_TYPE, ErrorContext} from '../../Errors/types';
import {handleCallErrors, parseMediaQualityStatistics} from '../../common/Utils';
import {
Expand Down Expand Up @@ -37,6 +38,7 @@ import {
HOLD_ENDPOINT,
INITIAL_SEQ_NUMBER,
MEDIA_ENDPOINT_RESOURCE,
NOISE_REDUCTION_EFFECT,
RESUME_ENDPOINT,
SPARK_USER_AGENT,
SUPPLEMENTARY_SERVICES_TIMEOUT,
Expand Down Expand Up @@ -80,7 +82,13 @@ import {
import log from '../../Logger';
import {ICallerId} from './CallerId/types';
import {createCallerId} from './CallerId';
import {IMetricManager, METRIC_TYPE, METRIC_EVENT, TRANSFER_ACTION} from '../../Metrics/types';
import {
IMetricManager,
METRIC_TYPE,
METRIC_EVENT,
TRANSFER_ACTION,
MEDIA_EFFECT_ACTION,
} from '../../Metrics/types';
import {getMetricManager} from '../../Metrics';
import {SERVICES_ENDPOINT} from '../../common/constants';

Expand Down Expand Up @@ -2000,6 +2008,17 @@ export class Call extends Eventing<CallEventTypes> implements ICall {
}

if (this.callStateMachine.state.value === 'S_SEND_CALL_PROGRESS') {
const effect = localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);
if (effect && effect.isEnabled) {
this.metricManager.submitBNRMetric(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
this.callId,
this.correlationId
);
}

this.sendCallStateMachineEvt({type: 'E_SEND_CALL_CONNECT'});
} else {
log.warn(
Expand Down Expand Up @@ -2036,6 +2055,17 @@ export class Call extends Eventing<CallEventTypes> implements ICall {
}

if (this.mediaStateMachine.state.value === 'S_ROAP_IDLE') {
const effect = localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);
if (effect && effect.isEnabled) {
this.metricManager.submitBNRMetric(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
this.callId,
this.correlationId
);
}

this.sendMediaStateMachineEvt({type: 'E_SEND_ROAP_OFFER'});
} else {
log.warn(
Expand Down Expand Up @@ -2430,8 +2460,32 @@ export class Call extends Eventing<CallEventTypes> implements ICall {
}

private outputTrackUpdateListener(localAudioStream: LocalMicrophoneStream) {
localAudioStream.on(LocalStreamEventNames.OutputTrackChange, (track: MediaStreamTrack) => {
this.mediaConnection.updateLocalTracks({audio: track});
let effect: any;
localAudioStream.on(LocalStreamEventNames.EffectAdded, (addedEffect: TrackEffect) => {
effect = localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);

if (effect === addedEffect) {
effect.on(EffectEvent.Enabled, () => {
this.mediaConnection.updateLocalTracks({audio: effect.effectTrack});
this.metricManager.submitBNRMetric(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_ENABLED,
METRIC_TYPE.BEHAVIORAL,
this.callId,
this.correlationId
);
});
effect.on(EffectEvent.Disabled, () => {
this.mediaConnection.updateLocalTracks({audio: effect.effectTrack});
this.metricManager.submitBNRMetric(
METRIC_EVENT.MEDIA,
MEDIA_EFFECT_ACTION.BNR_DISABLED,
METRIC_TYPE.BEHAVIORAL,
this.callId,
this.correlationId
);
});
}
});
}

Expand Down
Loading
Loading