Skip to content

Commit

Permalink
Fixing the ability to choose default input in browsers when default c…
Browse files Browse the repository at this point in the history
…hanges
  • Loading branch information
RidipDe committed Jun 27, 2020
1 parent 4c1ea4a commit 60777c6
Show file tree
Hide file tree
Showing 11 changed files with 2,902 additions and 2,629 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix multiple issues with integration tests
- Fix uuidv4 import
- Fix missing uuidv4 import in integration test.
- Fixing the ability to choose default input in browsers when default changes

## [1.10.0] - 2020-06-23

Expand Down
2 changes: 1 addition & 1 deletion demos/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
},
"dependencies": {
"amazon-chime-sdk-js": "file:../..",
"aws-sdk": "^2.704.0",
"aws-sdk": "^2.706.0",
"bootstrap": "^4.3.1",
"compression": "^1.7.4",
"jquery": "^3.4.1",
Expand Down
5,138 changes: 2,579 additions & 2,559 deletions docs/assets/js/search.js

Large diffs are not rendered by default.

197 changes: 160 additions & 37 deletions docs/classes/defaultdevicecontroller.html

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion docs/classes/deviceselection.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ <h2>Index</h2>
<h3>Properties</h3>
<ul class="tsd-index-list">
<li class="tsd-kind-property tsd-parent-kind-class"><a href="deviceselection.html#constraints" class="tsd-kind-icon">constraints</a></li>
<li class="tsd-kind-property tsd-parent-kind-class"><a href="deviceselection.html#groupid" class="tsd-kind-icon">group<wbr>Id</a></li>
<li class="tsd-kind-property tsd-parent-kind-class"><a href="deviceselection.html#stream" class="tsd-kind-icon">stream</a></li>
</ul>
</section>
Expand All @@ -106,6 +107,16 @@ <h3>constraints</h3>
</ul>
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class">
<a name="groupid" class="tsd-anchor"></a>
<h3>group<wbr>Id</h3>
<div class="tsd-signature tsd-kind-icon">group<wbr>Id<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &quot;&quot;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/master/src/devicecontroller/DeviceSelection.ts#L7">src/devicecontroller/DeviceSelection.ts:7</a></li>
</ul>
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class">
<a name="stream" class="tsd-anchor"></a>
<h3>stream</h3>
Expand All @@ -129,7 +140,7 @@ <h3>matches<wbr>Constraints</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/master/src/devicecontroller/DeviceSelection.ts#L8">src/devicecontroller/DeviceSelection.ts:8</a></li>
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/master/src/devicecontroller/DeviceSelection.ts#L9">src/devicecontroller/DeviceSelection.ts:9</a></li>
</ul>
</aside>
<h4 class="tsd-parameters-title">Parameters</h4>
Expand Down Expand Up @@ -162,6 +173,9 @@ <h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">boolean</
<li class=" tsd-kind-property tsd-parent-kind-class">
<a href="deviceselection.html#constraints" class="tsd-kind-icon">constraints</a>
</li>
<li class=" tsd-kind-property tsd-parent-kind-class">
<a href="deviceselection.html#groupid" class="tsd-kind-icon">group<wbr>Id</a>
</li>
<li class=" tsd-kind-property tsd-parent-kind-class">
<a href="deviceselection.html#stream" class="tsd-kind-icon">stream</a>
</li>
Expand Down
28 changes: 7 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amazon-chime-sdk-js",
"version": "1.10.7",
"version": "1.10.8",
"description": "Amazon Chime SDK for JavaScript",
"main": "build/index.js",
"types": "build/index.d.ts",
Expand Down
83 changes: 79 additions & 4 deletions src/devicecontroller/DefaultDeviceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import AudioVideoController from '../audiovideocontroller/AudioVideoController';
import DefaultBrowserBehavior from '../browserbehavior/DefaultBrowserBehavior';
import DeviceChangeObserver from '../devicechangeobserver/DeviceChangeObserver';
import Logger from '../logger/Logger';
import Maybe from '../maybe/Maybe';
Expand Down Expand Up @@ -42,7 +43,6 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
private videoMaxBandwidthKbps: number = DefaultDeviceController.defaultVideoMaxBandwidthKbps;

private useWebAudio: boolean = false;

private isAndroid: boolean = false;
private isPixel3: boolean = false;

Expand Down Expand Up @@ -482,6 +482,73 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
return device && device.id ? device : null;
}

