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 30, 2020
1 parent fe44b3c commit d95e8ca
Show file tree
Hide file tree
Showing 11 changed files with 2,935 additions and 2,652 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix missing uuidv4 import in integration test
- Disable w3c check for Chrome Android webdriver integration tests
- Fix setSinkId() from throwing DOMException in Chromium browsers
- Fixing the ability to choose default input in browsers when default changes

## [1.10.0] - 2020-06-23

Expand Down
6 changes: 3 additions & 3 deletions demos/serverless/package-lock.json

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

5,144 changes: 2,584 additions & 2,560 deletions docs/assets/js/search.js

Large diffs are not rendered by default.

285 changes: 208 additions & 77 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
2 changes: 1 addition & 1 deletion 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.9",
"version": "1.10.10",
"description": "Amazon Chime SDK for JavaScript",
"main": "build/index.js",
"types": "build/index.d.ts",
Expand Down
78 changes: 74 additions & 4 deletions src/devicecontroller/DefaultDeviceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0

import AudioVideoController from '../audiovideocontroller/AudioVideoController';
import BrowserBehavior from '../browserbehavior/BrowserBehavior';
import DefaultBrowserBehavior from '../browserbehavior/DefaultBrowserBehavior';
import DeviceChangeObserver from '../devicechangeobserver/DeviceChangeObserver';
import Logger from '../logger/Logger';
import Maybe from '../maybe/Maybe';
Expand All @@ -24,6 +26,7 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
private static defaultSampleRate = 48000;
private static defaultSampleSize = 16;
private static defaultChannelCount = 1;
private browserBehavior: BrowserBehavior = new DefaultBrowserBehavior();
private deviceInfoCache: MediaDeviceInfo[] | null = null;
private activeDevices: { [kind: string]: DeviceSelection | null } = { audio: null, video: null };
private audioOutputDeviceId: string | null = null;
Expand All @@ -42,7 +45,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 +484,65 @@ 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 '';
}

getActiveDeviceId(kind: string): string | null {
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;
/* istanbul ignore else */
if (activeDeviceId as string) return activeDeviceId as string;
}
return null;
}

private async chooseInputDevice(
kind: string,
device: Device,
Expand All @@ -503,15 +564,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 +590,15 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
newDevice.stream = stream;
newDevice.constraints = proposedConstraints;
} else {
if (
this.browserBehavior.hasChromiumWebRTC() &&
this.getDeviceIdStr(device) === this.getActiveDeviceId(kind) &&
this.getDeviceIdStr(device) === 'default'
) {
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 +626,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.9';
return '1.10.10';
}

/**
Expand Down
48 changes: 45 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,38 @@ describe('DefaultDeviceController', () => {
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
});

it('releases audio media stream when requesting default device and default is already active in chromium based browser', async () => {
deviceController.enableWebAudio(true);
domMockBehavior.browserName = 'chrome';
domMockBuilder = new DOMMockBuilder(domMockBehavior);
deviceController = new DefaultDeviceController(logger);
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 +479,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 +835,12 @@ describe('DefaultDeviceController', () => {
await new Promise(resolve => new TimeoutScheduler(100).start(resolve));
});
});

describe('getActiveDeviceId', () => {
it('calls getActiveDeviceId to get the avtive device for the video', async () => {
let permission = await deviceController.chooseVideoInputDevice('default');
expect(permission).to.equal(DevicePermission.PermissionGrantedByBrowser);
expect(deviceController.getActiveDeviceId('video')).to.equal('default');
});
});
});

0 comments on commit d95e8ca

Please sign in to comment.