| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Handler for audio extension functionality.""" |
| |
| import logging |
| |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.cros.multimedia import facade_resource |
| |
| class AudioExtensionHandlerError(Exception): |
| """Class for exceptions thrown from the AudioExtensionHandler""" |
| pass |
| |
| |
| class AudioExtensionHandler(object): |
| """Wrapper around test extension that uses chrome.audio API to get audio |
| device information |
| """ |
| def __init__(self, extension): |
| """Initializes an AudioExtensionHandler. |
| |
| @param extension: Extension got from telemetry chrome wrapper. |
| |
| """ |
| self._extension = extension |
| self._check_api_available() |
| |
| |
| def _check_api_available(self): |
| """Checks chrome.audio is available. |
| |
| @raises: AudioExtensionHandlerError if extension is not available. |
| |
| """ |
| success = utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "chrome.audio") != None), |
| expected_value=True) |
| if not success: |
| raise AudioExtensionHandlerError('chrome.audio is not available.') |
| |
| |
| @facade_resource.retry_chrome_call |
| def get_audio_devices(self, device_filter=None): |
| """Gets the audio device info from Chrome audio API. |
| |
| @param device_filter: Filter for returned device nodes. |
| An optional dict that can have the following properties: |
| string array streamTypes |
| Restricts stream types that returned devices can have. |
| It should contain "INPUT" for result to include input |
| devices, and "OUTPUT" for results to include output devices. |
| If not set, returned devices will not be filtered by the |
| stream type. |
| |
| boolean isActive |
| If true, only active devices will be included in the result. |
| If false, only inactive devices will be included in the |
| result. |
| |
| The filter param defaults to {}, requests all available audio |
| devices. |
| |
| @returns: An array of audioDeviceInfo. |
| Each audioDeviceInfo dict |
| contains these key-value pairs: |
| string id |
| The unique identifier of the audio device. |
| |
| string stableDeviceId |
| The stable identifier of the audio device. |
| |
| string streamType |
| "INPUT" if the device is an input audio device, |
| "OUTPUT" if the device is an output audio device. |
| |
| string displayName |
| The user-friendly name (e.g. "Bose Amplifier"). |
| |
| string deviceName |
| The devuce name |
| |
| boolean isActive |
| True if this is the current active device. |
| |
| boolean isMuted |
| True if this is muted. |
| |
| long level |
| The output volume or input gain. |
| |
| """ |
| def filter_to_str(device_filter): |
| """Converts python dict device filter to JS object string. |
| |
| @param device_filter: Device filter dict. |
| |
| @returns: Device filter as a srting representation of a |
| JavaScript object. |
| |
| """ |
| return str(device_filter or {}).replace('True', 'true').replace( |
| 'False', 'false') |
| |
| self._extension.ExecuteJavaScript('window.__audio_devices = null;') |
| self._extension.ExecuteJavaScript( |
| "chrome.audio.getDevices(%s, function(devices) {" |
| "window.__audio_devices = devices;})" |
| % filter_to_str(device_filter)) |
| utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "window.__audio_devices") != None), |
| expected_value=True) |
| return self._extension.EvaluateJavaScript("window.__audio_devices") |
| |
| |
| def _get_active_id_for_stream_type(self, stream_type): |
| """Gets active node id of the specified stream type. |
| |
| Assume there is only one active node. |
| |
| @param stream_type: 'INPUT' to get the active input device, |
| 'OUTPUT' to get the active output device. |
| |
| @returns: A string for the active device id. |
| |
| @raises: AudioExtensionHandlerError if active id is not unique. |
| |
| """ |
| nodes = self.get_audio_devices( |
| {'streamTypes': [stream_type], 'isActive': True}) |
| if len(nodes) != 1: |
| logging.error( |
| 'Node info contains multiple active nodes: %s', nodes) |
| raise AudioExtensionHandlerError('Active id should be unique') |
| |
| return nodes[0]['id'] |
| |
| |
| @facade_resource.retry_chrome_call |
| def set_active_volume(self, volume): |
| """Sets the active audio output volume using chrome.audio API. |
| |
| This method also unmutes the node. |
| |
| @param volume: Volume to set (0~100). |
| |
| """ |
| output_id = self._get_active_id_for_stream_type('OUTPUT') |
| logging.debug('output_id: %s', output_id) |
| |
| self.set_mute(False) |
| |
| self._extension.ExecuteJavaScript('window.__set_volume_done = null;') |
| self._extension.ExecuteJavaScript( |
| """ |
| chrome.audio.setProperties( |
| '%s', |
| {level: %s}, |
| function() {window.__set_volume_done = true;}); |
| """ |
| % (output_id, volume)) |
| utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "window.__set_volume_done") != None), |
| expected_value=True) |
| |
| |
| @facade_resource.retry_chrome_call |
| def set_mute(self, mute): |
| """Mutes the audio output using chrome.audio API. |
| |
| @param mute: True to mute. False otherwise. |
| |
| """ |
| is_muted_string = 'true' if mute else 'false' |
| |
| self._extension.ExecuteJavaScript('window.__set_mute_done = null;') |
| |
| self._extension.ExecuteJavaScript( |
| """ |
| chrome.audio.setMute( |
| 'OUTPUT', %s, |
| function() {window.__set_mute_done = true;}); |
| """ |
| % (is_muted_string)) |
| |
| utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "window.__set_mute_done") != None), |
| expected_value=True) |
| |
| |
| @facade_resource.retry_chrome_call |
| def get_mute(self): |
| """Determines whether audio output is muted. |
| |
| @returns Whether audio output is muted. |
| |
| """ |
| self._extension.ExecuteJavaScript('window.__output_muted = null;') |
| self._extension.ExecuteJavaScript( |
| "chrome.audio.getMute('OUTPUT', function(isMute) {" |
| "window.__output_muted = isMute;})") |
| utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "window.__output_muted") != None), |
| expected_value=True) |
| return self._extension.EvaluateJavaScript("window.__output_muted") |
| |
| |
| @facade_resource.retry_chrome_call |
| def get_active_volume_mute(self): |
| """Gets the volume state of active audio output using chrome.audio API. |
| |
| @param returns: A tuple (volume, mute), where volume is 0~100, and mute |
| is True if node is muted, False otherwise. |
| |
| """ |
| nodes = self.get_audio_devices( |
| {'streamTypes': ['OUTPUT'], 'isActive': True}) |
| if len(nodes) != 1: |
| logging.error('Node info contains multiple active nodes: %s', nodes) |
| raise AudioExtensionHandlerError('Active id should be unique') |
| |
| return (nodes[0]['level'], self.get_mute()) |
| |
| |
| @facade_resource.retry_chrome_call |
| def set_active_node_id(self, node_id): |
| """Sets the active node by node id. |
| |
| The current active node will be disabled first if the new active node |
| is different from the current one. |
| |
| @param node_id: Node id obtained from cras_utils.get_cras_nodes. |
| Chrome.audio also uses this id to specify input/output |
| nodes. |
| Note that node id returned by cras_utils.get_cras_nodes |
| is a number, while chrome.audio API expects a string. |
| |
| @raises AudioExtensionHandlerError if there is no such id. |
| |
| """ |
| nodes = self.get_audio_devices({}) |
| target_node = None |
| for node in nodes: |
| if node['id'] == str(node_id): |
| target_node = node |
| break |
| |
| if not target_node: |
| logging.error('Node %s not found.', node_id) |
| raise AudioExtensionHandlerError('Node id not found') |
| |
| if target_node['isActive']: |
| logging.debug('Node %s is already active.', node_id) |
| return |
| |
| logging.debug('Setting active id to %s', node_id) |
| |
| self._extension.ExecuteJavaScript('window.__set_active_done = null;') |
| |
| is_input = target_node['streamType'] == 'INPUT' |
| stream_type = 'input' if is_input else 'output' |
| self._extension.ExecuteJavaScript( |
| """ |
| chrome.audio.setActiveDevices( |
| {'%s': ['%s']}, |
| function() {window.__set_active_done = true;}); |
| """ |
| % (stream_type, node_id)) |
| |
| utils.wait_for_value( |
| lambda: (self._extension.EvaluateJavaScript( |
| "window.__set_active_done") != None), |
| expected_value=True) |