Skip to content

Commit bbfdc3d

Browse files
committed
2 parents a519c0f + e89c1a6 commit bbfdc3d

9 files changed

Lines changed: 464 additions & 40 deletions

changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
# [Unreleased](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v4.1.0...HEAD)
88

9+
### Fixed
10+
* [PCM-1428](https://inindca.atlassian.net/browse/PCM-1428) – in chrome, if you were using the system default audio device and then changed the default on the system level, the sdk would not start a audio with the new system default.
11+
12+
### Added
13+
* [PCM-1426](https://inindca.atlassian.net/browse/PCM-1426) – better logging around devices. It will log devices in use we a session starts and when the devices change.
14+
15+
916
# [v4.1.0](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v4.0.1...v4.1.0)
1017
### Added
1118
* added client logger to spigot tests for test debugability

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"test:unit": "NODE_ENV=test jest --runInBand",
3333
"test:watch": "jest --watch --collectCoverage=false --runInBand",
3434
"lint": "npm run tslint",
35+
"lint:fix": "npm run tslint:fix",
3536
"tslint": "tslint --project . --config tslint.json",
3637
"tslint:fix": "tslint --project . --config tslint.json --fix",
3738
"greenkeep": "npx npm-check --update",
@@ -102,4 +103,4 @@
102103
"ws": "^7.1.0"
103104
},
104105
"false": {}
105-
}
106+
}

src/media-utils.ts

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import browserama from 'browserama';
22

33
import { GenesysCloudWebrtcSdk } from './client';
4-
import { IMediaRequestOptions, IEnumeratedDevices } from './types/interfaces';
4+
import { IMediaRequestOptions, IEnumeratedDevices, IJingleSession } from './types/interfaces';
55
import { SdkErrorTypes } from './types/enums';
66
import { throwSdkError } from './utils';
77

@@ -227,28 +227,131 @@ export const stopListeningForDeviceChanges = function () {
227227
navigator.mediaDevices.removeEventListener('devicechange', handleDeviceChange);
228228
};
229229

