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

Fixing the ability to choose default input in browsers when default changes #496

Merged
merged 1 commit into from
Jun 30, 2020

Conversation

RidipDe
Copy link
Contributor

@RidipDe RidipDe commented Jun 18, 2020

Issue #:
When a new audio input device is connected to a system while the 'default' system microphone is in use such that the new input device becomes the system default, the call to the method getUserMedia() with audio constraint deviceId as 'default' in chromium based browsers continues to use the previously active media stream (system microphone) instead of the the expected new stream (new audio input device). However if we call the getUserMedia() with audio constraint deviceId with alpha-numeric deviceId of the new device, it returns the stream of the new device.
When a meeting is active and we connect an external device (lets say bluetooth device) to our system and then select the default which is the bluetooth device option from the audio input drop down, the switch does not take place. The app continues to use the audio input device that it has been using before the bluetooth was connected.

Description of changes:
Change 1:
In chromium browsers running on desktops and mobile devices, the system speaker is the 'default' device id.
With an external audio device connected (like a bluetooth audio device), the external audio device becomes the 'default' but the bowser browser don't automatically switch the device. The user has to manually reselect the default device.
When choosing the new device with deviceId 'default', while the the previous device with deviceId 'default' is active, the current logic in the SDK checks the constraints of the activeDevice and the new requesting device based on just the deviceId. So when requesting the new 'default' device, the current logic returns the already active media stream as it thinks that a request to the already active 'default' device is made.
Each device also has a groupId associated. While the deviceId is unique to each device, the groupId may/may not group multiple devices into single group. So instead of just using the deviceId as a constraint check for the condition, deviceId and the corresponding groupId should be used as a unique pair to validate the check. So when a new device id added the deviceId and groupId pair will be unique.

Change 2:
Following the above problem we have another issue that needs to be addressed to solve the issue. If we try to call the getUserMedia() on the 'default' device, since the browser is already using the 'default', it is going to create a new stream but will point to the old 'default' device. So we need to ensure that the old media stream is stopped/released before requesting the new 'default' device in the getUserMedia().
The understanding here is, that the mapping between the deviceId and the device is not updated until the track is stopped.
If we call the getUserMedia() with 'default' as deviceId in the constraint without stopping the active track, it returns the previously acquired MediaStream associated to the 'default' deviceId. But once we stop the active media track and then call the getUserMedia(), it will tell the user agent that the track's source is no longer needed by the MediaStreamTrack. This way, the browser will know to clean up the existing references and update the mapping.

Repro Steps using the demo meeting app:

  1. Start a meeting with no external device in a Chromium based browser. The 'default' audio input is the system microphone.
  2. Connect an external device (bluetooth for example). Under the hood, the bluetooth becomes the new default.
  3. From the audio input drop down, select the 'default'.
    The switch in the microphone does not take place. The SDK continues to use the old 'default' microphone

Testing

  1. Have you successfully run npm run build:release locally?
    Yes
  2. How did you test these changes?
    Tested it manually. We can now select the default option in the drop down audio input menu or perform await app.audioVideo.chooseAudioInputDevice('default');
    Inter browser tests have also been performed between Chrome and Firefox

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -475,6 +481,15 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
return device && device.id ? device : null;
}

