Skip to content

Commit 06e1c99

Browse files
authored
Merge pull request #996 from MyPureCloud/release/v13.1.0
Release/v13.1.0
2 parents fe49526 + 733c881 commit 06e1c99

10 files changed

Lines changed: 402 additions & 32 deletions

File tree

changelog.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ All notable changes to this project will be documented in this file.
33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6-
# [Unreleased](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v13.0.0...HEAD)
6+
# [Unreleased](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v13.1.0...HEAD)
77

8-
# [v13.0.0](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v12.1.0...HEAD)
8+
# [v13.1.0](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v13.0.0...v13.1.0)
9+
### Added
10+
* [STREAM-1180](https://inindca.atlassian.net/browse/STREAM-1180) - Allow for different media handling strategies. This supports alerting leader functionality, where one instance of the SDK needs to handle media, but other instances should not automatically handle media.
11+
12+
# [v13.0.0](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v12.1.0...v13.0.0)
913
### Breaking Changes
1014
* [STREAM-1351](https://inindca.atlassian.net/browse/STREAM-1351) - Removed `v2.conversations.{id}.media` notification subscription. Removed the `activeVideoParticipantsUpdate` session event and `IOnScreenParticipantsUpdate` interface. Speaker and on-screen participant updates are now entirely handled via the data channel.
1115

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "genesys-cloud-webrtc-sdk",
3-
"version": "13.0.0",
3+
"version": "13.1.0",
44
"description": "client for the interfacing with Genesys Cloud WebRTC",
55
"repository": "https://github.com/mypurecloud/genesys-cloud-webrtc-sdk",
66
"license": "MIT",

src/client.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ import {
3737
} from './client-private';
3838
import { requestApi, createAndEmitSdkError, defaultConfigOption, requestApiWithRetry } from './utils';
3939
import { setupLogging } from './logging';
40-
import { SdkErrorTypes, SessionTypes } from './types/enums';
40+
import { MediaHandling, SdkErrorTypes, SessionTypes } from './types/enums';
4141
import { SessionManager } from './sessions/session-manager';
4242
import { SdkMedia } from './media/media';
4343
import { HeadsetProxyService } from './headsets/headset';
4444
import { Constants } from 'stanza';
4545
import { setupWebrtcForWindows11 } from './windows11-first-session-hack';
4646
import { ISdkHeadsetService } from './headsets/headset-types';
47+
import { SoftphoneSessionHandler } from '.';
4748

4849
const ENVIRONMENTS = [
4950
'mypurecloud.com',
@@ -111,6 +112,7 @@ export class GenesysCloudWebrtcSdk extends (EventEmitter as { new(): StrictEvent
111112
_customerData: ICustomerData;
112113
_hasConnected: boolean;
113114
_config: ISdkFullConfig;
115+
_mediaHandling = MediaHandling.standardMedia;
114116

115117
get isInitialized (): boolean {
116118
return !!this._streamingConnection;
@@ -862,6 +864,51 @@ export class GenesysCloudWebrtcSdk extends (EventEmitter as { new(): StrictEvent
862864
this.media.setDefaultAudioStream(stream);
863865
}
864866

867+
/**
868+
* **Genesys internal use only** - non-Genesys apps may experience unexpected behavior.
869+
*
870+
* Change the media handling for softphone sessions, which should be
871+
* be chosen based on the alerting leader status of the consuming client.
872+
*
873+
* If media handling is reduced, idle persistent connections will be
874+
* disconnected.
875+
*
876+
* @param mediaHandling how softphone media should be handled
877+
*/
878+
setMediaHandling (mediaHandling: MediaHandling): void {
879+
const useHeadsets = !(mediaHandling === MediaHandling.reducedMedia);
880+
881+
if (!this.sessionManager) {
882+
this._mediaHandling = mediaHandling;
883+
this.setUseHeadsets(useHeadsets);
884+
return;
885+
}
886+
887+
const activeConversations = this.sessionManager.getAllActiveConversations();
888+
889+
if (activeConversations.length !== 0 && !useHeadsets) {
890+
this._mediaHandling = MediaHandling.reducedMediaHeadsets;
891+
this.setUseHeadsets(true);
892+
throw createAndEmitSdkError.call(this, SdkErrorTypes.not_supported, 'Cannot downgrade media handling to stop using headsets during an active call');
893+
}
894+
895+
this._mediaHandling = mediaHandling;
896+
this.setUseHeadsets(useHeadsets);
897+
898+
const reduceMediaHandling = mediaHandling === MediaHandling.reducedMediaHeadsets || mediaHandling === MediaHandling.reducedMedia;
899+
if (reduceMediaHandling) {
900+
const conversationSessionIds = activeConversations.map(conversation => conversation.sessionId);
901+
// Disconnect connections that aren't associated with an active conversation.
902+
// When a client is no longer the alerting leader, it needs to give up any
903+
// persistent connections. Active calls should be maintained.
904+
this.sessionManager.getAllSessions().forEach(session => {
905+
if (session.sessionType === SessionTypes.softphone && !conversationSessionIds.includes(session.id)) {
906+
this.sessionManager.forceTerminateSession(session.id);
907+
}
908+
});
909+
}
910+
}
911+
865912
/**
866913
* Accept a pending session based on the passed in conversation ID.
867914
*

src/headsets/headset.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SdkHeadsetService } from './sdk-headset-service';
99
import { HeadsetRequestType } from '../types/interfaces';
1010
import { ExpandedConsumedHeadsetEvents, ISdkHeadsetService, OrchestrationState } from './headset-types';
1111
import { HeadsetChangesQueue } from './headset-utils';
12+
import { MediaHandling } from '../types/enums';
1213

1314
const REQUEST_PRIORITY: {[key in HeadsetControlsRequestType]: number} = {
1415
'mediaHelper': 30,
@@ -48,10 +49,15 @@ export class HeadsetProxyService implements ISdkHeadsetService {
4849
// TODO: PCM-2060 - remove this
4950
this.useHeadsetOrchestration = !this.sdk._config.disableHeadsetControlsOrchestration;
5051

52+
if (this.sdk._mediaHandling === MediaHandling.reducedMedia) {
53+
this.sdk.logger.warn('setUseHeadsets was called with `true` but media handling is set to `reducedMedia`; headsets are not supported in this configuration - not handling media. Not activating headsets.');
54+
useHeadsets = false;
55+
}
56+
5157
// currently only softphone is supported
5258
const headsetsIsSupported = this.sdk._config.allowedSessionTypes.includes(SessionTypes.softphone);
5359
if (useHeadsets && !headsetsIsSupported) {
54-
this.sdk.logger.warn('setUseHeadsets was called with `true` but headsets are not supported in this configuration. Not activating headsets.');
60+
this.sdk.logger.warn('setUseHeadsets was called with `true` but headsets are not supported in this configuration - headset is not supported. Not activating headsets.');
5561
useHeadsets = false;
5662
}
5763

@@ -141,14 +147,21 @@ export class HeadsetProxyService implements ISdkHeadsetService {
141147

142148
this.sdk.logger.info('Starting headsetCallControls orchestration');
143149

150+
let requestType: HeadsetControlsRequestType;
151+
if (this.sdk._mediaHandling === MediaHandling.alertingLeaderMedia) {
152+
requestType = 'prioritized';
153+
} else {
154+
requestType = this.sdk._config.headsetRequestType || 'standard';
155+
}
156+
144157
const headsetControlsRequest: HeadsetControlsRequest = {
145158
jsonrpc: '2.0',
146159
method: 'headsetControlsRequest',
147160
params: {
148-
requestType: this.sdk._config.headsetRequestType || 'standard'
161+
requestType
149162
}
150163
};
151-
164+
152165
this.sdk._streamingConnection.messenger.broadcastMessage({
153166
mediaMessage: headsetControlsRequest
154167
});
@@ -163,7 +176,7 @@ export class HeadsetProxyService implements ISdkHeadsetService {
163176
if (state === this.orchestrationState && !forceUpdate) {
164177
return;
165178
}
166-
179+
167180
this.sdk.logger.debug('Headset Orchestration state change', { oldState: this.orchestrationState, newState: state });
168181

169182
if (state === 'alternativeClient') {
@@ -188,7 +201,7 @@ export class HeadsetProxyService implements ISdkHeadsetService {
188201
if (msg.fromMyClient) {
189202
return;
190203
}
191-
204+
192205
switch(msg.mediaMessage.method) {
193206
case 'headsetControlsRequest':
194207
this.handleHeadsetControlsRequest(msg);
@@ -217,6 +230,21 @@ export class HeadsetProxyService implements ISdkHeadsetService {
217230
const mediaMessage = msg.mediaMessage as HeadsetControlsRequest;
218231
this.sdk.logger.debug('Received headsetControlsRequest message', { requestType: mediaMessage.params.requestType });
219232

233+
if (this.sdk._mediaHandling === MediaHandling.alertingLeaderMedia) {
234+
// we still yield to media-helper
235+
if (this.getRequestPriority(mediaMessage.params.requestType) === this.getRequestPriority('mediaHelper')) {
236+
this.sdk.logger.info('Handling alerting leader media, but yielding headset controls to media-helper', { requestType: mediaMessage.params.requestType });
237+
this.setOrchestrationState('alternativeClient');
238+
} else if (this.getRequestPriority(mediaMessage.params.requestType) === this.getRequestPriority('prioritized')) {
239+
this.sdk.logger.info('Currently handling alerting leader media, but yielding headset controls to new alerting leader', { requestType: mediaMessage.params.requestType });
240+
this.setOrchestrationState('alternativeClient');
241+
} else {
242+
this.sendControlsRejectionMessage(msg, 'priority');
243+
}
244+
245+
return;
246+
}
247+
220248
// if incoming request is lower priority, reject
221249
if (this.getRequestPriority(mediaMessage.params.requestType) < this.getRequestPriority(this.sdk._config.headsetRequestType)) {
222250
this.sendControlsRejectionMessage(msg, this.sdk._config.headsetRequestType === 'mediaHelper' ? 'mediaHelper' : 'priority');
@@ -311,7 +339,7 @@ export class HeadsetProxyService implements ISdkHeadsetService {
311339
endAllCalls (): Promise<void> {
312340
return this.currentHeadsetService.endAllCalls();
313341
}
314-
342+
315343
answerIncomingCall (conversationId: string, autoAnswer: boolean): Promise<void> {
316344
return this.currentHeadsetService.answerIncomingCall(conversationId, autoAnswer);
317345
}
@@ -331,4 +359,4 @@ export class HeadsetProxyService implements ISdkHeadsetService {
331359
resetHeadsetStateForCall(conversationId: string): Promise<void> {
332360
return this.currentHeadsetService.resetHeadsetStateForCall(conversationId);
333361
}
334-
}
362+
}

src/sessions/softphone-session-handler.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
PersistentConnectionEvent,
2020
HawkNotification
2121
} from '../types/interfaces';
22-
import { SessionTypes, SdkErrorTypes, JingleReasons, CommunicationStates } from '../types/enums';
22+
import { SessionTypes, SdkErrorTypes, JingleReasons, CommunicationStates, MediaHandling } from '../types/enums';
2323
import { attachAudioMedia, logDeviceChange, createUniqueAudioMediaElement } from '../media/media-utils';
2424
import { requestApi, isSoftphoneJid, createAndEmitSdkError, isPeerConnectionDisconnected } from '../utils';
2525
import { HeadsetChangesQueue } from '../headsets/headset-utils';
@@ -150,31 +150,51 @@ export class SoftphoneSessionHandler extends BaseSessionHandler {
150150
const isPrivAnswerAuto = pendingSession.privAnswerMode === 'Auto';
151151
const eagerConnectionEstablishmentMode = this.sdk._config.eagerPersistentConnectionEstablishment;
152152
const logInfo = { sessionId: pendingSession?.id, conversationId: pendingSession.conversationId };
153+
const reducedMediaHandling = this.sdk._mediaHandling === MediaHandling.reducedMediaHeadsets || this.sdk._mediaHandling === MediaHandling.reducedMedia;
153154

154-
if (isPrivAnswerAuto) {
155-
this.log('info', 'received a propose with privAnswerMode=Auto', logInfo);
155+
if (reducedMediaHandling) {
156+
this.log('info', 'received a propose while the SDK is configured for reduced media handling', logInfo);
156157

157-
if (eagerConnectionEstablishmentMode === 'none') {
158-
this.log('info', 'eagerPersistentConnectionEstablishment is "none" so propose with privAnswerMode=Auto will be ignored', logInfo);
159-
return;
160-
} else if (eagerConnectionEstablishmentMode === 'auto') {
161-
// we don't need to emit a pendingSession event when we auto-answer eager persistent connections
162-
return await this.proceedWithSession(pendingSession);
163-
} else {
158+
if (pendingSession.autoAnswer) {
159+
// emit the pendingSession event
164160
await super.handlePropose(pendingSession);
165-
}
166-
} else {
167-
// we want to emit the pendingSession event in all other cases
168-
await super.handlePropose(pendingSession);
169161

170-
// calls will can be marked as auto-answer or priv-answer-mode: Auto, but never both
171-
if (pendingSession.autoAnswer) {
172162
if (this.sdk._config.disableAutoAnswer) {
173163
// It is possible that the consuming client has its own logic for auto-answering calls (e.g. web-dir).
174164
this.log('info', 'received an autoAnswer tagged propose but the SDK was configured to not auto-answer, deferring to the consuming client.', logInfo);
175165
} else {
176166
await this.proceedWithSession(pendingSession);
177167
}
168+
} else {
169+
this.log('info', 'media handling is reduced, but this propose is not marked as autoAnswer and will be ignored', logInfo);
170+
return;
171+
}
172+
} else {
173+
if (isPrivAnswerAuto) {
174+
this.log('info', 'received a propose with privAnswerMode=Auto', logInfo);
175+
176+
if (eagerConnectionEstablishmentMode === 'none') {
177+
this.log('info', 'eagerPersistentConnectionEstablishment is "none" so propose with privAnswerMode=Auto will be ignored', logInfo);
178+
return;
179+
} else if (eagerConnectionEstablishmentMode === 'auto') {
180+
// we don't need to emit a pendingSession event when we auto-answer eager persistent connections
181+
return await this.proceedWithSession(pendingSession);
182+
} else {
183+
await super.handlePropose(pendingSession);
184+
}
185+
} else {
186+
// we want to emit the pendingSession event in all other cases
187+
await super.handlePropose(pendingSession);
188+
189+
// calls will can be marked as auto-answer or priv-answer-mode: Auto, but never both
190+
if (pendingSession.autoAnswer) {
191+
if (this.sdk._config.disableAutoAnswer) {
192+
// It is possible that the consuming client has its own logic for auto-answering calls (e.g. web-dir).
193+
this.log('info', 'received an autoAnswer tagged propose but the SDK was configured to not auto-answer, deferring to the consuming client.', logInfo);
194+
} else {
195+
await this.proceedWithSession(pendingSession);
196+
}
197+
}
178198
}
179199
}
180200
}

src/types/enums.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,27 @@ export enum JingleReasons {
4444
connectivityError = 'connectivity-error',
4545
alternativeSession = 'alternative-session'
4646
}
47+
48+
/** These currently only affect softphone media */
49+
export enum MediaHandling {
50+
/** Handle all media; headset controls use traditional orchestration */
51+
standardMedia = 'standard-media',
52+
/** Handle all media; headset controls follow alerting leader */
53+
alertingLeaderMedia = 'alerting-leader-media',
54+
/**
55+
* Handle some media (see below); headset controls are not used
56+
*
57+
* - New eager persistent connections will be ignored.
58+
* - Auto-answer calls will be handled, which could result in a
59+
* persistent connection being established.
60+
*/
61+
reducedMedia = 'reduced-media',
62+
/**
63+
* SDK internal use only. Handle some media (see below); headset controls use traditional orchestration.
64+
*
65+
* - New eager persistent connections will be ignored.
66+
* - Auto-answer calls will be handled, which could result in a
67+
* persistent connection being established.
68+
*/
69+
reducedMediaHeadsets = 'reduced-media-headsets',
70+
}

0 commit comments

Comments
 (0)