230+
/**
231+
* Look through the cached enumerated devices and match based on
232+
* the passed in track's `kind` and `label`
233+
* @param track media stream track with the label to search for
234+
*/
235+
export const findCachedDeviceByTrackLabel = (track?: MediaStreamTrack): MediaDeviceInfo | undefined => {
236+
if (!track?.kind) return;
237+
const availableDevices = getCachedEnumeratedDevices();
238+
const devicesToSearch = track.kind === 'video' ? availableDevices.videoDevices : availableDevices.audioDevices;
239+
return devicesToSearch.find(d => d.label === track.label);
240+
};
241+
242+
/**
243+
* Look through the cached enumerated devices and match based on
244+
* the passed in output deviceId
245+
* @param id output deviceId
246+
*/
247+
export const findCachedOutputDeviceById = (id?: string): MediaDeviceInfo | undefined => {
248+
return getCachedEnumeratedDevices().outputDevices.find(d => d.deviceId === id);
249+
};
250+
251+
/**
252+
* Utility to log device changes. It will use the passed in `from` track _or_
253+
* look up the currently used devices via the sender (based on labels) to see
254+
* what device is currently in use. Then it will log out the device changing `to`
255+
* as the new device.
256+
*
257+
* NOTE: if the system default is being used and then changes (which will force
258+
* devices to be enumerated) the device will not be able to be looked up in the
259+
* cached devices. So the caller will need to pass in the "old" system default(s)
260+
* as `fromVideoTrack` and/or `fromAudioTrack`.
261+
*
262+
* @param sdk sdk instance
263+
* @param session session devices are changing for
264+
* @param action action taken
265+
* @param devicesChange devices changing to/from
266+
*/
267+
export function logDeviceChange (
268+
sdk: GenesysCloudWebrtcSdk,
269+
session: IJingleSession,
270+
action: 'sessionStarted' | 'changingDevices',
271+
devicesChange: {
272+
toVideoTrack?: MediaStreamTrack,
273+
fromVideoTrack?: MediaStreamTrack,
274+
toAudioTrack?: MediaStreamTrack,
275+
fromAudioTrack?: MediaStreamTrack,
276+
toOutputDeviceId?: string
277+
} = {}
278+
): void {
279+
let currentVideoTrack: MediaStreamTrack;
280+
let currentAudioTrack: MediaStreamTrack;
281+
const currentOutputDeviceId: string = session._outputAudioElement?.sinkId;
282+
const screenShareTrackId = session._screenShareStream?.getVideoTracks()[0]?.id;
283+
284+
/* grab the currect device being used */
285+
session.pc.getSenders()
286+
.filter(s => s.track && s.track.id !== screenShareTrackId)
287+
.forEach(sender => {
288+
if (sender.track.kind === 'audio') {
289+
currentAudioTrack = sender.track;
290+
} else /* if (sender.track.kind === 'video') */ {
291+
currentVideoTrack = sender.track;
292+
}
293+
});
294+
295+
const availableDevices = getCachedEnumeratedDevices();
296+
const details = {
297+
/* meta data */
298+
action,
299+
availableDevices,
300+
sessionId: session.id,
301+
conversationId: session.conversationId,
302+
/* the device being switched from and/or currently being used.
303+
if a track was passed in, we will assume the caller knows what it is doing
304+
and we will use that track for logging. Otherwise, we will look up the device */
305+
currentVideoDevice: devicesChange.fromVideoTrack ? { deviceId: undefined, groupId: undefined, label: devicesChange.fromVideoTrack.label } : findCachedDeviceByTrackLabel(currentVideoTrack),
306+
currentAudioDevice: devicesChange.fromAudioTrack ? { deviceId: undefined, groupId: undefined, label: devicesChange.fromAudioTrack.label } : findCachedDeviceByTrackLabel(currentAudioTrack),
307+
currentOutputDevice: findCachedOutputDeviceById(currentOutputDeviceId),
308+
/* the device being switched to */
309+
newVideoDevice: findCachedDeviceByTrackLabel(devicesChange.toVideoTrack),
310+
newAudioDevice: findCachedDeviceByTrackLabel(devicesChange.toAudioTrack),
311+
newOutputDevice: findCachedOutputDeviceById(devicesChange.toOutputDeviceId),
312+
/* other potentially useful information to log */
313+
sessionVideoMute: session.videoMuted,
314+
sessionAudioMute: session.audioMuted,
315+
hasDevicePermissions: hasDevicePermissions(availableDevices)
316+
};
317+
318+
sdk.logger.info('media devices changing for session', details);
319+
}
320+
321+
/**
322+
* Get the currently cached enumerated devices
323+
*/
324+
export function getCachedEnumeratedDevices (): IEnumeratedDevices {
325+
/* return a copy of cached devices */
326+
return {
327+
audioDevices: enumeratedDevices.audioDevices.slice(),
328+
videoDevices: enumeratedDevices.videoDevices.slice(),
329+
outputDevices: enumeratedDevices.outputDevices.slice()
330+
};
331+
}
332+
230333
export async function getEnumeratedDevices (sdk: GenesysCloudWebrtcSdk, forceRefresh: boolean = false): Promise<IEnumeratedDevices> {
231334
if (!window.navigator.mediaDevices || !window.navigator.mediaDevices.enumerateDevices) {
232335
sdk.logger.warn('Unable to enumerate devices');
233336
enumeratedDevices.videoDevices = [];
234337
enumeratedDevices.audioDevices = [];
235338
enumeratedDevices.outputDevices = [];
236-
return enumeratedDevices;
339+
return getCachedEnumeratedDevices();
237340
}
238341

239342
if (!isListeningForDeviceChanges) {
240343
navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange.bind(sdk));
241344
isListeningForDeviceChanges = true;
242345
}
243346

244-
/* if devices haven't changed, no forceRefresh, & we have permission - return the cache devices */
347+
/* if devices haven't changed, no forceRefresh, & we have permissions - return the cache devices */
245348
if (!refreshDevices && !forceRefresh && hasDevicePermissions(enumeratedDevices)) {
246349
sdk.logger.debug('Returning cached enumerated devices', { devices: enumeratedDevices });
247-
return enumeratedDevices;
350+
return getCachedEnumeratedDevices();
248351
}
249352

