Home | History | Annotate | Download | only in multimedia
      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