# 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):
pass
class AudioExtensionHandler(object):
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_info(self):
"""Gets the audio info from Chrome audio API.
@returns: An array of [outputInfo, inputInfo].
outputInfo is an array of output node info dicts. Each dict
contains these key-value pairs:
string id
The unique identifier of the audio output device.
string name
The user-friendly name (e.g. "Bose Amplifier").
boolean isActive
True if this is the current active device.
boolean isMuted
True if this is muted.
double volume
The output volume ranging from 0.0 to 100.0.
inputInfo is an arrya of input node info dicts. Each dict
contains these key-value pairs:
string id
The unique identifier of the audio input device.
string name
The user-friendly name (e.g. "USB Microphone").
boolean isActive
True if this is the current active device.
boolean isMuted
True if this is muted.
double gain
The input gain ranging from 0.0 to 100.0.
"""
self._extension.ExecuteJavaScript('window.__audio_info = null;')
self._extension.ExecuteJavaScript(
"chrome.audio.getInfo(function(outputInfo, inputInfo) {"
"window.__audio_info = [outputInfo, inputInfo];})")
utils.wait_for_value(
lambda: (self._extension.EvaluateJavaScript(
"window.__audio_info") != None),
expected_value=True)
return self._extension.EvaluateJavaScript("window.__audio_info")
def _get_active_id(self):
"""Gets active output and input node id.
Assume there is only one active output node and one active input node.
@returns: (output_id, input_id) where output_id and input_id are
strings for active node id.
"""
output_nodes, input_nodes = self.get_audio_info()
return (self._get_active_id_from_nodes(output_nodes),
self._get_active_id_from_nodes(input_nodes))
def _get_active_id_from_nodes(self, nodes):
"""Gets active node id from nodes.
Assume there is only one active node.
@param nodes: A list of input/output nodes got from get_audio_info().
@returns: node['id'] where node['isActive'] is True.
@raises: AudioExtensionHandlerError if active id is not unique.
"""
active_ids = [x['id'] for x in nodes if x['isActive']]
if len(active_ids) != 1:
logging.error(
'Node info contains multiple active nodes: %s', nodes)
raise AudioExtensionHandlerError(
'Active id should be unique')
return active_ids[0]
@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()
logging.debug('output_id: %s', output_id)
self._extension.ExecuteJavaScript('window.__set_volume_done = null;')
self._extension.ExecuteJavaScript(
"""
chrome.audio.setProperties(
'%s',
{isMuted: false, volume: %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 active audio output using chrome.audio API.
@param mute: True to mute. False otherwise.
"""
output_id, _ = self._get_active_id()
logging.debug('output_id: %s', output_id)
is_muted_string = 'true' if mute else 'false'
self._extension.ExecuteJavaScript('window.__set_mute_done = null;')
self._extension.ExecuteJavaScript(
"""
chrome.audio.setProperties(
'%s',
{isMuted: %s},
function() {window.__set_mute_done = true;});
"""
% (output_id, 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_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.
"""
output_nodes, _ = self.get_audio_info()
active_id = self._get_active_id_from_nodes(output_nodes)
for node in output_nodes:
if node['id'] == active_id:
return (node['volume'], node['isMuted'])
@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: The node id obtained from cras_utils.get_cras_nodes.
Chrome.audio also uses this id to specify input/output
nodes.
@raises AudioExtensionHandlerError if there is no such id.
"""
if node_id in self._get_active_id():
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;')
self._extension.ExecuteJavaScript(
"""
chrome.audio.setActiveDevices(
['%s'],
function() {window.__set_active_done = true;});
"""
% (node_id))
utils.wait_for_value(
lambda: (self._extension.EvaluateJavaScript(
"window.__set_active_done") != None),
expected_value=True)