250353
try {
251-
const oldDevices = { ...enumeratedDevices };
354+
const oldDevices = getCachedEnumeratedDevices();
252355
enumeratedDevices.videoDevices = [];
253356
enumeratedDevices.audioDevices = [];
254357
enumeratedDevices.outputDevices = [];
@@ -271,7 +374,8 @@ export async function getEnumeratedDevices (sdk: GenesysCloudWebrtcSdk, forceRef
271374
}
272375

273376
sdk.logger.debug('Enumerated devices', { devices: enumeratedDevices });
274-
return enumeratedDevices;
377+
/* the "cache" was just updated with the new devices, this will make a copy */
378+
return getCachedEnumeratedDevices();
275379
}
276380

277381
function mapOldToNewDevices (oldEnumeratedDevices: IEnumeratedDevices | undefined, newDevices: MediaDeviceInfo[]): MediaDeviceInfo[] {

src/sessions/base-session-handler.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import { GenesysCloudWebrtcSdk } from '../client';
44
import { LogLevels, SessionTypes, SdkErrorTypes } from '../types/enums';
55
import { SessionManager } from './session-manager';
66
import { IPendingSession, IStartSessionParams, IAcceptSessionRequest, ISessionMuteRequest, IJingleSession, IUpdateOutgoingMedia, IJingleReason } from '../types/interfaces';
7-
import { checkHasTransceiverFunctionality, startMedia } from '../media-utils';
7+
import { checkHasTransceiverFunctionality, startMedia, logDeviceChange } from '../media-utils';
88
import { throwSdkError } from '../utils';
99
import { ConversationUpdate } from '../types/conversation-update';
10-
import browserama from 'browserama';
1110

1211
type ExtendedHTMLAudioElement = HTMLAudioElement & {
1312
setSinkId (deviceId: string): Promise<undefined>;
@@ -171,6 +170,7 @@ export default abstract class BaseSessionHandler {
171170
if (session._screenShareStream) {
172171
trackIdsToIgnore.push(...session._screenShareStream.getTracks().map((track) => track.id));
173172
}
173+
174174
/*
175175
if we aren't updating a media type, leave the existing track(s) alone
176176
also true if our video is on mute, we don't need to touch it
@@ -187,13 +187,23 @@ export default abstract class BaseSessionHandler {
187187
)
188188
);
189189

190-
/* stop media first because FF does not allow more than one audio/video track */
191-
if (browserama.isFirefox) {
192-
senders.forEach(sender => {
193-
sender.track.stop();
194-
destroyMediaPromises.push(sender.replaceTrack(null));
195-
});
196-
}
190+
/*
191+
stop media first because FF does not allow more than one audio/video track
192+
AND stop the media in case we are using system default because chrome will not let you switch
193+
system default devices _while_ you have an active media track with the old system default
194+
*/
195+
let fromVideoTrack: MediaStreamTrack;
196+
let fromAudioTrack: MediaStreamTrack;
197+
198+
senders.forEach(sender => {
199+
if (sender.track.kind === 'video') {
200+
fromVideoTrack = sender.track;
201+
} else {
202+
fromAudioTrack = sender.track;
203+
}
204+
sender.track.stop();
205+
destroyMediaPromises.push(sender.replaceTrack(null));
206+
});
197207

198208
await Promise.all(destroyMediaPromises);
199209

@@ -239,6 +249,15 @@ export default abstract class BaseSessionHandler {
239249
}
240250
});
241251

252+
/* just in case the system default is changing, we need to tell this log function what was the
253+
previous device being used (since it won't show up in the enumerated device list anymore) */
254+
logDeviceChange(this.sdk, session, 'changingDevices', {
255+
fromVideoTrack: fromVideoTrack,
256+
fromAudioTrack: fromAudioTrack,
257+
toVideoTrack: stream.getVideoTracks()[0],
258+
toAudioTrack: stream.getAudioTracks()[0]
259+
});
260+
242261
const newMediaPromises: Promise<any>[] = stream.getTracks().map(async track => {
243262
await this.addReplaceTrackToSession(session, track);
244263
outboundStream.addTrack(track);
@@ -275,7 +294,7 @@ export default abstract class BaseSessionHandler {
275294
throwSdkError.call(this.sdk, SdkErrorTypes.not_supported, err, { conversationId: session.conversationId, sessionId: session.id });
276295
}
277296

278-
this.log(LogLevels.info, 'Setting output deviceId', { deviceId, conversationId: session.conversationId, sessionId: session.id });
297+
logDeviceChange(this.sdk, session, 'changingDevices', { toOutputDeviceId: deviceId });
279298
return el.setSinkId(deviceId);
280299
}
281300

@@ -337,11 +356,11 @@ export default abstract class BaseSessionHandler {
337356
}
338357

339358
_warnNegotiationNeeded (session: IJingleSession): void {
340-
this.log(LogLevels.error, 'negotiation needed and not supported', { conversationId: session.conversationId });
359+
this.log(LogLevels.error, 'negotiation needed and not supported', { conversationId: session.conversationId, sessionId: session.id });
341360
}
342361

343362
removeMediaFromSession (session: IJingleSession, track: MediaStreamTrack): Promise<void> {
344-
this.log(LogLevels.debug, 'Removing track from session', { track, conversationId: session.conversationId });
363+
this.log(LogLevels.debug, 'Removing track from session', { track, conversationId: session.conversationId, sessionId: session.id });
345364
return session.removeTrack(track);
346365
}
347366

src/sessions/softphone-session-handler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import BaseSessionHandler from './base-session-handler';
22
import { IPendingSession, IAcceptSessionRequest, ISessionMuteRequest, IConversationParticipant, IJingleSession } from '../types/interfaces';
33
import { SessionTypes, LogLevels, SdkErrorTypes } from '../types/enums';
4-
import { attachAudioMedia, startMedia } from '../media-utils';
4+
import { attachAudioMedia, startMedia, logDeviceChange } from '../media-utils';
55
import { requestApi, throwSdkError, isSoftphoneJid } from '../utils';
66
import { pick } from 'lodash';
77

@@ -47,7 +47,8 @@ export default class SoftphoneSessionHandler extends BaseSessionHandler {
4747
});
4848
}
4949