private hasSameGroupId(groupId: string, kind: string, device: Device): boolean {
device = this.getDeviceIdStr(device);
if (groupId === this.getGroupIdFromDeviceId(kind, device) || groupId === '') {
return true;
}
return false;
}

private getGroupIdFromDeviceId(kind: string, device: string): string {
if (this.deviceInfoCache !== null) {
const cachedDeviceInfo = this.listCachedDevicesOfKind(`${kind}input`).find(
(cachedDevice: MediaDeviceInfo) => cachedDevice.deviceId === device
);
if (cachedDeviceInfo && cachedDeviceInfo.groupId) {
return cachedDeviceInfo.groupId;
}
}
return '';
}

private getDeviceIdStr(device: Device): string | null {
if (typeof device === 'string') {
return device;
}
if (device === null) {
return null;
}
const deviceMediaTrackConstraints = device as MediaTrackConstraints;
if ((device as MediaStream).id) {
return (device as MediaStream).id;
} else if (deviceMediaTrackConstraints.deviceId) {
/* istanbul ignore else */
if ((deviceMediaTrackConstraints.deviceId as ConstrainDOMStringParameters).exact) {
/* istanbul ignore else */
if (
(deviceMediaTrackConstraints.deviceId as ConstrainDOMStringParameters).exact as string
) {
return (deviceMediaTrackConstraints.deviceId as ConstrainDOMStringParameters)
.exact as string;
}
}
}
return '';
}

isProposedDeviceSameAsActive(
kind: string,
proposedDevice: Device,
expectedDeviceId?: string
): boolean {
if (this.activeDevices[kind] && this.activeDevices[kind].constraints) {
const activeDeviceMediaTrackConstraints =
this.activeDevices[kind].constraints.audio || this.activeDevices[kind].constraints.video;
const activeDeviceConstrainDOMStringParameters = (activeDeviceMediaTrackConstraints as MediaTrackConstraints)
.deviceId;
const activeDeviceId = (activeDeviceConstrainDOMStringParameters as ConstrainDOMStringParameters)
.exact;
if (proposedDevice && this.getDeviceIdStr(proposedDevice) === activeDeviceId) {
if (typeof expectedDeviceId !== 'undefined') {
return this.getDeviceIdStr(proposedDevice) === expectedDeviceId;
}
return true;
}
}
return false;
}

private async chooseInputDevice(
kind: string,
device: Device,
Expand All @@ -503,15 +570,17 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
kind,
device
);

if (
this.activeDevices[kind] &&
this.activeDevices[kind].matchesConstraints(proposedConstraints) &&
this.activeDevices[kind].stream.active
this.activeDevices[kind].stream.active &&
this.activeDevices[kind].groupId !== null &&
this.hasSameGroupId(this.activeDevices[kind].groupId, kind, device)
) {
this.logger.info(`reusing existing ${kind} device`);
return DevicePermission.PermissionGrantedPreviously;
}

