Merge pull request #9996 from zfox23/cleanup_selectAudioDevice

FB3651: Fix audio devices switching when they shouldn't
This commit is contained in:
Ken Cooke 2017-03-31 12:07:49 -07:00 committed by GitHub
commit fa53ebef30

View file

@ -15,95 +15,94 @@
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
if (typeof String.prototype.startsWith != 'function') { const INPUT = "Input";
String.prototype.startsWith = function (str){ const OUTPUT = "Output";
return this.slice(0, str.length) == str; function parseMenuItem(item) {
}; const USE = "Use ";
} const FOR_INPUT = " for " + INPUT;
const FOR_OUTPUT = " for " + OUTPUT;
if (typeof String.prototype.endsWith != 'function') { if (item.slice(0, USE.length) == USE) {
String.prototype.endsWith = function (str){ if (item.slice(-FOR_INPUT.length) == FOR_INPUT) {
return this.slice(-str.length) == str; return { device: item.slice(USE.length, -FOR_INPUT.length), mode: INPUT };
}; } else if (item.slice(-FOR_OUTPUT.length) == FOR_OUTPUT) {
} return { device: item.slice(USE.length, -FOR_OUTPUT.length), mode: OUTPUT };
}
if (typeof String.prototype.trimStartsWith != 'function') { }
String.prototype.trimStartsWith = function (str){
if (this.startsWith(str)) {
return this.substr(str.length);
}
return this;
};
}
if (typeof String.prototype.trimEndsWith != 'function') {
String.prototype.trimEndsWith = function (str){
if (this.endsWith(str)) {
return this.substr(0,this.length - str.length);
}
return this;
};
} }
//
// VAR DEFINITIONS
//
var debugPrintStatements = true;
const INPUT_DEVICE_SETTING = "audio_input_device"; const INPUT_DEVICE_SETTING = "audio_input_device";
const OUTPUT_DEVICE_SETTING = "audio_output_device"; const OUTPUT_DEVICE_SETTING = "audio_output_device";
var audioDevicesList = [];
var wasHmdActive = false; // assume it's not active to start
var switchedAudioInputToHMD = false;
var switchedAudioOutputToHMD = false;
var previousSelectedInputAudioDevice = "";
var previousSelectedOutputAudioDevice = "";
var skipMenuEvents = true;
var selectedInputMenu = ""; //
var selectedOutputMenu = ""; // BEGIN FUNCTION DEFINITIONS
//
var audioDevicesList = []; function debug() {
function setupAudioMenus() { if (debugPrintStatements) {
removeAudioMenus(); print.apply(null, [].concat.apply(["selectAudioDevice.js:"], [].map.call(arguments, JSON.stringify)));
Menu.addSeparator("Audio", "Input Audio Device");
var inputDeviceSetting = Settings.getValue(INPUT_DEVICE_SETTING);
var inputDevices = AudioDevice.getInputDevices();
var selectedInputDevice = AudioDevice.getInputDevice();
if (inputDevices.indexOf(inputDeviceSetting) != -1 && selectedInputDevice != inputDeviceSetting) {
if (AudioDevice.setInputDevice(inputDeviceSetting)) {
selectedInputDevice = inputDeviceSetting;
}
} }
print("audio input devices: " + inputDevices); }
for(var i = 0; i < inputDevices.length; i++) {
var thisDeviceSelected = (inputDevices[i] == selectedInputDevice);
var menuItem = "Use " + inputDevices[i] + " for Input"; function setupAudioMenus() {
// menu events can be triggered asynchronously; skip them for 200ms to avoid recursion and false switches
skipMenuEvents = true;
Script.setTimeout(function() { skipMenuEvents = false; }, 200);
removeAudioMenus();
// Setup audio input devices
Menu.addSeparator("Audio", "Input Audio Device");
var inputDevices = AudioDevice.getInputDevices();
for (var i = 0; i < inputDevices.length; i++) {
var audioDeviceMenuString = "Use " + inputDevices[i] + " for Input";
Menu.addMenuItem({ Menu.addMenuItem({
menuName: "Audio", menuName: "Audio",
menuItemName: menuItem, menuItemName: audioDeviceMenuString,
isCheckable: true, isCheckable: true,
isChecked: thisDeviceSelected isChecked: inputDevices[i] == AudioDevice.getInputDevice()
}); });
audioDevicesList.push(menuItem); audioDevicesList.push(audioDeviceMenuString);
if (thisDeviceSelected) {
selectedInputMenu = menuItem;
}
} }
// Setup audio output devices
Menu.addSeparator("Audio", "Output Audio Device"); Menu.addSeparator("Audio", "Output Audio Device");
var outputDevices = AudioDevice.getOutputDevices();
for (var i = 0; i < outputDevices.length; i++) {
var audioDeviceMenuString = "Use " + outputDevices[i] + " for Output";
Menu.addMenuItem({
menuName: "Audio",
menuItemName: audioDeviceMenuString,
isCheckable: true,
isChecked: outputDevices[i] == AudioDevice.getOutputDevice()
});
audioDevicesList.push(audioDeviceMenuString);
}
}
function checkDeviceMismatch() {
var inputDeviceSetting = Settings.getValue(INPUT_DEVICE_SETTING);
var interfaceInputDevice = AudioDevice.getInputDevice();
if (interfaceInputDevice != inputDeviceSetting) {
debug("Input Setting & Device mismatch! Input SETTING: " + inputDeviceSetting + "Input DEVICE IN USE: " + interfaceInputDevice);
switchAudioDevice("Use " + inputDeviceSetting + " for Input");
}
var outputDeviceSetting = Settings.getValue(OUTPUT_DEVICE_SETTING); var outputDeviceSetting = Settings.getValue(OUTPUT_DEVICE_SETTING);
var outputDevices = AudioDevice.getOutputDevices(); var interfaceOutputDevice = AudioDevice.getOutputDevice();
var selectedOutputDevice = AudioDevice.getOutputDevice(); if (interfaceOutputDevice != outputDeviceSetting) {
if (outputDevices.indexOf(outputDeviceSetting) != -1 && selectedOutputDevice != outputDeviceSetting) { debug("Output Setting & Device mismatch! Output SETTING: " + outputDeviceSetting + "Output DEVICE IN USE: " + interfaceOutputDevice);
if (AudioDevice.setOutputDevice(outputDeviceSetting)) { switchAudioDevice("Use " + outputDeviceSetting + " for Output");
selectedOutputDevice = outputDeviceSetting;
}
}
print("audio output devices: " + outputDevices);
for (var i = 0; i < outputDevices.length; i++) {
var thisDeviceSelected = (outputDevices[i] == selectedOutputDevice);
var menuItem = "Use " + outputDevices[i] + " for Output";
Menu.addMenuItem({
menuName: "Audio",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
});
audioDevicesList.push(menuItem);
if (thisDeviceSelected) {
selectedOutputMenu = menuItem;
}
} }
} }
@ -112,130 +111,170 @@ function removeAudioMenus() {
Menu.removeSeparator("Audio", "Output Audio Device"); Menu.removeSeparator("Audio", "Output Audio Device");
for (var index = 0; index < audioDevicesList.length; index++) { for (var index = 0; index < audioDevicesList.length; index++) {
Menu.removeMenuItem("Audio", audioDevicesList[index]); if (Menu.menuItemExists("Audio", audioDevicesList[index])) {
Menu.removeMenuItem("Audio", audioDevicesList[index]);
}
} }
Menu.removeMenu("Audio > Devices");
audioDevicesList = []; audioDevicesList = [];
} }
function onDevicechanged() { function onDevicechanged() {
print("audio devices changed, removing Audio > Devices menu..."); debug("System audio devices changed. Removing and replacing Audio Menus...");
Menu.removeMenu("Audio > Devices");
print("now setting up Audio > Devices menu");
setupAudioMenus(); setupAudioMenus();
checkDeviceMismatch();
} }
// Have a small delay before the menu's get setup and the audio devices can switch to the last selected ones function onMenuEvent(audioDeviceMenuString) {
Script.setTimeout(function () { if (!skipMenuEvents) {
print("connecting deviceChanged"); switchAudioDevice(audioDeviceMenuString);
AudioDevice.deviceChanged.connect(onDevicechanged); }
print("setting up Audio > Devices menu for first time");
setupAudioMenus();
}, 5000);
function scriptEnding() {
Menu.removeMenu("Audio > Devices");
} }
Script.scriptEnding.connect(scriptEnding);
function switchAudioDevice(audioDeviceMenuString) {
// if the device is not plugged in, short-circuit
if (!~audioDevicesList.indexOf(audioDeviceMenuString)) {
return;
}
function menuItemEvent(menuItem) { var selection = parseMenuItem(audioDeviceMenuString);
if (menuItem.startsWith("Use ")) { if (!selection) {
if (menuItem.endsWith(" for Output")) { debug("Invalid Audio audioDeviceMenuString! Doesn't end with 'for Input' or 'for Output'");
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Output"); return;
print("output audio selection..." + selectedDevice); }
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedOutputMenu, false); // menu events can be triggered asynchronously; skip them for 200ms to avoid recursion and false switches
selectedOutputMenu = menuItem; skipMenuEvents = true;
Menu.setIsOptionChecked(selectedOutputMenu, true); Script.setTimeout(function() { skipMenuEvents = false; }, 200);
if (AudioDevice.setOutputDevice(selectedDevice)) {
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice); var selectedDevice = selection.device;
} if (selection.mode == INPUT) {
Menu.menuItemEvent.connect(menuItemEvent); var currentInputDevice = AudioDevice.getInputDevice();
} else if (menuItem.endsWith(" for Input")) { if (selectedDevice != currentInputDevice) {
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Input"); debug("Switching audio INPUT device from " + currentInputDevice + " to " + selectedDevice);
print("input audio selection..." + selectedDevice); Menu.setIsOptionChecked("Use " + currentInputDevice + " for Input", false);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedInputMenu, false);
selectedInputMenu = menuItem;
Menu.setIsOptionChecked(selectedInputMenu, true);
if (AudioDevice.setInputDevice(selectedDevice)) { if (AudioDevice.setInputDevice(selectedDevice)) {
Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice); Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
} else {
debug("Error setting audio input device!")
Menu.setIsOptionChecked(audioDeviceMenuString, false);
} }
Menu.menuItemEvent.connect(menuItemEvent); } else {
debug("Selected input device is the same as the current input device!")
Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
AudioDevice.setInputDevice(selectedDevice); // Still try to force-set the device (in case the user's trying to forcefully debug an issue)
}
} else if (selection.mode == OUTPUT) {
var currentOutputDevice = AudioDevice.getOutputDevice();
if (selectedDevice != currentOutputDevice) {
debug("Switching audio OUTPUT device from " + currentOutputDevice + " to " + selectedDevice);
Menu.setIsOptionChecked("Use " + currentOutputDevice + " for Output", false);
if (AudioDevice.setOutputDevice(selectedDevice)) {
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
} else {
debug("Error setting audio output device!")
Menu.setIsOptionChecked(audioDeviceMenuString, false);
}
} else {
debug("Selected output device is the same as the current output device!")
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
AudioDevice.setOutputDevice(selectedDevice); // Still try to force-set the device (in case the user's trying to forcefully debug an issue)
} }
} }
} }
Menu.menuItemEvent.connect(menuItemEvent); function restoreAudio() {
if (switchedAudioInputToHMD) {
debug("Switching back from HMD preferred audio input to: " + previousSelectedInputAudioDevice);
switchAudioDevice("Use " + previousSelectedInputAudioDevice + " for Input");
switchedAudioInputToHMD = false;
}
if (switchedAudioOutputToHMD) {
debug("Switching back from HMD preferred audio output to: " + previousSelectedOutputAudioDevice);
switchAudioDevice("Use " + previousSelectedOutputAudioDevice + " for Output");
switchedAudioOutputToHMD = false;
}
}
// Some HMDs (like Oculus CV1) have a built in audio device. If they // Some HMDs (like Oculus CV1) have a built in audio device. If they
// do, then this function will handle switching to that device automatically // do, then this function will handle switching to that device automatically
// when you goActive with the HMD active. // when you goActive with the HMD active.
var wasHmdMounted = false; // assume it's un-mounted to start
var switchedAudioInputToHMD = false;
var switchedAudioOutputToHMD = false;
var previousSelectedInputAudioDevice = "";
var previousSelectedOutputAudioDevice = "";
function restoreAudio() {
if (switchedAudioInputToHMD) {
print("switching back from HMD preferred audio input to:" + previousSelectedInputAudioDevice);
menuItemEvent("Use " + previousSelectedInputAudioDevice + " for Input");
}
if (switchedAudioOutputToHMD) {
print("switching back from HMD preferred audio output to:" + previousSelectedOutputAudioDevice);
menuItemEvent("Use " + previousSelectedOutputAudioDevice + " for Output");
}
}
function checkHMDAudio() { function checkHMDAudio() {
// Mounted state is changing... handle switching // HMD Active state is changing; handle switching
if (HMD.mounted != wasHmdMounted) { if (HMD.active != wasHmdActive) {
print("HMD mounted changed..."); debug("HMD Active state changed!");
// We're putting the HMD on... switch to those devices // We're putting the HMD on; switch to those devices
if (HMD.mounted) { if (HMD.active) {
print("NOW mounted..."); debug("HMD is now Active.");
var hmdPreferredAudioInput = HMD.preferredAudioInput(); var hmdPreferredAudioInput = HMD.preferredAudioInput();
var hmdPreferredAudioOutput = HMD.preferredAudioOutput(); var hmdPreferredAudioOutput = HMD.preferredAudioOutput();
print("hmdPreferredAudioInput:" + hmdPreferredAudioInput); debug("hmdPreferredAudioInput: " + hmdPreferredAudioInput);
print("hmdPreferredAudioOutput:" + hmdPreferredAudioOutput); debug("hmdPreferredAudioOutput: " + hmdPreferredAudioOutput);
if (hmdPreferredAudioInput !== "") {
var hmdHasPreferredAudio = (hmdPreferredAudioInput !== "") || (hmdPreferredAudioOutput !== ""); debug("HMD has preferred audio input device.");
if (hmdHasPreferredAudio) {
print("HMD has preferred audio!");
previousSelectedInputAudioDevice = Settings.getValue(INPUT_DEVICE_SETTING); previousSelectedInputAudioDevice = Settings.getValue(INPUT_DEVICE_SETTING);
previousSelectedOutputAudioDevice = Settings.getValue(OUTPUT_DEVICE_SETTING); debug("previousSelectedInputAudioDevice: " + previousSelectedInputAudioDevice);
print("previousSelectedInputAudioDevice:" + previousSelectedInputAudioDevice); if (hmdPreferredAudioInput != previousSelectedInputAudioDevice) {
print("previousSelectedOutputAudioDevice:" + previousSelectedOutputAudioDevice);
if (hmdPreferredAudioInput != previousSelectedInputAudioDevice && hmdPreferredAudioInput !== "") {
print("switching to HMD preferred audio input to:" + hmdPreferredAudioInput);
switchedAudioInputToHMD = true; switchedAudioInputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioInput + " for Input"); switchAudioDevice("Use " + hmdPreferredAudioInput + " for Input");
} }
if (hmdPreferredAudioOutput != previousSelectedOutputAudioDevice && hmdPreferredAudioOutput !== "") { }
print("switching to HMD preferred audio output to:" + hmdPreferredAudioOutput); if (hmdPreferredAudioOutput !== "") {
debug("HMD has preferred audio output device.");
previousSelectedOutputAudioDevice = Settings.getValue(OUTPUT_DEVICE_SETTING);
debug("previousSelectedOutputAudioDevice: " + previousSelectedOutputAudioDevice);
if (hmdPreferredAudioOutput != previousSelectedOutputAudioDevice) {
switchedAudioOutputToHMD = true; switchedAudioOutputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioOutput + " for Output"); switchAudioDevice("Use " + hmdPreferredAudioOutput + " for Output");
} }
} }
} else { } else {
print("HMD NOW un-mounted..."); debug("HMD no longer active. Restoring audio I/O devices...");
restoreAudio(); restoreAudio();
} }
} }
wasHmdMounted = HMD.mounted; wasHmdActive = HMD.active;
} }
Script.update.connect(checkHMDAudio); //
// END FUNCTION DEFINITIONS
//
//
// BEGIN SCRIPT BODY
//
// Wait for the C++ systems to fire up before trying to do anything with audio devices
Script.setTimeout(function () {
debug("Connecting deviceChanged(), displayModeChanged(), and switchAudioDevice()...");
AudioDevice.deviceChanged.connect(onDevicechanged);
HMD.displayModeChanged.connect(checkHMDAudio);
Menu.menuItemEvent.connect(onMenuEvent);
debug("Setting up Audio I/O menu for the first time...");
setupAudioMenus();
checkDeviceMismatch();
debug("Checking HMD audio status...")
checkHMDAudio();
}, 3000);
debug("Connecting scriptEnding()");
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
restoreAudio(); restoreAudio();
removeAudioMenus(); removeAudioMenus();
Menu.menuItemEvent.disconnect(menuItemEvent); Menu.menuItemEvent.disconnect(onMenuEvent);
Script.update.disconnect(checkHMDAudio); HMD.displayModeChanged.disconnect(checkHMDAudio);
AudioDevice.deviceChanged.disconnect(onDevicechanged);
}); });
//
// END SCRIPT BODY
//
}()); // END LOCAL_SCOPE }()); // END LOCAL_SCOPE