1 # Copyright 2015 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Handler for audio extension functionality.""" 6 7 import logging 8 9 from autotest_lib.client.bin import utils 10 from autotest_lib.client.cros.multimedia import facade_resource 11 12 class AudioExtensionHandlerError(Exception): 13 pass 14 15 16 class AudioExtensionHandler(object): 17 def __init__(self, extension): 18 """Initializes an AudioExtensionHandler. 19 20 @param extension: Extension got from telemetry chrome wrapper. 21 22 """ 23 self._extension = extension 24 self._check_api_available() 25 26 27 def _check_api_available(self): 28 """Checks chrome.audio is available. 29 30 @raises: AudioExtensionHandlerError if extension is not available. 31 32 """ 33 success = utils.wait_for_value( 34 lambda: (self._extension.EvaluateJavaScript( 35 "chrome.audio") != None), 36 expected_value=True) 37 if not success: 38 raise AudioExtensionHandlerError('chrome.audio is not available.') 39 40 41 @facade_resource.retry_chrome_call 42 def get_audio_info(self): 43 """Gets the audio info from Chrome audio API. 44 45 @returns: An array of [outputInfo, inputInfo]. 46 outputInfo is an array of output node info dicts. Each dict 47 contains these key-value pairs: 48 string id 49 The unique identifier of the audio output device. 50 51 string name 52 The user-friendly name (e.g. "Bose Amplifier"). 53 54 boolean isActive 55 True if this is the current active device. 56 57 boolean isMuted 58 True if this is muted. 59 60 double volume 61 The output volume ranging from 0.0 to 100.0. 62 63 inputInfo is an arrya of input node info dicts. Each dict 64 contains these key-value pairs: 65 string id 66 The unique identifier of the audio input device. 67 68 string name 69 The user-friendly name (e.g. "USB Microphone"). 70 71 boolean isActive 72 True if this is the current active device. 73 74 boolean isMuted 75 True if this is muted. 76 77 double gain 78 The input gain ranging from 0.0 to 100.0. 79 80 """ 81 self._extension.ExecuteJavaScript('window.__audio_info = null;') 82 self._extension.ExecuteJavaScript( 83 "chrome.audio.getInfo(function(outputInfo, inputInfo) {" 84 "window.__audio_info = [outputInfo, inputInfo];})") 85 utils.wait_for_value( 86 lambda: (self._extension.EvaluateJavaScript( 87 "window.__audio_info") != None), 88 expected_value=True) 89 return self._extension.EvaluateJavaScript("window.__audio_info") 90 91 92 def _get_active_id(self): 93 """Gets active output and input node id. 94 95 Assume there is only one active output node and one active input node. 96 97 @returns: (output_id, input_id) where output_id and input_id are 98 strings for active node id. 99 100 """ 101 output_nodes, input_nodes = self.get_audio_info() 102 103 return (self._get_active_id_from_nodes(output_nodes), 104 self._get_active_id_from_nodes(input_nodes)) 105 106 107 def _get_active_id_from_nodes(self, nodes): 108 """Gets active node id from nodes. 109 110 Assume there is only one active node. 111 112 @param nodes: A list of input/output nodes got from get_audio_info(). 113 114 @returns: node['id'] where node['isActive'] is True. 115 116 @raises: AudioExtensionHandlerError if active id is not unique. 117 118 """ 119 active_ids = [x['id'] for x in nodes if x['isActive']] 120 if len(active_ids) != 1: 121 logging.error( 122 'Node info contains multiple active nodes: %s', nodes) 123 raise AudioExtensionHandlerError( 124 'Active id should be unique') 125 126 return active_ids[0] 127 128 129 130 @facade_resource.retry_chrome_call 131 def set_active_volume(self, volume): 132 """Sets the active audio output volume using chrome.audio API. 133 134 This method also unmutes the node. 135 136 @param volume: Volume to set (0~100). 137 138 """ 139 output_id, _ = self._get_active_id() 140 logging.debug('output_id: %s', output_id) 141 142 self._extension.ExecuteJavaScript('window.__set_volume_done = null;') 143 144 self._extension.ExecuteJavaScript( 145 """ 146 chrome.audio.setProperties( 147 '%s', 148 {isMuted: false, volume: %s}, 149 function() {window.__set_volume_done = true;}); 150 """ 151 % (output_id, volume)) 152 153 utils.wait_for_value( 154 lambda: (self._extension.EvaluateJavaScript( 155 "window.__set_volume_done") != None), 156 expected_value=True) 157 158 159 @facade_resource.retry_chrome_call 160 def set_mute(self, mute): 161 """Mutes the active audio output using chrome.audio API. 162 163 @param mute: True to mute. False otherwise. 164 165 """ 166 output_id, _ = self._get_active_id() 167 logging.debug('output_id: %s', output_id) 168 169 is_muted_string = 'true' if mute else 'false' 170 171 self._extension.ExecuteJavaScript('window.__set_mute_done = null;') 172 173 self._extension.ExecuteJavaScript( 174 """ 175 chrome.audio.setProperties( 176 '%s', 177 {isMuted: %s}, 178 function() {window.__set_mute_done = true;}); 179 """ 180 % (output_id, is_muted_string)) 181 182 utils.wait_for_value( 183 lambda: (self._extension.EvaluateJavaScript( 184 "window.__set_mute_done") != None), 185 expected_value=True) 186 187 188 @facade_resource.retry_chrome_call 189 def get_active_volume_mute(self): 190 """Gets the volume state of active audio output using chrome.audio API. 191 192 @param returns: A tuple (volume, mute), where volume is 0~100, and mute 193 is True if node is muted, False otherwise. 194 195 """ 196 output_nodes, _ = self.get_audio_info() 197 active_id = self._get_active_id_from_nodes(output_nodes) 198 for node in output_nodes: 199 if node['id'] == active_id: 200 return (node['volume'], node['isMuted']) 201 202 203 @facade_resource.retry_chrome_call 204 def set_active_node_id(self, node_id): 205 """Sets the active node by node id. 206 207 The current active node will be disabled first if the new active node 208 is different from the current one. 209 210 @param node_id: The node id obtained from cras_utils.get_cras_nodes. 211 Chrome.audio also uses this id to specify input/output 212 nodes. 213 214 @raises AudioExtensionHandlerError if there is no such id. 215 216 """ 217 if node_id in self._get_active_id(): 218 logging.debug('Node %s is already active.', node_id) 219 return 220 221 logging.debug('Setting active id to %s', node_id) 222 223 self._extension.ExecuteJavaScript('window.__set_active_done = null;') 224 225 self._extension.ExecuteJavaScript( 226 """ 227 chrome.audio.setActiveDevices( 228 ['%s'], 229 function() {window.__set_active_done = true;}); 230 """ 231 % (node_id)) 232 233 utils.wait_for_value( 234 lambda: (self._extension.EvaluateJavaScript( 235 "window.__set_active_done") != None), 236 expected_value=True) 237