const startTimeMs = Date.now();
const newDevice: DeviceSelection = new DeviceSelection();
try {
Expand All @@ -527,9 +596,14 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
newDevice.stream = stream;
newDevice.constraints = proposedConstraints;
} else {
if (
this.isProposedDeviceSameAsActive(kind, device, 'default') &&
new DefaultBrowserBehavior().hasChromiumWebRTC()
) {
this.releaseMediaStream(this.activeDevices[kind].stream);
}
newDevice.stream = await navigator.mediaDevices.getUserMedia(proposedConstraints);
newDevice.constraints = proposedConstraints;

if (kind === 'video' && this.lastNoVideoInputDeviceCount > callCount) {
this.logger.warn(
`ignored to get video device for constraints ${JSON.stringify(
Expand Down Expand Up @@ -557,6 +631,7 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
}
});
}
newDevice.groupId = this.getGroupIdFromDeviceId(kind, this.getDeviceIdStr(device));
} catch (error) {
this.logger.error(
`failed to get ${kind} device for constraints ${JSON.stringify(proposedConstraints)}: ${
Expand Down
3 changes: 2 additions & 1 deletion src/devicecontroller/DeviceSelection.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export default class DeviceSelection {
constraints: MediaStreamConstraints;
stream: MediaStream;
groupId: string = '';

matchesConstraints(constraints: MediaStreamConstraints): boolean {
return JSON.stringify(this.constraints) === JSON.stringify(constraints);
Expand Down
2 changes: 1 addition & 1 deletion src/versioning/Versioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class Versioning {
* Return string representation of SDK version
*/
static get sdkVersion(): string {
return '1.10.7';
return '1.10.8';
}

/**
Expand Down
59 changes: 56 additions & 3 deletions test/devicecontroller/DefaultDeviceController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ describe('DefaultDeviceController', () => {
function getMediaDeviceInfo(
deviceId: string,
kind: MediaDeviceKind,
label: string
label: string,
groupId?: string
): MediaDeviceInfo {
// @ts-ignore
return {
deviceId,
kind,
label,
groupId,
};
}

Expand Down Expand Up @@ -185,7 +187,7 @@ describe('DefaultDeviceController', () => {
expect(called).to.be.true;
});

it('cathces an error from restarting the local audio', async () => {
it('catches an error from restarting the local audio', async () => {
let called = false;

class TestAudioVideoController extends NoOpAudioVideoController {
Expand Down Expand Up @@ -218,6 +220,36 @@ describe('DefaultDeviceController', () => {
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
});

it('releases media stream when requesting default device and default is already active in chromium based browser', async () => {
deviceController.enableWebAudio(true);
domMockBehavior.browserName = 'chrome';
domMockBehavior.enumerateDeviceList = [
getMediaDeviceInfo('default', 'audioinput', 'label', 'group-id-1'),
];
await deviceController.listAudioInputDevices();
let permission = await deviceController.chooseAudioInputDevice('default');
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
domMockBehavior.enumerateDeviceList.pop();
// add external default device
domMockBehavior.enumerateDeviceList = [
getMediaDeviceInfo('default', 'audioinput', 'default - label2', 'group-id-2'),
];
domMockBuilder = new DOMMockBuilder(domMockBehavior);
await deviceController.listAudioInputDevices();
permission = await deviceController.chooseAudioInputDevice('default');
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
});

it('sets to null device when an external device disconnects', async () => {
deviceController.enableWebAudio(true);
let permission = await deviceController.chooseAudioInputDevice(stringDeviceId);
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);

// The previous audio source node will be disconneted.
permission = await deviceController.chooseAudioInputDevice(null);
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
});

it('releases all previously-acquired audio streams', done => {
const stringDeviceIds: Device[] = [
'device-id-1',
Expand Down Expand Up @@ -445,7 +477,7 @@ describe('DefaultDeviceController', () => {
expect(spy.called).to.be.false;
});

it("disconnects the audio input source node instead of the given stream 's tracks", async () => {
it("disconnects the audio input source node instead of the given stream's tracks", async () => {
deviceController.enableWebAudio(true);
await deviceController.chooseAudioInputDevice(stringDeviceId);
const stream = await deviceController.acquireAudioInputStream();
Expand Down Expand Up @@ -801,4 +833,25 @@ describe('DefaultDeviceController', () => {
await new Promise(resolve => new TimeoutScheduler(100).start(resolve));
});
});

describe('isProposedDeviceSameAsActive', () => {
it('can check if proposed device is same as the active device or not', async () => {
await deviceController.chooseAudioInputDevice('default');
let isProposedDeviceSameAsActive = deviceController.isProposedDeviceSameAsActive(
'audio',
'default'
);
expect(isProposedDeviceSameAsActive).to.equal(true);
isProposedDeviceSameAsActive = deviceController.isProposedDeviceSameAsActive(
'audio',
stringDeviceId
);
expect(isProposedDeviceSameAsActive).to.equal(false);
isProposedDeviceSameAsActive = deviceController.isProposedDeviceSameAsActive(
'video',
'default'
);
expect(isProposedDeviceSameAsActive).to.equal(false);
});
});
});

0 comments on commit 60777c6

Please sign in to comment.