50-
return super.acceptSession(session, params);
50+
await super.acceptSession(session, params);
51+
logDeviceChange(this.sdk, session, 'sessionStarted');
5152
}
5253

5354
async endSession (session: IJingleSession): Promise<void> {

src/sessions/video-session-handler.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from '../types/interfaces';
1616
import BaseSessionHandler from './base-session-handler';
1717
import { SessionTypes, LogLevels, SdkErrorTypes, CommunicationStates } from '../types/enums';
18-
import { createNewStreamWithTrack, startMedia, startDisplayMedia, getEnumeratedDevices } from '../media-utils';
18+
import { createNewStreamWithTrack, startMedia, startDisplayMedia, getEnumeratedDevices, logDeviceChange } from '../media-utils';
1919
import { throwSdkError, requestApi, isVideoJid, isPeerVideoJid } from '../utils';
2020
import { ConversationUpdate } from '../types/conversation-update';
2121

@@ -308,6 +308,8 @@ export default class VideoSessionHandler extends BaseSessionHandler {
308308

309309
await super.acceptSession(session, params);
310310
this.setInitialMuteStates(session);
311+
312+
logDeviceChange(this.sdk, session, 'sessionStarted');
311313
}
312314

313315
setInitialMuteStates (session: IJingleSession): void {
@@ -318,13 +320,17 @@ export default class VideoSessionHandler extends BaseSessionHandler {
318320
session.videoMuted = true;
319321
this.log(LogLevels.info, 'Sending initial video mute', { conversationId: session.conversationId });
320322
session.mute(userId, 'video');
323+
} else {
324+
session.videoMuted = false;
321325
}
322326

323327
const audioSender = session.pc.getSenders().find((sender) => sender.track && sender.track.kind === 'audio');
324328
if (!audioSender || !audioSender.track.enabled) {
325329
session.audioMuted = true;
326330
this.log(LogLevels.info, 'Sending initial audio mute', { conversationId: session.conversationId });
327331
session.mute(userId, 'audio');
332+
} else {
333+
session.audioMuted = false;
328334
}
329335
}
330336

test/test-utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ class MockStream {
162162
getVideoTracks () {
163163
return this.getTracks().filter((t) => t.kind === 'video');
164164
}
165+
getAudioTracks () {
166+
return this.getTracks().filter((t) => t.kind === 'audio');
167+
}
165168
getTracks () {
166169
return this._tracks;
167170
}

0 commit comments

Comments
 (0)