private isGroudpIdSame(groupId: string, kind: string, device: Device): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename it to hasSameGroupId?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private isGroudpIdSame(groupId: string, kind: string, device: Device): boolean {
if (device === null) return false;
if (typeof device !== 'string')
device = JSON.parse(JSON.stringify(device)).deviceId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move JSON.stringify to a variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@simmkyu

This comment has been minimized.

@RidipDe RidipDe force-pushed the groupIdMethod2 branch 8 times, most recently from 43d9d96 to c6e4e75 Compare June 19, 2020 01:32
@RidipDe RidipDe force-pushed the groupIdMethod2 branch 3 times, most recently from 29401b8 to 70467ec Compare June 22, 2020 18:01
Copy link

@andi-amzn andi-amzn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions on the PR overview:

When a meeting is active and we connect a bluetooth device to our system

  • Is this specific to Bluetooth devices?
  • What if the device is already connected, can the issue be reproduced?
  • What browser does this impact?

then select the default (which is the bluetooth device) option from the audio input drop down

  • Does this impact the SDK or just the meeting demo?

The constraint check for chooseInputDevice() now checks with the help of groupId in addition to deviceId.

  • What does this mean specifically? How is the groupId used?

This call tells the user agent that the track's source is no longer needed by the MediaStreamTrack.

  • Why does this resolve the issue?

How did you test these changes?

  • Are there unit tests to test behavior with the workaround and without the workaround?

@@ -520,9 +548,24 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
newDevice.stream = stream;
newDevice.constraints = proposedConstraints;
} else {
// TODO: remove check when the bug is fixed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When what bug is fixed?

if (
this.activeDevices[kind].constraints &&
this.activeDevices[kind].constraints.audio &&
JSON.parse(JSON.stringify(this.activeDevices[kind].constraints.audio)).deviceId

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why roundtrip through JSON?

src/devicecontroller/DefaultDeviceController.ts Outdated Show resolved Hide resolved
@@ -41,6 +41,11 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
private videoMaxBandwidthKbps: number = DefaultDeviceController.defaultVideoMaxBandwidthKbps;

private useWebAudio: boolean = false;
private deviceIdGroupIdMap: { [mediaDeviceKind: string]: Map<Device, string> } = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the group id information available in the deviceInfoCache and the activeDevices? Do we really need more state in order to solve the problem that this PR addresses?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few browsers like Safari does not have groupId as part of the device information. To make the solution oblivious of the browser I added this state so that it can have a mapping between the deviceId and the groupId (or '' if group id is missing)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the browsers that have an empty group ID require the workaround logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No browsers such as Safari does not have groupId. It has unique deviceId which is never reassigned like in the case of chromium based browser.

device &&
this.getDeviceIdStr(device) === 'default'
) {
if (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for having two ifs? Can they be combined with &&?

src/devicecontroller/DefaultDeviceController.ts Outdated Show resolved Hide resolved
@RidipDe
Copy link
Contributor Author

RidipDe commented Jun 22, 2020

Questions on the PR overview:

When a meeting is active and we connect a bluetooth device to our system

  • Is this specific to Bluetooth devices?
  • What if the device is already connected, can the issue be reproduced?
  • What browser does this impact?

then select the default (which is the bluetooth device) option from the audio input drop down

  • Does this impact the SDK or just the meeting demo?

The constraint check for chooseInputDevice() now checks with the help of groupId in addition to deviceId.

  • What does this mean specifically? How is the groupId used?

This call tells the user agent that the track's source is no longer needed by the MediaStreamTrack.

  • Why does this resolve the issue?

How did you test these changes?

  • Are there unit tests to test behavior with the workaround and without the workaround?

Is this specific to Bluetooth devices?

The issue can occur whenever an external device is attached to the system in the middle of the meeting. Bluetooth was the external device used for testing here

What if the device is already connected, can the issue be reproduced?

If the device is already connected, and the default changes while in the middle of meeting, the issue can be reproduced.

What browser does this impact?

Any browser that uses 'default' as the deviceId. Chrome and Opera are couple of examples

Does this impact the SDK or just the meeting demo?

It affects both the SDK and the meeting demo

What does this mean specifically? How is the groupId used?

groupId is being used as a condition check along with the constraint parameter in this PR.
If activeDevice (deviceId and groupId) = requestingDevice (deviceId and groupId) continue using the same device, else new create DeviceSelection() with the same deviceId and the groupId
Also, when in some browsers the groupId is undefined (like Safari), we assign a default groupId to make the solution oblivious of the browser.

Why does this resolve the issue?

When the getUserMedia() is called with 'default' as the constraint deviceId, while the stream is active, the 'default' refers to the old 'default' even after the new device was connected in the middle of the meeting and it is chosen by the OS to become the 'default'

Copy link

@andi-amzn andi-amzn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue can occur whenever an external device is attached to the system in the middle of the meeting. Bluetooth was the external device used for testing here

The PR overview makes it sound like this is fixing an issue with Bluetooth devices, but as far as what we have discussed it sounds like the type of device is not relevant.

If the device is already connected, and the default changes while in the middle of meeting, the issue can be reproduced.

The PR overview makes it sound like the device being newly connected is part of the issue repro. Could you clarify the minimal repro in the overview?

Any browser that uses 'default' as the deviceId. Chrome and Opera are couple of examples

Are there any non- Chromium-based browsers that have this ? Would it be worth putting the logic behind a BrowserBehavior flag so as not to change the logic for other browsers that do not have this issue?

It affects both the SDK and the meeting demo

The PR overview refers to the "drop down" menu item; however, from what we've discussed here it sounds like this issue lies in the SDK and the meeting demo is just one possible way to reproduce. Could you clarify the repro in terms of the actual sequence of events from the SDK perspective (e.g. chooseAudioInputDevice and other APIs)?

groupId is being used as a condition check along with the constraint parameter in this PR. If activeDevice (deviceId and groupId) = requestingDevice (deviceId and groupId) continue using the same device, else new create DeviceSelection() with the same deviceId and the groupId
Also, when in some browsers the groupId is undefined (like Safari), we assign a default groupId to make the solution oblivious of the browser.

Could you update the PR overview with this information?

When the getUserMedia() is called with 'default' as the constraint deviceId, while the stream is active, the 'default' refers to the old 'default' even after the new device was connected in the middle of the meeting and it is chosen by the OS to become the 'default'

Could you describe what we believe the browser to be doing under the hood and why releasing the media stream helps (and add to the PR overview)?

@@ -41,6 +41,11 @@ export default class DefaultDeviceController implements DeviceControllerBasedMed
private videoMaxBandwidthKbps: number = DefaultDeviceController.defaultVideoMaxBandwidthKbps;

private useWebAudio: boolean = false;
private deviceIdGroupIdMap: { [mediaDeviceKind: string]: Map<Device, string> } = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the browsers that have an empty group ID require the workaround logic?

@simmkyu
Copy link
Contributor

simmkyu commented Jun 24, 2020

@aburnson @RidipDe Based on PR #490 (closed), please take a look at my minimal solution for choosing the default: 81f5462

  • Include groupId in DeviceSelection.constraints if the given device is a string ID.
  • To avoid any regression, call getUserMedia() without a group ID as before.

@RidipDe RidipDe force-pushed the groupIdMethod2 branch 4 times, most recently from 1adaa14 to 5dcbf7b Compare June 25, 2020 16:23
@RidipDe RidipDe force-pushed the groupIdMethod2 branch 9 times, most recently from 7bc76eb to 60777c6 Compare June 27, 2020 23:39
andi-amzn
andi-amzn previously approved these changes Jun 29, 2020
@RidipDe RidipDe force-pushed the groupIdMethod2 branch 6 times, most recently from 186b3c9 to f75e759 Compare June 30, 2020 01:14
@RidipDe RidipDe merged commit fe48254 into master Jun 30, 2020
@RidipDe RidipDe deleted the groupIdMethod2 branch June 30, 2020 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants